|
1 | 1 | from __future__ import absolute_import
|
2 | 2 |
|
3 |
| -import socket |
4 |
| -import threading |
5 |
| -try: |
6 |
| - import SocketServer |
7 |
| -except ImportError: |
8 |
| - import socketserver as SocketServer |
9 |
| -from contextlib import contextmanager |
10 |
| - |
11 | 3 | import pytest
|
12 | 4 |
|
13 |
| -import tchannel.socket as tchannel |
14 | 5 | import tchannel.messages as tmessage
|
| 6 | +from tchannel.tchannel import TChannel |
| 7 | +from tchannel.tcurl import tcurl |
15 | 8 |
|
16 | 9 |
|
17 | 10 | @pytest.fixture
|
18 |
| -def random_open_port(): |
19 |
| - """Find and return a random open TCP port.""" |
20 |
| - sock = socket.socket(socket.AF_INET) |
21 |
| - try: |
22 |
| - sock.bind(('', 0)) |
23 |
| - return sock.getsockname()[1] |
24 |
| - finally: |
25 |
| - sock.close() |
26 |
| - |
27 |
| - |
28 |
| -class Expectation(object): |
29 |
| - """Represents an expectation for the ServerManager.""" |
30 |
| - def __init__(self, matcher): |
31 |
| - assert matcher is not None |
32 |
| - |
33 |
| - # expectation.matches(req) accepts a Message and returns True or |
34 |
| - # False. |
35 |
| - self.matches = matcher |
36 |
| - |
37 |
| - # expectation.respond(context, connection) accepts the context and the |
38 |
| - # connection and writes output to the connection. |
39 |
| - self._respond = None |
40 |
| - |
41 |
| - @classmethod |
42 |
| - def messageType(cls, msg_typ): |
43 |
| - """Build an expectation that expects a mesasge with the given type.""" |
44 |
| - return cls(lambda msg: msg.message_type == msg_typ) |
45 |
| - |
46 |
| - @property |
47 |
| - def respond(self): |
48 |
| - # Do nothing if an action setter wasn't called. |
49 |
| - if self._respond: |
50 |
| - return self._respond |
51 |
| - else: |
52 |
| - return (lambda ctx, conn: None) |
53 |
| - |
54 |
| - def and_return(self, resp): |
55 |
| - """Write the given Message as a response.""" |
56 |
| - def respond(ctx, conn): |
57 |
| - return conn.frame_and_write(resp) |
58 |
| - self._respond = respond |
59 |
| - |
60 |
| - |
61 |
| -class ServerManager(object): |
62 |
| - """Provides a dynamically configurable TChannel server.""" |
63 |
| - TIMEOUT = 0.15 |
64 |
| - |
65 |
| - def __init__(self, port, timeout=None): |
66 |
| - manager = self |
67 |
| - self.port = port |
68 |
| - self.timeout = timeout or self.TIMEOUT |
69 |
| - |
70 |
| - class Handler(SocketServer.BaseRequestHandler): |
71 |
| - def setup(self): |
72 |
| - self.request.settimeout(manager.timeout) |
73 |
| - self.tchan_conn = tchannel.SocketConnection(self.request) |
74 |
| - |
75 |
| - def handle(self): |
76 |
| - (host, port) = self.request.getsockname() |
77 |
| - self.tchan_conn.await_handshake(headers={ |
78 |
| - 'host_port': '%s:%s' % (host, port), |
79 |
| - 'process_name': 'tchannel_server-%s' % port |
80 |
| - }) |
81 |
| - self.tchan_conn.handle_calls(manager.handle_call) |
82 |
| - |
83 |
| - self.server = SocketServer.TCPServer(("", port), Handler) |
84 |
| - self.thread = None |
85 |
| - self._expectations = [] |
86 |
| - |
87 |
| - def expect_ping(self): |
88 |
| - """Expect a Ping request. |
89 |
| -
|
90 |
| - Returns an Expectation to allow setting the response behavior.""" |
91 |
| - exp = Expectation.messageType( |
92 |
| - tmessage.PingRequestMessage.message_type |
93 |
| - ) |
94 |
| - |
95 |
| - self._expectations.append(exp) |
96 |
| - return exp |
97 |
| - |
98 |
| - def handle_call(self, context, connection): |
99 |
| - for exp in self._expectations: |
100 |
| - if exp.matches(context.message): |
101 |
| - exp.respond(context, connection) |
102 |
| - |
103 |
| - @contextmanager |
104 |
| - def client_connection(self): |
105 |
| - """Get an initiated Connection to this TChannel server.""" |
106 |
| - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
107 |
| - sock.settimeout(self.timeout) |
108 |
| - try: |
109 |
| - (host, port) = ('localhost', self.port) |
110 |
| - sock.connect((host, port)) |
111 |
| - |
112 |
| - conn = tchannel.SocketConnection(sock) |
113 |
| - conn.initiate_handshake(headers={ |
114 |
| - 'host_port': '%s:%s' % (host, port), |
115 |
| - 'process_name': 'tchannel_client-%s' % port |
116 |
| - }) |
117 |
| - conn.await_handshake_reply() |
118 |
| - yield conn |
119 |
| - finally: |
120 |
| - sock.close() |
121 |
| - |
122 |
| - def start(self): |
123 |
| - assert self.thread is None, 'server already started' |
124 |
| - self.thread = threading.Thread(target=self.server.serve_forever) |
125 |
| - self.thread.start() |
126 |
| - |
127 |
| - def stop(self): |
128 |
| - self.server.shutdown() |
129 |
| - |
130 |
| - def __enter__(self): |
131 |
| - self.start() |
132 |
| - return self |
133 |
| - |
134 |
| - def __exit__(self, *args): |
135 |
| - self.stop() |
136 |
| - |
137 |
| - |
138 |
| -@pytest.yield_fixture |
139 |
| -def server_manager(random_open_port): |
140 |
| - with ServerManager(random_open_port) as manager: |
141 |
| - yield manager |
142 |
| - |
143 |
| - |
144 |
| -def test_ping_pong(server_manager): |
| 11 | +def call_response(): |
| 12 | + resp = tmessage.CallResponseMessage() |
| 13 | + resp.flags = 0 |
| 14 | + resp.code = 0 |
| 15 | + resp.span_id = 0 |
| 16 | + resp.parent_id = 0 |
| 17 | + resp.trace_id = 0 |
| 18 | + resp.traceflags = 0 |
| 19 | + resp.headers = {} |
| 20 | + resp.checksum_type = 0 |
| 21 | + resp.checksum = 0 |
| 22 | + resp.arg_1 = 'hello' |
| 23 | + resp.arg_2 = '' |
| 24 | + resp.arg_3 = 'world' |
| 25 | + return resp |
| 26 | + |
| 27 | + |
| 28 | +def test_tcp_ping_pong(server_manager): |
145 | 29 | with server_manager.client_connection() as conn:
|
146 | 30 | resp = tmessage.PingResponseMessage()
|
147 | 31 | server_manager.expect_ping().and_return(resp)
|
148 | 32 |
|
149 | 33 | for i in range(1000):
|
150 | 34 | conn.ping()
|
151 | 35 | assert resp == next(conn).message
|
| 36 | + |
| 37 | + |
| 38 | +@pytest.mark.gen_test |
| 39 | +def test_tchannel_call_request(server_manager, call_response): |
| 40 | + endpoint = 'tchannelpeertest' |
| 41 | + call_response.arg_1 = endpoint |
| 42 | + |
| 43 | + server_manager.expect_call_request(endpoint).and_return(call_response) |
| 44 | + |
| 45 | + tchannel = TChannel() |
| 46 | + |
| 47 | + hostport = 'localhost:%d' % (server_manager.port) |
| 48 | + |
| 49 | + response = yield tchannel.request(hostport).send(endpoint, '', '') |
| 50 | + |
| 51 | + assert response.arg_1 == call_response.arg_1 |
| 52 | + assert response.arg_3 == call_response.arg_3 |
| 53 | + |
| 54 | + |
| 55 | +@pytest.mark.gen_test |
| 56 | +def test_tcurl(server_manager, call_response): |
| 57 | + endpoint = 'tcurltest' |
| 58 | + call_response.arg_1 = endpoint |
| 59 | + |
| 60 | + server_manager.expect_call_request(endpoint).and_return(call_response) |
| 61 | + |
| 62 | + hostport = 'localhost:%d' % (server_manager.port) |
| 63 | + |
| 64 | + response = yield tcurl(hostport, endpoint, '', '') |
| 65 | + |
| 66 | + assert response.arg_1 == call_response.arg_1 |
| 67 | + assert response.arg_3 == call_response.arg_3 |
0 commit comments