class TestBlockingClient(unittest.TestCase): def setUp(self): self.server = DeviceTestServer('', 0) self.server.start(timeout=0.1) host, port = self.server._sock.getsockname() self.client = katcp.BlockingClient(host, port) self.client.start(timeout=0.1) def tearDown(self): if self.client.running(): self.client.stop() self.client.join() if self.server.running(): self.server.stop() self.server.join() def test_blocking_request(self): """Test blocking_request.""" reply, informs = self.client.blocking_request( katcp.Message.request("watchdog")) assert reply.name == "watchdog" assert reply.arguments == ["ok"] assert informs == [] reply, informs = self.client.blocking_request( katcp.Message.request("help")) assert reply.name == "help" assert reply.arguments == ["ok", "13"] assert len(informs) == int(reply.arguments[1]) def test_timeout(self): """Test calling blocking_request with a timeout.""" try: self.client.blocking_request( katcp.Message.request("slow-command", "0.5"), timeout=0.001) except RuntimeError, e: self.assertEqual(str(e), "Request slow-command timed out after 0.001 seconds.") else:
class TestCallbackClient(unittest.TestCase, TestUtilMixin): def setUp(self): self.addCleanup(self.stop_server_client) self.server = DeviceTestServer('', 0) self.server.start(timeout=0.1) host, port = self.server.bind_address self.client = katcp.CallbackClient(host, port) self.client.start(timeout=1) self.assertTrue(self.client.wait_protocol(timeout=1)) def stop_server_client(self): if hasattr(self, 'client') and self.client.running(): self.client.stop() self.client.join() if hasattr(self, 'server') and self.server.running(): self.server.stop() self.server.join() def test_callback_request(self): """Test callback request.""" watchdog_replies = [] watchdog_replied = threading.Event() def watchdog_reply(reply): self.assertEqual(reply.name, "watchdog") self.assertEqual(reply.arguments, ["ok"]) watchdog_replies.append(reply) watchdog_replied.set() self.assertTrue(self.client.wait_protocol(0.5)) self.client.callback_request( Message.request("watchdog"), reply_cb=watchdog_reply, ) watchdog_replied.wait(0.5) self.assertTrue(watchdog_replies) help_replies = [] help_informs = [] help_replied = threading.Event() def help_reply(reply): self.assertEqual(reply.name, "help") self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES]) self.assertEqual(len(help_informs), int(reply.arguments[1])) help_replies.append(reply) help_replied.set() def help_inform(inform): self.assertEqual(inform.name, "help") self.assertEqual(len(inform.arguments), 2) help_informs.append(inform) self.client.callback_request( Message.request("help"), reply_cb=help_reply, inform_cb=help_inform, ) help_replied.wait(1) self.assertTrue(help_replied.isSet()) help_replied.clear() help_replied.wait(0.05) # Check if (unwanted) late help replies arrive self.assertFalse(help_replied.isSet()) self.assertEqual(len(help_replies), 1) self.assertEqual(len(help_informs), NO_HELP_MESSAGES) def test_callback_request_mid(self): ## Test that the client does the right thing with message identifiers # Wait for the client to detect the server protocol. Server should # support message identifiers self.assertTrue(self.client.wait_protocol(0.2)) # Replace send_message so that we can check the message self.client.send_message = mock.Mock(wraps=self.client.send_message) # By default message identifiers should be enabled, and should start # counting at 1 cb = counting_callback(number_of_calls=3)(lambda *x: x) self.client.callback_request(Message.request('watchdog'), reply_cb=cb) self.client.callback_request(Message.request('watchdog'), reply_cb=cb) self.client.callback_request(Message.request('watchdog'), reply_cb=cb) cb.assert_wait() # Extract Message object .mid attributes from the mock calls to # send_message mids = [ args[0].mid # args[0] should be the Message() object for args, kwargs in self.client.send_message.call_args_list ] self.assertEqual(mids, ['1', '2', '3']) self.client.send_message.reset_mock() # Explicitly ask for no mid to be used cb = counting_callback(number_of_calls=1)(lambda *x: x) self.client.callback_request(Message.request('watchdog'), use_mid=False, reply_cb=cb) cb.assert_wait() mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) # Ask for a specific mid to be used self.client.send_message.reset_mock() cb = counting_callback(number_of_calls=1)(lambda *x: x) self.client.callback_request(Message.request('watchdog', mid=42), reply_cb=cb) cb.assert_wait() mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, '42') ## Check situation for a katcpv4 server self.client._server_supports_ids = False # Should fail if an mid is passed reply = [None] @counting_callback() def cb(msg): reply[0] = msg self.client.callback_request(Message.request('watchdog', mid=42), reply_cb=cb) cb.assert_wait() self.assertFalse(reply[0].reply_ok()) # Should fail if an mid is requested reply = [None] cb.reset() self.client.callback_request(Message.request('watchdog'), use_mid=True, reply_cb=cb) cb.assert_wait() self.assertFalse(reply[0].reply_ok()) # Should use no mid by default self.client.send_message.reset_mock() cb = counting_callback(number_of_calls=1)(lambda *x: x) self.client.callback_request(Message.request('watchdog'), reply_cb=cb) cb.assert_wait(timeout=1) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) def test_no_callback(self): """Test request without callback.""" help_messages = [] help_completed = threading.Event() def handle_help_message(client, msg): help_messages.append(msg) if msg.mtype == msg.REPLY: help_completed.set() self.client._inform_handlers["help"] = handle_help_message self.client._reply_handlers["help"] = handle_help_message # Set client._last_msg_id so we know that the ID is. Should be # _last_msg_id + 1 self.client._last_msg_id = 0 self.assertTrue(self.client.wait_protocol(0.2)) self.client.callback_request(Message.request("help")) help_completed.wait(1) self.assertTrue(help_completed.isSet()) self._assert_msgs_like(help_messages, [("#help[1] ", "")] * NO_HELP_MESSAGES + [("!help[1] ok %d" % NO_HELP_MESSAGES, "")]) def test_timeout(self): self._test_timeout() def test_timeout_nomid(self, use_mid=False): self._test_timeout(use_mid=False) def _test_timeout(self, use_mid=None): """Test requests that timeout.""" replies = [] replied = threading.Event() informs = [] timeout = 0.001 @counting_callback() def reply_cb(msg): replies.append(msg) replied.set() def inform_cb(msg): informs.append(msg) self.assertTrue(self.client.wait_protocol(0.2)) self.client.callback_request( Message.request("slow-command", "0.1"), use_mid=use_mid, reply_cb=reply_cb, inform_cb=inform_cb, timeout=timeout, ) reply_cb.assert_wait(1) self.client.request(Message.request('cancel-slow-command')) msg = replies[0] self.assertEqual(msg.name, "slow-command") self.assertFalse(msg.reply_ok()) self.assertRegexpMatches( msg.arguments[1], r"Request slow-command timed out after 0\..* seconds.") self.assertEqual(len(remove_version_connect(informs)), 0) self.assertEqual(len(replies), 1) del replies[:] del informs[:] reply_cb.reset() # test next request succeeds self.client.callback_request( Message.request("slow-command", "0.05"), reply_cb=reply_cb, inform_cb=inform_cb, ) reply_cb.assert_wait() self.assertEqual(len(replies), 1) self.assertEqual(len(informs), 0) self.assertEqual([msg.name for msg in replies + informs], ["slow-command"] * len(replies + informs)) self.assertEqual([msg.arguments for msg in replies], [["ok"]]) def test_timeout_nocb(self): """Test requests that timeout with no callbacks.""" # Included to test https://katfs.kat.ac.za/mantis/view.php?id=1722 # Situation can occur during a race between the timeout handler and the # receipt of a reply -- the reply can arrive after the timeout timer has # expired but before the request has been popped off the stack with # client._pop_async_request(). The normal request handler then pops off # the request first, resulting in the timeout handler getting a bunch of # None's. It should handle this gracefully. # Running the handler with a fake msg_id should have the same result as # running it after a real request has already been popped. The expected # result is that no assertions are raised. # NM 2014-09-26: This is probably no longer an issue with the tornado-based client # implementatin, but leaving the test for good measure f = Future() @gen.coroutine def cb(): self.client._handle_timeout('fake_msg_id', time.time()) self.client.ioloop.add_callback(lambda: gen.chain_future(cb(), f)) f.result(timeout=1) def test_user_data(self): """Test callbacks with user data.""" help_replies = [] help_informs = [] done = threading.Event() def help_reply(reply, x, y): self.assertEqual(reply.name, "help") self.assertEqual(x, 5) self.assertEqual(y, "foo") help_replies.append(reply) done.set() def help_inform(inform, x, y): self.assertEqual(inform.name, "help") self.assertEqual(x, 5) self.assertEqual(y, "foo") help_informs.append(inform) self.client.callback_request(Message.request("help"), reply_cb=help_reply, inform_cb=help_inform, user_data=(5, "foo")) done.wait(1) # Wait a bit longer to see if spurious replies arrive time.sleep(0.01) self.assertEqual(len(help_replies), 1) self.assertEqual(len(remove_version_connect(help_informs)), NO_HELP_MESSAGES) def test_fifty_thread_mayhem(self): """Test using callbacks from fifty threads simultaneously.""" num_threads = 50 # map from thread_id -> (replies, informs) results = {} # list of thread objects threads = [] def reply_cb(reply, thread_id): results[thread_id][0].append(reply) results[thread_id][2].set() def inform_cb(inform, thread_id): results[thread_id][1].append(inform) def worker(thread_id, request): self.client.callback_request( request.copy(), reply_cb=reply_cb, inform_cb=inform_cb, user_data=(thread_id, ), ) request = Message.request("help") for thread_id in range(num_threads): results[thread_id] = ([], [], threading.Event()) for thread_id in range(num_threads): thread = threading.Thread(target=worker, args=(thread_id, request)) threads.append(thread) for thread in threads: thread.start() for thread in threads: thread.join() for thread_id in range(num_threads): replies, informs, done = results[thread_id] done.wait(5.0) self.assertTrue(done.isSet()) self.assertEqual(len(replies), 1) self.assertEqual(replies[0].arguments[0], "ok") informs = remove_version_connect(informs) if len(informs) != NO_HELP_MESSAGES: print thread_id, len(informs) print[x.arguments[0] for x in informs] self.assertEqual(len(informs), NO_HELP_MESSAGES) def test_blocking_request(self): """Test the callback client's blocking request.""" reply, informs = self.client.blocking_request( Message.request("help"), ) self.assertEqual(reply.name, "help") self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES]) self.assertEqual(len(remove_version_connect(informs)), NO_HELP_MESSAGES) reply, informs = self.client.blocking_request(Message.request( "slow-command", "0.5"), timeout=0.001) self.assertEqual(reply.name, "slow-command") self.assertEqual(reply.arguments[0], "fail") self.assertRegexpMatches( reply.arguments[1], r"Request slow-command timed out after 0\..* seconds.") def test_blocking_request_mid(self): ## Test that the blocking client does the right thing with message ## identifiers # Wait for the client to detect the server protocol. Server should # support message identifiers self.assertTrue(self.client.wait_protocol(1)) # Replace send_message so that we can check the message self.client.send_message = mock.Mock() # By default message identifiers should be enabled, and should start # counting at 1 self.client.blocking_request(Message.request('watchdog'), timeout=0) self.client.blocking_request(Message.request('watchdog'), timeout=0) self.client.blocking_request(Message.request('watchdog'), timeout=0) # Extract Message object .mid attributes from the mock calls to # send_message mids = [ args[0].mid # arg[0] should be the Message() object for args, kwargs in self.client.send_message.call_args_list ] self.assertEqual(mids, ['1', '2', '3']) self.client.send_message.reset_mock() # Explicitly ask for no mid to be used self.client.blocking_request(Message.request('watchdog'), use_mid=False, timeout=0) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) # Ask for a specific mid to be used self.client.send_message.reset_mock() self.client.blocking_request(Message.request('watchdog', mid=42), timeout=0) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, '42') ## Check situation for a katcpv4 server self.client._server_supports_ids = False # Should fail if an mid is passed reply, inform = self.client.blocking_request(Message.request( 'watchdog', mid=42), timeout=0) self.assertFalse(reply.reply_ok()) # Should fail if an mid is requested reply, inform = self.client.blocking_request( Message.request('watchdog'), use_mid=True, timeout=0) self.assertFalse(reply.reply_ok()) # Should use no mid by default self.client.send_message.reset_mock() self.client.blocking_request(Message.request('watchdog'), timeout=0) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) def test_request_fail_on_raise(self): """Test that the callback is called even if send_message raises KatcpClientError.""" def raise_error(msg, timeout=None): raise katcp.KatcpClientError("Error %s" % msg.name) self.client.send_message = raise_error replies = [] @counting_callback() def reply_cb(msg): replies.append(msg) self.client.callback_request( Message.request("foo"), reply_cb=reply_cb, ) reply_cb.assert_wait() self.assertEqual(len(replies), 1) self.assertEqual(replies[0].name, "foo") self.assertEqual(replies[0].arguments, ["fail", "Error foo"]) def test_stop_cleanup(self): self.client.wait_protocol(timeout=1) mid = 56 future_reply = Future() self.client.ioloop.add_callback(lambda: gen.chain_future( self.client.future_request( Message.request('slow-command', 1, mid=mid)), future_reply)) # Force a disconnect self.client.stop() reply, informs = future_reply.result(timeout=1) self.assertEqual( reply, Message.reply('slow-command', 'fail', 'Client stopped before reply was received', mid=mid))
class TestCallbackClient(unittest.TestCase, TestUtilMixin): def setUp(self): self.addCleanup(self.stop_server_client) self.server = DeviceTestServer('', 0) self.server.start(timeout=0.1) host, port = self.server._sock.getsockname() self.client = katcp.CallbackClient(host, port) self.client.start(timeout=0.1) self.assertTrue(self.client.wait_protocol(timeout=1)) def stop_server_client(self): if hasattr(self, 'client') and self.client.running(): self.client.stop() self.client.join() if hasattr(self, 'server') and self.server.running(): self.server.stop() self.server.join() def test_callback_request(self): """Test callback request.""" watchdog_replies = [] watchdog_replied = threading.Event() def watchdog_reply(reply): self.assertEqual(reply.name, "watchdog") self.assertEqual(reply.arguments, ["ok"]) watchdog_replies.append(reply) watchdog_replied.set() self.assertTrue(self.client.wait_protocol(0.2)) self.client.callback_request( katcp.Message.request("watchdog"), reply_cb=watchdog_reply, ) watchdog_replied.wait(0.2) self.assertTrue(watchdog_replies) help_replies = [] help_informs = [] help_replied = threading.Event() def help_reply(reply): self.assertEqual(reply.name, "help") self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES]) self.assertEqual(len(help_informs), int(reply.arguments[1])) help_replies.append(reply) help_replied.set() def help_inform(inform): self.assertEqual(inform.name, "help") self.assertEqual(len(inform.arguments), 2) help_informs.append(inform) self.client.callback_request( katcp.Message.request("help"), reply_cb=help_reply, inform_cb=help_inform, ) help_replied.wait(1) self.assertTrue(help_replied.isSet()) help_replied.clear() help_replied.wait(0.05) # Check if (unwanted) late help replies arrive self.assertFalse(help_replied.isSet()) self.assertEqual(len(help_replies), 1) self.assertEqual(len(help_informs), NO_HELP_MESSAGES) def test_callback_request_mid(self): ## Test that the client does the right thing with message identifiers # Wait for the client to detect the server protocol. Server should # support message identifiers self.assertTrue(self.client.wait_protocol(0.2)) # Replace send_message so that we can check the message self.client.send_message = mock.Mock() # By default message identifiers should be enabled, and should start # counting at 1 self.client.callback_request(katcp.Message.request('watchdog')) self.client.callback_request(katcp.Message.request('watchdog')) self.client.callback_request(katcp.Message.request('watchdog')) # Extract katcp.Message object .mid attributes from the mock calls to # send_message mids = [args[0].mid # arg[0] should be the Message() object for args, kwargs in self.client.send_message.call_args_list] self.assertEqual(mids, ['1','2','3']) self.client.send_message.reset_mock() # Explicitly ask for no mid to be used self.client.callback_request( katcp.Message.request('watchdog'), use_mid=False) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) # Ask for a specific mid to be used self.client.send_message.reset_mock() self.client.callback_request(katcp.Message.request('watchdog', mid=42)) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, '42') ## Check situation for a katcpv4 server self.client._server_supports_ids = False # Should fail if an mid is passed with self.assertRaises(katcp.core.KatcpVersionError): self.client.callback_request( katcp.Message.request('watchdog', mid=42)) # Should fail if an mid is requested with self.assertRaises(katcp.core.KatcpVersionError): self.client.callback_request( katcp.Message.request('watchdog'), use_mid=True) # Should use no mid by default self.client.send_message.reset_mock() self.client.callback_request(katcp.Message.request('watchdog')) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) def test_no_callback(self): """Test request without callback.""" help_messages = [] help_completed = threading.Event() def handle_help_message(client, msg): help_messages.append(msg) if msg.mtype == msg.REPLY: help_completed.set() self.client._inform_handlers["help"] = handle_help_message self.client._reply_handlers["help"] = handle_help_message # Set client._last_msg_id so we know that the ID is. Should be # _last_msg_id + 1 self.client._last_msg_id = 0 self.assertTrue(self.client.wait_protocol(0.2)) self.client.callback_request(katcp.Message.request("help")) help_completed.wait(1) self.assertTrue(help_completed.isSet()) self._assert_msgs_like(help_messages, [("#help[1] ", "")] * NO_HELP_MESSAGES + [("!help[1] ok %d" % NO_HELP_MESSAGES, "")]) def test_no_timeout(self): self.client._request_timeout = None replies = [] informs = [] replied = threading.Event() def reply_handler(reply): replies.append(reply) replied.set() def inform_handler(reply): informs.append(reply) with mock.patch('katcp.client.threading.Timer') as MockTimer: self.client.callback_request(katcp.Message.request("help"), reply_cb=reply_handler, inform_cb=inform_handler) replied.wait(1) # With no timeout no Timer object should have been instantiated self.assertEqual(MockTimer.call_count, 0) self.assertEqual(len(replies), 1) self.assertEqual(len(remove_version_connect(informs)), NO_HELP_MESSAGES) def test_timeout(self): self._test_timeout() def test_timeout_nomid(self, use_mid=False): self._test_timeout(use_mid=False) def _test_timeout(self, use_mid=None): """Test requests that timeout.""" replies = [] replied = threading.Event() informs = [] timeout = 0.001 @counting_callback() def reply_cb(msg): replies.append(msg) replied.set() def inform_cb(msg): informs.append(msg) self.assertTrue(self.client.wait_protocol(0.2)) self.client.callback_request( katcp.Message.request("slow-command", "0.1"), use_mid=use_mid, reply_cb=reply_cb, inform_cb=inform_cb, timeout=timeout, ) reply_cb.assert_wait(1) self.assertEqual(len(replies), 1) self.assertEqual([msg.name for msg in replies], ["slow-command"]) self.assertEqual([msg.arguments for msg in replies], [ ["fail", "Timed out after %f seconds" % timeout]]) self.assertEqual(len(remove_version_connect(informs)), 0) del replies[:] del informs[:] reply_cb.reset() # test next request succeeds self.client.callback_request( katcp.Message.request("slow-command", "0.05"), reply_cb=reply_cb, inform_cb=inform_cb, ) reply_cb.assert_wait(1) self.assertEqual(len(replies), 1) self.assertEqual(len(informs), 0) self.assertEqual([msg.name for msg in replies + informs], ["slow-command"] * len(replies + informs)) self.assertEqual([msg.arguments for msg in replies], [["ok"]]) def test_timeout_nocb(self): """Test requests that timeout with no callbacks.""" # Included to test https://katfs.kat.ac.za/mantis/view.php?id=1722 # Situation can occur during a race between the timeout handler and the # receipt of a reply -- the reply can arrive after the timeout timer has # expired but before the request has been popped off the stack with # client._pop_async_request(). The normal request handler then pops off # the request first, resulting in the timeout handler getting a bunch of # None's. It should handle this gracefully. # Running the handler with a fake msg_id should have the same result as # running it after a real request has already been popped. The expected # result is that no assertions are raised. self.client._handle_timeout('fake_msg_id') def test_user_data(self): """Test callbacks with user data.""" help_replies = [] help_informs = [] done = threading.Event() def help_reply(reply, x, y): self.assertEqual(reply.name, "help") self.assertEqual(x, 5) self.assertEqual(y, "foo") help_replies.append(reply) done.set() def help_inform(inform, x, y): self.assertEqual(inform.name, "help") self.assertEqual(x, 5) self.assertEqual(y, "foo") help_informs.append(inform) self.client.callback_request( katcp.Message.request("help"), reply_cb=help_reply, inform_cb=help_inform, user_data=(5, "foo")) done.wait(1) # Wait a bit longer to see if spurious replies arrive time.sleep(0.01) self.assertEqual(len(help_replies), 1) self.assertEqual(len(remove_version_connect(help_informs)), NO_HELP_MESSAGES) def test_fifty_thread_mayhem(self): """Test using callbacks from fifty threads simultaneously.""" num_threads = 50 # map from thread_id -> (replies, informs) results = {} # list of thread objects threads = [] def reply_cb(reply, thread_id): results[thread_id][0].append(reply) results[thread_id][2].set() def inform_cb(inform, thread_id): results[thread_id][1].append(inform) def worker(thread_id, request): self.client.callback_request( request.copy(), reply_cb=reply_cb, inform_cb=inform_cb, user_data=(thread_id,), ) request = katcp.Message.request("help") for thread_id in range(num_threads): results[thread_id] = ([], [], threading.Event()) for thread_id in range(num_threads): thread = threading.Thread(target=worker, args=(thread_id, request)) threads.append(thread) for thread in threads: thread.start() for thread in threads: thread.join() for thread_id in range(num_threads): replies, informs, done = results[thread_id] done.wait(5.0) self.assertTrue(done.isSet()) self.assertEqual(len(replies), 1) self.assertEqual(replies[0].arguments[0], "ok") informs = remove_version_connect(informs) if len(informs) != NO_HELP_MESSAGES: print thread_id, len(informs) print [x.arguments[0] for x in informs] self.assertEqual(len(informs), NO_HELP_MESSAGES) def test_blocking_request(self): """Test the callback client's blocking request.""" reply, informs = self.client.blocking_request( katcp.Message.request("help"), ) self.assertEqual(reply.name, "help") self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES]) self.assertEqual(len(remove_version_connect(informs)), NO_HELP_MESSAGES) reply, informs = self.client.blocking_request( katcp.Message.request("slow-command", "0.5"), timeout=0.001) self.assertEqual(reply.name, "slow-command") self.assertEqual(reply.arguments[0], "fail") self.assertTrue(reply.arguments[1].startswith("Timed out after")) def test_blocking_request_mid(self): ## Test that the blocking client does the right thing with message ## identifiers # Wait for the client to detect the server protocol. Server should # support message identifiers self.assertTrue(self.client.wait_protocol(0.2)) # Replace send_message so that we can check the message self.client.send_message = mock.Mock() # By default message identifiers should be enabled, and should start # counting at 1 self.client.blocking_request( katcp.Message.request('watchdog'), timeout=0) self.client.blocking_request( katcp.Message.request('watchdog'), timeout=0) self.client.blocking_request( katcp.Message.request('watchdog'), timeout=0) # Extract katcp.Message object .mid attributes from the mock calls to # send_message mids = [args[0].mid # arg[0] should be the Message() object for args, kwargs in self.client.send_message.call_args_list] self.assertEqual(mids, ['1','2','3']) self.client.send_message.reset_mock() # Explicitly ask for no mid to be used self.client.blocking_request(katcp.Message.request('watchdog'), use_mid=False, timeout=0) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) # Ask for a specific mid to be used self.client.send_message.reset_mock() self.client.blocking_request(katcp.Message.request( 'watchdog', mid=42), timeout=0) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, '42') ## Check situation for a katcpv4 server self.client._server_supports_ids = False # Should fail if an mid is passed with self.assertRaises(katcp.core.KatcpVersionError): self.client.blocking_request(katcp.Message.request( 'watchdog', mid=42), timeout=0) # Should fail if an mid is requested with self.assertRaises(katcp.core.KatcpVersionError): self.client.blocking_request( katcp.Message.request('watchdog'), use_mid=True, timeout=0) # Should use no mid by default self.client.send_message.reset_mock() self.client.blocking_request(katcp.Message.request( 'watchdog'), timeout=0) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) def test_request_fail_on_raise(self): """Test that the callback is called even if send_message raises KatcpClientError.""" def raise_error(msg, timeout=None): raise katcp.KatcpClientError("Error %s" % msg.name) self.client.send_message = raise_error replies = [] def reply_cb(msg): replies.append(msg) self.client.callback_request(katcp.Message.request("foo"), reply_cb=reply_cb, ) self.assertEqual(len(replies), 1) self.assertEqual(replies[0].name, "foo") self.assertEqual(replies[0].arguments, ["fail", "Error foo"]) def test_stop_join(self): # Set up a slow command to ensure that there is something in # the async queue with mock.patch('katcp.client.threading.Timer') as MockTimer: instance = MockTimer.return_value self.client.callback_request( katcp.Message.request("slow-command", "10000"), timeout=10000.1) # Exactly one instance of MockTimer should have been instantiated self.assertEqual(MockTimer.call_count, 1) self.client.stop(timeout=0.5) # Check that the timer's cancel() method has been called instance.cancel.assert_called_once_with() self.client.join(timeout=0.45) instance.join.assert_called_once_with(timeout=0.45) # It's OK not to have this in teardown since the client will # itself cancel the slow_command when it is stop()ed self.client.blocking_request(katcp.Message.request("cancel-slow-command"))
class TestDeviceClientIntegrated(unittest.TestCase, TestUtilMixin): def setUp(self): self.server = DeviceTestServer('', 0) self.server.start(timeout=0.1) host, port = self.server._sock.getsockname() self.client = katcp.DeviceClient(host, port) self.client.start(timeout=0.1) def tearDown(self): if self.client.running(): self.client.stop() self.client.join() if self.server.running(): self.server.stop() self.server.join() def test_request(self): """Test request method.""" self.assertTrue(self.client.wait_protocol(1)) self.client.send_request(katcp.Message.request("watchdog")) self.client.send_request(katcp.Message.request("watchdog", mid=55)) self.client._server_supports_ids = False with self.assertRaises(katcp.core.KatcpVersionError): self.client.send_request(katcp.Message.request("watchdog", mid=56)) time.sleep(0.1) msgs = self.server.messages() self._assert_msgs_equal(msgs, [ r"?watchdog", r"?watchdog[55]", ]) def test_send_message(self): """Test send_message method.""" self.client.send_message(katcp.Message.inform("random-inform")) time.sleep(0.1) msgs = self.server.messages() self._assert_msgs_equal(msgs, [ r"#random-inform", ]) def test_stop_and_restart(self): """Test stopping and then restarting a client.""" self.client.stop(timeout=0.1) # timeout needs to be longer than select sleep. self.client.join(timeout=1.5) self.assertEqual(self.client._thread, None) self.assertFalse(self.client._running.isSet()) self.client.start(timeout=0.1) def test_is_connected(self): """Test is_connected method.""" self.assertTrue(self.client.is_connected()) self.server.stop(timeout=0.1) # timeout needs to be longer than select sleep. self.server.join(timeout=1.5) self.assertFalse(self.client.is_connected()) def test_wait_connected(self): """Test wait_connected method.""" start = time.time() self.assertTrue(self.client.wait_connected(1.0)) self.assertTrue(time.time() - start < 1.0) self.server.stop(timeout=0.1) # timeout needs to be longer than select sleep. self.server.join(timeout=1.5) start = time.time() self.assertFalse(self.client.wait_connected(0.2)) self.assertTrue(0.15 < time.time() - start < 0.25) def test_bad_socket(self): """Test what happens when select is called on a dead socket.""" # wait for client to connect time.sleep(0.1) # close socket while the client isn't looking # then wait for the client to notice sock = self.client._sock sockname = sock.getpeername() sock.close() time.sleep(1.25) # check that client reconnected self.assertTrue(sock is not self.client._sock, "Expected %r to not be %r" % (sock, self.client._sock)) self.assertEqual(sockname, self.client._sock.getpeername()) def test_daemon_value(self): """Test passing in a daemon value to client start method.""" self.client.stop(timeout=0.1) # timeout needs to be longer than select sleep. self.client.join(timeout=1.5) self.client.start(timeout=0.1, daemon=True) self.assertTrue(self.client._thread.isDaemon()) def test_excepthook(self): """Test passing in an excepthook to client start method.""" exceptions = [] except_event = threading.Event() def excepthook(etype, value, traceback): """Keep track of exceptions.""" exceptions.append(etype) except_event.set() self.client.stop(timeout=0.1) # timeout needs to be longer than select sleep. self.client.join(timeout=1.5) self.client.start(timeout=0.1, excepthook=excepthook) # force exception by deleteing _running old_running = self.client._running try: del self.client._running except_event.wait(1.5) self.assertEqual(exceptions, [AttributeError]) finally: self.client._running = old_running
class TestCallbackClient(unittest.TestCase, TestUtilMixin): def setUp(self): self.addCleanup(self.stop_server_client) self.server = DeviceTestServer('', 0) self.server.start(timeout=0.1) host, port = self.server.bind_address self.client = katcp.CallbackClient(host, port) self.client.start(timeout=1) self.assertTrue(self.client.wait_protocol(timeout=1)) def stop_server_client(self): if hasattr(self, 'client') and self.client.running(): self.client.stop() self.client.join() if hasattr(self, 'server') and self.server.running(): self.server.stop() self.server.join() def test_callback_request(self): """Test callback request.""" watchdog_replies = [] watchdog_replied = threading.Event() def watchdog_reply(reply): self.assertEqual(reply.name, "watchdog") self.assertEqual(reply.arguments, ["ok"]) watchdog_replies.append(reply) watchdog_replied.set() self.assertTrue(self.client.wait_protocol(0.5)) self.client.callback_request( Message.request("watchdog"), reply_cb=watchdog_reply, ) watchdog_replied.wait(0.5) self.assertTrue(watchdog_replies) help_replies = [] help_informs = [] help_replied = threading.Event() def help_reply(reply): self.assertEqual(reply.name, "help") self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES]) self.assertEqual(len(help_informs), int(reply.arguments[1])) help_replies.append(reply) help_replied.set() def help_inform(inform): self.assertEqual(inform.name, "help") self.assertEqual(len(inform.arguments), 2) help_informs.append(inform) self.client.callback_request( Message.request("help"), reply_cb=help_reply, inform_cb=help_inform, ) help_replied.wait(1) self.assertTrue(help_replied.isSet()) help_replied.clear() help_replied.wait(0.05) # Check if (unwanted) late help replies arrive self.assertFalse(help_replied.isSet()) self.assertEqual(len(help_replies), 1) self.assertEqual(len(help_informs), NO_HELP_MESSAGES) def test_callback_request_mid(self): ## Test that the client does the right thing with message identifiers # Wait for the client to detect the server protocol. Server should # support message identifiers self.assertTrue(self.client.wait_protocol(0.2)) # Replace send_message so that we can check the message self.client.send_message = mock.Mock(wraps=self.client.send_message) # By default message identifiers should be enabled, and should start # counting at 1 cb = counting_callback(number_of_calls=3)(lambda *x: x) self.client.callback_request(Message.request('watchdog'), reply_cb=cb) self.client.callback_request(Message.request('watchdog'), reply_cb=cb) self.client.callback_request(Message.request('watchdog'), reply_cb=cb) cb.assert_wait() # Extract Message object .mid attributes from the mock calls to # send_message mids = [args[0].mid # args[0] should be the Message() object for args, kwargs in self.client.send_message.call_args_list] self.assertEqual(mids, ['1','2','3']) self.client.send_message.reset_mock() # Explicitly ask for no mid to be used cb = counting_callback(number_of_calls=1)(lambda *x: x) self.client.callback_request( Message.request('watchdog'), use_mid=False, reply_cb=cb) cb.assert_wait() mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) # Ask for a specific mid to be used self.client.send_message.reset_mock() cb = counting_callback(number_of_calls=1)(lambda *x: x) self.client.callback_request( Message.request('watchdog', mid=42), reply_cb=cb) cb.assert_wait() mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, '42') ## Check situation for a katcpv4 server self.client._server_supports_ids = False # Should fail if an mid is passed reply = [None] @counting_callback() def cb(msg): reply[0] = msg self.client.callback_request( Message.request('watchdog', mid=42), reply_cb=cb) cb.assert_wait() self.assertFalse(reply[0].reply_ok()) # Should fail if an mid is requested reply = [None] cb.reset() self.client.callback_request( Message.request('watchdog'), use_mid=True, reply_cb=cb) cb.assert_wait() self.assertFalse(reply[0].reply_ok()) # Should use no mid by default self.client.send_message.reset_mock() cb = counting_callback(number_of_calls=1)(lambda *x: x) self.client.callback_request(Message.request('watchdog'), reply_cb=cb) cb.assert_wait(timeout=1) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) def test_no_callback(self): """Test request without callback.""" help_messages = [] help_completed = threading.Event() def handle_help_message(client, msg): help_messages.append(msg) if msg.mtype == msg.REPLY: help_completed.set() self.client._inform_handlers["help"] = handle_help_message self.client._reply_handlers["help"] = handle_help_message # Set client._last_msg_id so we know that the ID is. Should be # _last_msg_id + 1 self.client._last_msg_id = 0 self.assertTrue(self.client.wait_protocol(0.2)) self.client.callback_request(Message.request("help")) help_completed.wait(1) self.assertTrue(help_completed.isSet()) self._assert_msgs_like(help_messages, [("#help[1] ", "")] * NO_HELP_MESSAGES + [("!help[1] ok %d" % NO_HELP_MESSAGES, "")]) def test_timeout(self): self._test_timeout() def test_timeout_nomid(self, use_mid=False): self._test_timeout(use_mid=False) def _test_timeout(self, use_mid=None): """Test requests that timeout.""" replies = [] replied = threading.Event() informs = [] timeout = 0.001 @counting_callback() def reply_cb(msg): replies.append(msg) replied.set() def inform_cb(msg): informs.append(msg) self.assertTrue(self.client.wait_protocol(0.2)) self.client.callback_request( Message.request("slow-command", "0.1"), use_mid=use_mid, reply_cb=reply_cb, inform_cb=inform_cb, timeout=timeout, ) reply_cb.assert_wait(1) self.client.request(Message.request('cancel-slow-command')) msg = replies[0] self.assertEqual(msg.name, "slow-command") self.assertFalse(msg.reply_ok()) self.assertRegexpMatches(msg.arguments[1], r"Request slow-command timed out after 0\..* seconds.") self.assertEqual(len(remove_version_connect(informs)), 0) self.assertEqual(len(replies), 1) del replies[:] del informs[:] reply_cb.reset() # test next request succeeds self.client.callback_request( Message.request("slow-command", "0.05"), reply_cb=reply_cb, inform_cb=inform_cb, ) reply_cb.assert_wait() self.assertEqual(len(replies), 1) self.assertEqual(len(informs), 0) self.assertEqual([msg.name for msg in replies + informs], ["slow-command"] * len(replies + informs)) self.assertEqual([msg.arguments for msg in replies], [["ok"]]) def test_timeout_nocb(self): """Test requests that timeout with no callbacks.""" # Included to test https://katfs.kat.ac.za/mantis/view.php?id=1722 # Situation can occur during a race between the timeout handler and the # receipt of a reply -- the reply can arrive after the timeout timer has # expired but before the request has been popped off the stack with # client._pop_async_request(). The normal request handler then pops off # the request first, resulting in the timeout handler getting a bunch of # None's. It should handle this gracefully. # Running the handler with a fake msg_id should have the same result as # running it after a real request has already been popped. The expected # result is that no assertions are raised. # NM 2014-09-26: This is probably no longer an issue with the tornado-based client # implementatin, but leaving the test for good measure f = Future() @gen.coroutine def cb(): self.client._handle_timeout('fake_msg_id', time.time()) self.client.ioloop.add_callback(lambda : gen.chain_future(cb(), f)) f.result(timeout=1) def test_user_data(self): """Test callbacks with user data.""" help_replies = [] help_informs = [] done = threading.Event() def help_reply(reply, x, y): self.assertEqual(reply.name, "help") self.assertEqual(x, 5) self.assertEqual(y, "foo") help_replies.append(reply) done.set() def help_inform(inform, x, y): self.assertEqual(inform.name, "help") self.assertEqual(x, 5) self.assertEqual(y, "foo") help_informs.append(inform) self.client.callback_request( Message.request("help"), reply_cb=help_reply, inform_cb=help_inform, user_data=(5, "foo")) done.wait(1) # Wait a bit longer to see if spurious replies arrive time.sleep(0.01) self.assertEqual(len(help_replies), 1) self.assertEqual(len(remove_version_connect(help_informs)), NO_HELP_MESSAGES) def test_fifty_thread_mayhem(self): """Test using callbacks from fifty threads simultaneously.""" num_threads = 50 # map from thread_id -> (replies, informs) results = {} # list of thread objects threads = [] def reply_cb(reply, thread_id): results[thread_id][0].append(reply) results[thread_id][2].set() def inform_cb(inform, thread_id): results[thread_id][1].append(inform) def worker(thread_id, request): self.client.callback_request( request.copy(), reply_cb=reply_cb, inform_cb=inform_cb, user_data=(thread_id,), ) request = Message.request("help") for thread_id in range(num_threads): results[thread_id] = ([], [], threading.Event()) for thread_id in range(num_threads): thread = threading.Thread(target=worker, args=(thread_id, request)) threads.append(thread) for thread in threads: thread.start() for thread in threads: thread.join() for thread_id in range(num_threads): replies, informs, done = results[thread_id] done.wait(5.0) self.assertTrue(done.isSet()) self.assertEqual(len(replies), 1) self.assertEqual(replies[0].arguments[0], "ok") informs = remove_version_connect(informs) if len(informs) != NO_HELP_MESSAGES: print thread_id, len(informs) print [x.arguments[0] for x in informs] self.assertEqual(len(informs), NO_HELP_MESSAGES) def test_blocking_request(self): """Test the callback client's blocking request.""" reply, informs = self.client.blocking_request( Message.request("help"), ) self.assertEqual(reply.name, "help") self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES]) self.assertEqual(len(remove_version_connect(informs)), NO_HELP_MESSAGES) reply, informs = self.client.blocking_request( Message.request("slow-command", "0.5"), timeout=0.001) self.assertEqual(reply.name, "slow-command") self.assertEqual(reply.arguments[0], "fail") self.assertRegexpMatches( reply.arguments[1], r"Request slow-command timed out after 0\..* seconds.") def test_blocking_request_mid(self): ## Test that the blocking client does the right thing with message ## identifiers # Wait for the client to detect the server protocol. Server should # support message identifiers self.assertTrue(self.client.wait_protocol(1)) # Replace send_message so that we can check the message self.client.send_message = mock.Mock() # By default message identifiers should be enabled, and should start # counting at 1 self.client.blocking_request(Message.request('watchdog'), timeout=0) self.client.blocking_request(Message.request('watchdog'), timeout=0) self.client.blocking_request(Message.request('watchdog'), timeout=0) # Extract Message object .mid attributes from the mock calls to # send_message mids = [args[0].mid # arg[0] should be the Message() object for args, kwargs in self.client.send_message.call_args_list] self.assertEqual(mids, ['1','2','3']) self.client.send_message.reset_mock() # Explicitly ask for no mid to be used self.client.blocking_request(Message.request('watchdog'), use_mid=False, timeout=0) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) # Ask for a specific mid to be used self.client.send_message.reset_mock() self.client.blocking_request(Message.request( 'watchdog', mid=42), timeout=0) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, '42') ## Check situation for a katcpv4 server self.client._server_supports_ids = False # Should fail if an mid is passed reply, inform = self.client.blocking_request(Message.request( 'watchdog', mid=42), timeout=0) self.assertFalse(reply.reply_ok()) # Should fail if an mid is requested reply, inform = self.client.blocking_request( Message.request('watchdog'), use_mid=True, timeout=0) self.assertFalse(reply.reply_ok()) # Should use no mid by default self.client.send_message.reset_mock() self.client.blocking_request(Message.request( 'watchdog'), timeout=0) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) def test_request_fail_on_raise(self): """Test that the callback is called even if send_message raises KatcpClientError.""" def raise_error(msg, timeout=None): raise katcp.KatcpClientError("Error %s" % msg.name) self.client.send_message = raise_error replies = [] @counting_callback() def reply_cb(msg): replies.append(msg) self.client.callback_request(Message.request("foo"), reply_cb=reply_cb, ) reply_cb.assert_wait() self.assertEqual(len(replies), 1) self.assertEqual(replies[0].name, "foo") self.assertEqual(replies[0].arguments, ["fail", "Error foo"]) def test_stop_cleanup(self): self.client.wait_protocol(timeout=1) mid = 56 future_reply = Future() self.client.ioloop.add_callback( lambda : gen.chain_future(self.client.future_request(Message.request( 'slow-command', 1, mid=mid)), future_reply)) # Force a disconnect self.client.stop() reply, informs = future_reply.result(timeout=1) self.assertEqual(reply, Message.reply( 'slow-command', 'fail', 'Client stopped before reply was received', mid=mid))
class TestCallbackClient(unittest.TestCase, TestUtilMixin): def setUp(self): self.addCleanup(self.stop_server_client) self.server = DeviceTestServer('', 0) self.server.start(timeout=0.1) host, port = self.server._sock.getsockname() self.client = katcp.CallbackClient(host, port) self.client.start(timeout=0.1) self.assertTrue(self.client.wait_protocol(timeout=1)) def stop_server_client(self): if hasattr(self, 'client') and self.client.running(): self.client.stop() self.client.join() if hasattr(self, 'server') and self.server.running(): self.server.stop() self.server.join() def test_callback_request(self): """Test callback request.""" watchdog_replies = [] watchdog_replied = threading.Event() def watchdog_reply(reply): self.assertEqual(reply.name, "watchdog") self.assertEqual(reply.arguments, ["ok"]) watchdog_replies.append(reply) watchdog_replied.set() self.assertTrue(self.client.wait_protocol(0.2)) self.client.callback_request( katcp.Message.request("watchdog"), reply_cb=watchdog_reply, ) watchdog_replied.wait(0.2) self.assertTrue(watchdog_replies) help_replies = [] help_informs = [] help_replied = threading.Event() def help_reply(reply): self.assertEqual(reply.name, "help") self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES]) self.assertEqual(len(help_informs), int(reply.arguments[1])) help_replies.append(reply) help_replied.set() def help_inform(inform): self.assertEqual(inform.name, "help") self.assertEqual(len(inform.arguments), 2) help_informs.append(inform) self.client.callback_request( katcp.Message.request("help"), reply_cb=help_reply, inform_cb=help_inform, ) help_replied.wait(1) self.assertTrue(help_replied.isSet()) help_replied.clear() help_replied.wait(0.05) # Check if (unwanted) late help replies arrive self.assertFalse(help_replied.isSet()) self.assertEqual(len(help_replies), 1) self.assertEqual(len(help_informs), NO_HELP_MESSAGES) def test_callback_request_mid(self): ## Test that the client does the right thing with message identifiers # Wait for the client to detect the server protocol. Server should # support message identifiers self.assertTrue(self.client.wait_protocol(0.2)) # Replace send_message so that we can check the message self.client.send_message = mock.Mock() # By default message identifiers should be enabled, and should start # counting at 1 self.client.callback_request(katcp.Message.request('watchdog')) self.client.callback_request(katcp.Message.request('watchdog')) self.client.callback_request(katcp.Message.request('watchdog')) # Extract katcp.Message object .mid attributes from the mock calls to # send_message mids = [ args[0].mid # arg[0] should be the Message() object for args, kwargs in self.client.send_message.call_args_list ] self.assertEqual(mids, ['1', '2', '3']) self.client.send_message.reset_mock() # Explicitly ask for no mid to be used self.client.callback_request(katcp.Message.request('watchdog'), use_mid=False) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) # Ask for a specific mid to be used self.client.send_message.reset_mock() self.client.callback_request(katcp.Message.request('watchdog', mid=42)) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, '42') ## Check situation for a katcpv4 server self.client._server_supports_ids = False # Should fail if an mid is passed with self.assertRaises(katcp.core.KatcpVersionError): self.client.callback_request( katcp.Message.request('watchdog', mid=42)) # Should fail if an mid is requested with self.assertRaises(katcp.core.KatcpVersionError): self.client.callback_request(katcp.Message.request('watchdog'), use_mid=True) # Should use no mid by default self.client.send_message.reset_mock() self.client.callback_request(katcp.Message.request('watchdog')) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) def test_no_callback(self): """Test request without callback.""" help_messages = [] help_completed = threading.Event() def handle_help_message(client, msg): help_messages.append(msg) if msg.mtype == msg.REPLY: help_completed.set() self.client._inform_handlers["help"] = handle_help_message self.client._reply_handlers["help"] = handle_help_message # Set client._last_msg_id so we know that the ID is. Should be # _last_msg_id + 1 self.client._last_msg_id = 0 self.assertTrue(self.client.wait_protocol(0.2)) self.client.callback_request(katcp.Message.request("help")) help_completed.wait(1) self.assertTrue(help_completed.isSet()) self._assert_msgs_like(help_messages, [("#help[1] ", "")] * NO_HELP_MESSAGES + [("!help[1] ok %d" % NO_HELP_MESSAGES, "")]) def test_no_timeout(self): self.client._request_timeout = None replies = [] informs = [] replied = threading.Event() def reply_handler(reply): replies.append(reply) replied.set() def inform_handler(reply): informs.append(reply) with mock.patch('katcp.client.threading.Timer') as MockTimer: self.client.callback_request(katcp.Message.request("help"), reply_cb=reply_handler, inform_cb=inform_handler) replied.wait(1) # With no timeout no Timer object should have been instantiated self.assertEqual(MockTimer.call_count, 0) self.assertEqual(len(replies), 1) self.assertEqual(len(remove_version_connect(informs)), NO_HELP_MESSAGES) def test_timeout(self): self._test_timeout() def test_timeout_nomid(self, use_mid=False): self._test_timeout(use_mid=False) def _test_timeout(self, use_mid=None): """Test requests that timeout.""" replies = [] replied = threading.Event() informs = [] timeout = 0.001 @counting_callback() def reply_cb(msg): replies.append(msg) replied.set() def inform_cb(msg): informs.append(msg) self.assertTrue(self.client.wait_protocol(0.2)) self.client.callback_request( katcp.Message.request("slow-command", "0.1"), use_mid=use_mid, reply_cb=reply_cb, inform_cb=inform_cb, timeout=timeout, ) reply_cb.assert_wait(1) self.assertEqual(len(replies), 1) self.assertEqual([msg.name for msg in replies], ["slow-command"]) self.assertEqual( [msg.arguments for msg in replies], [["fail", "Timed out after %f seconds" % timeout]]) self.assertEqual(len(remove_version_connect(informs)), 0) del replies[:] del informs[:] reply_cb.reset() # test next request succeeds self.client.callback_request( katcp.Message.request("slow-command", "0.05"), reply_cb=reply_cb, inform_cb=inform_cb, ) reply_cb.assert_wait(1) self.assertEqual(len(replies), 1) self.assertEqual(len(informs), 0) self.assertEqual([msg.name for msg in replies + informs], ["slow-command"] * len(replies + informs)) self.assertEqual([msg.arguments for msg in replies], [["ok"]]) def test_timeout_nocb(self): """Test requests that timeout with no callbacks.""" # Included to test https://katfs.kat.ac.za/mantis/view.php?id=1722 # Situation can occur during a race between the timeout handler and the # receipt of a reply -- the reply can arrive after the timeout timer has # expired but before the request has been popped off the stack with # client._pop_async_request(). The normal request handler then pops off # the request first, resulting in the timeout handler getting a bunch of # None's. It should handle this gracefully. # Running the handler with a fake msg_id should have the same result as # running it after a real request has already been popped. The expected # result is that no assertions are raised. self.client._handle_timeout('fake_msg_id') def test_user_data(self): """Test callbacks with user data.""" help_replies = [] help_informs = [] done = threading.Event() def help_reply(reply, x, y): self.assertEqual(reply.name, "help") self.assertEqual(x, 5) self.assertEqual(y, "foo") help_replies.append(reply) done.set() def help_inform(inform, x, y): self.assertEqual(inform.name, "help") self.assertEqual(x, 5) self.assertEqual(y, "foo") help_informs.append(inform) self.client.callback_request(katcp.Message.request("help"), reply_cb=help_reply, inform_cb=help_inform, user_data=(5, "foo")) done.wait(1) # Wait a bit longer to see if spurious replies arrive time.sleep(0.01) self.assertEqual(len(help_replies), 1) self.assertEqual(len(remove_version_connect(help_informs)), NO_HELP_MESSAGES) def test_fifty_thread_mayhem(self): """Test using callbacks from fifty threads simultaneously.""" num_threads = 50 # map from thread_id -> (replies, informs) results = {} # list of thread objects threads = [] def reply_cb(reply, thread_id): results[thread_id][0].append(reply) results[thread_id][2].set() def inform_cb(inform, thread_id): results[thread_id][1].append(inform) def worker(thread_id, request): self.client.callback_request( request.copy(), reply_cb=reply_cb, inform_cb=inform_cb, user_data=(thread_id, ), ) request = katcp.Message.request("help") for thread_id in range(num_threads): results[thread_id] = ([], [], threading.Event()) for thread_id in range(num_threads): thread = threading.Thread(target=worker, args=(thread_id, request)) threads.append(thread) for thread in threads: thread.start() for thread in threads: thread.join() for thread_id in range(num_threads): replies, informs, done = results[thread_id] done.wait(5.0) self.assertTrue(done.isSet()) self.assertEqual(len(replies), 1) self.assertEqual(replies[0].arguments[0], "ok") informs = remove_version_connect(informs) if len(informs) != NO_HELP_MESSAGES: print thread_id, len(informs) print[x.arguments[0] for x in informs] self.assertEqual(len(informs), NO_HELP_MESSAGES) def test_blocking_request(self): """Test the callback client's blocking request.""" reply, informs = self.client.blocking_request( katcp.Message.request("help"), ) self.assertEqual(reply.name, "help") self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES]) self.assertEqual(len(remove_version_connect(informs)), NO_HELP_MESSAGES) reply, informs = self.client.blocking_request(katcp.Message.request( "slow-command", "0.5"), timeout=0.001) self.assertEqual(reply.name, "slow-command") self.assertEqual(reply.arguments[0], "fail") self.assertTrue(reply.arguments[1].startswith("Timed out after")) def test_blocking_request_mid(self): ## Test that the blocking client does the right thing with message ## identifiers # Wait for the client to detect the server protocol. Server should # support message identifiers self.assertTrue(self.client.wait_protocol(0.2)) # Replace send_message so that we can check the message self.client.send_message = mock.Mock() # By default message identifiers should be enabled, and should start # counting at 1 self.client.blocking_request(katcp.Message.request('watchdog'), timeout=0) self.client.blocking_request(katcp.Message.request('watchdog'), timeout=0) self.client.blocking_request(katcp.Message.request('watchdog'), timeout=0) # Extract katcp.Message object .mid attributes from the mock calls to # send_message mids = [ args[0].mid # arg[0] should be the Message() object for args, kwargs in self.client.send_message.call_args_list ] self.assertEqual(mids, ['1', '2', '3']) self.client.send_message.reset_mock() # Explicitly ask for no mid to be used self.client.blocking_request(katcp.Message.request('watchdog'), use_mid=False, timeout=0) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) # Ask for a specific mid to be used self.client.send_message.reset_mock() self.client.blocking_request(katcp.Message.request('watchdog', mid=42), timeout=0) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, '42') ## Check situation for a katcpv4 server self.client._server_supports_ids = False # Should fail if an mid is passed with self.assertRaises(katcp.core.KatcpVersionError): self.client.blocking_request(katcp.Message.request('watchdog', mid=42), timeout=0) # Should fail if an mid is requested with self.assertRaises(katcp.core.KatcpVersionError): self.client.blocking_request(katcp.Message.request('watchdog'), use_mid=True, timeout=0) # Should use no mid by default self.client.send_message.reset_mock() self.client.blocking_request(katcp.Message.request('watchdog'), timeout=0) mid = self.client.send_message.call_args[0][0].mid self.assertEqual(mid, None) def test_request_fail_on_raise(self): """Test that the callback is called even if send_message raises KatcpClientError.""" def raise_error(msg, timeout=None): raise katcp.KatcpClientError("Error %s" % msg.name) self.client.send_message = raise_error replies = [] def reply_cb(msg): replies.append(msg) self.client.callback_request( katcp.Message.request("foo"), reply_cb=reply_cb, ) self.assertEqual(len(replies), 1) self.assertEqual(replies[0].name, "foo") self.assertEqual(replies[0].arguments, ["fail", "Error foo"]) def test_stop_join(self): # Set up a slow command to ensure that there is something in # the async queue with mock.patch('katcp.client.threading.Timer') as MockTimer: instance = MockTimer.return_value self.client.callback_request(katcp.Message.request( "slow-command", "10000"), timeout=10000.1) # Exactly one instance of MockTimer should have been instantiated self.assertEqual(MockTimer.call_count, 1) self.client.stop(timeout=0.5) # Check that the timer's cancel() method has been called instance.cancel.assert_called_once_with() self.client.join(timeout=0.45) instance.join.assert_called_once_with(timeout=0.45) # It's OK not to have this in teardown since the client will # itself cancel the slow_command when it is stop()ed self.client.blocking_request( katcp.Message.request("cancel-slow-command"))
class TestDeviceClientIntegrated(unittest.TestCase, TestUtilMixin): def setUp(self): self.server = DeviceTestServer('', 0) self.server.start(timeout=0.1) host, port = self.server._sock.getsockname() self.client = katcp.DeviceClient(host, port) self.client.start(timeout=0.1) def tearDown(self): if self.client.running(): self.client.stop() self.client.join() if self.server.running(): self.server.stop() self.server.join() def test_request(self): """Test request method.""" self.assertTrue(self.client.wait_protocol(1)) self.client.send_request(katcp.Message.request("watchdog")) self.client.send_request(katcp.Message.request("watchdog", mid=55)) self.client._server_supports_ids = False with self.assertRaises(katcp.core.KatcpVersionError): self.client.send_request(katcp.Message.request("watchdog", mid=56)) time.sleep(0.1) msgs = self.server.messages() self._assert_msgs_equal(msgs, [ r"?watchdog", r"?watchdog[55]", ]) def test_send_message(self): """Test send_message method.""" self.client.send_message(katcp.Message.inform("random-inform")) time.sleep(0.1) msgs = self.server.messages() self._assert_msgs_equal(msgs, [ r"#random-inform", ]) def test_stop_and_restart(self): """Test stopping and then restarting a client.""" self.client.stop(timeout=0.1) # timeout needs to be longer than select sleep. self.client.join(timeout=1.5) self.assertEqual(self.client._thread, None) self.assertFalse(self.client._running.isSet()) self.client.start(timeout=0.1) def test_is_connected(self): """Test is_connected method.""" self.assertTrue(self.client.is_connected()) self.server.stop(timeout=0.1) # timeout needs to be longer than select sleep. self.server.join(timeout=1.5) self.assertFalse(self.client.is_connected()) def test_wait_connected(self): """Test wait_connected method.""" start = time.time() self.assertTrue(self.client.wait_connected(1.0)) self.assertTrue(time.time() - start < 1.0) self.server.stop(timeout=0.1) # timeout needs to be longer than select sleep. self.server.join(timeout=1.5) start = time.time() self.assertFalse(self.client.wait_connected(0.2)) self.assertTrue(0.15 < time.time() - start < 0.25) def test_bad_socket(self): """Test what happens when select is called on a dead socket.""" # wait for client to connect time.sleep(0.1) # close socket while the client isn't looking # then wait for the client to notice sock = self.client._sock sockname = sock.getpeername() sock.close() time.sleep(1.25) # check that client reconnected self.assertTrue(sock is not self.client._sock, "Expected %r to not be %r" % (sock, self.client._sock)) self.assertEqual(sockname, self.client._sock.getpeername()) def test_daemon_value(self): """Test passing in a daemon value to client start method.""" self.client.stop(timeout=0.1) # timeout needs to be longer than select sleep. self.client.join(timeout=1.5) self.client.start(timeout=0.1, daemon=True) self.assertTrue(self.client._thread.isDaemon()) def test_excepthook(self): """Test passing in an excepthook to client start method.""" exceptions = [] except_event = threading.Event() def excepthook(etype, value, traceback): """Keep track of exceptions.""" exceptions.append(etype) except_event.set() self.client.stop(timeout=0.1) # timeout needs to be longer than select sleep. self.client.join(timeout=1.5) self.client.start(timeout=0.1, excepthook=excepthook) # force exception by deleteing _running old_running = self.client._running try: del self.client._running except_event.wait(1.5) self.assertEqual(exceptions, [AttributeError]) finally: self.client._running = old_running
class TestDeviceServerClientIntegrated(unittest.TestCase, TestUtilMixin): BLACKLIST = ("version-connect", "version", "build-state") def setUp(self): super(TestDeviceServerClientIntegrated, self).setUp() self._setup_server() host, port = self.server.bind_address self.server_addr = (host, port) self.client = BlockingTestClient(self, host, port) start_thread_with_cleanup(self, self.client, start_timeout=1) self.assertTrue(self.client.wait_protocol(timeout=1)) def _setup_server(self): self.server = DeviceTestServer('', 0) start_thread_with_cleanup(self, self.server, start_timeout=1) def test_log(self): get_msgs = self.client.message_recorder(blacklist=self.BLACKLIST, replies=True) def tst(): with mock.patch('katcp.server.time.time') as m_time: m_time.return_value = 1234 self.server.log.error('An error') self.server.ioloop.add_callback(tst) get_msgs.wait_number(1) self._assert_msgs_equal(get_msgs(), [r"#log error 1234.000000 root An\_error"]) def test_simple_connect(self): """Test a simple server setup and teardown with client connect.""" get_msgs = self.client.message_recorder(blacklist=self.BLACKLIST, replies=True) # basic send self.client.request(katcp.Message.request("foo"), use_mid=False) # pipe-lined send self.client.raw_send("?bar-boom\r\n?baz\r") # broken up sends self.client.raw_send("?boo") self.client.raw_send("m arg1 arg2") self.client.raw_send("\n") self._assert_msgs_equal(get_msgs(min_number=4), [ r"!foo invalid Unknown\_request.", r"!bar-boom invalid Unknown\_request.", r"!baz invalid Unknown\_request.", r"!boom invalid Unknown\_request.", ]) def test_bad_requests(self): """Test request failure paths in device server.""" get_msgs = self.client.message_recorder(blacklist=self.BLACKLIST, replies=True) self.client.raw_send("bad msg\n") # wait for reply self.client.blocking_request(katcp.Message.request("watchdog"), use_mid=False) self._assert_msgs_like(get_msgs(), [ (r"#log error", "KatcpSyntaxError:" "\_Bad\_type\_character\_'b'.\\n"), (r"!watchdog ok", ""), ]) def test_slow_client(self): # Test that server does not choke sending messages to slow clients # Set max server write buffer size smaller so that it gives up earlier to make the # test faster self.server._server.MAX_WRITE_BUFFER_SIZE = 16384 self.client.wait_protocol(1) slow_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) slow_sock.connect(self.server_addr) slow_sock.settimeout(0.01) # Send a bunch of request to the server, but don't read anything from # the server try: slow_sock.sendall('?help\n' * 1000000) except (socket.error, socket.timeout): pass t0 = time.time() # Request should not have taken a very long time. self.client.assert_request_succeeds('help', informs_count=NO_HELP_MESSAGES) self.assertTrue(time.time() - t0 < 1) def test_server_ignores_informs_and_replies(self): """Test server ignores informs and replies.""" get_msgs = self.client.message_recorder(blacklist=self.BLACKLIST, replies=True) self.client.raw_send("#some inform\n") self.client.raw_send("!some reply\n") time.sleep(0.1) self.assertFalse(get_msgs()) def test_standard_requests(self): """Test standard request and replies.""" get_msgs = self.client.message_recorder(blacklist=self.BLACKLIST, replies=True) nomid_req = partial(self.client.blocking_request, use_mid=False) nomid_req(katcp.Message.request("watchdog")) nomid_req(katcp.Message.request("restart")) nomid_req(katcp.Message.request("log-level")) nomid_req(katcp.Message.request("log-level", "trace")) nomid_req(katcp.Message.request("log-level", "unknown")) nomid_req(katcp.Message.request("help")) nomid_req(katcp.Message.request("help", "watchdog")) nomid_req(katcp.Message.request("help", "unknown-request")) nomid_req(katcp.Message.request("client-list")) nomid_req(katcp.Message.request("version-list")) nomid_req(katcp.Message.request("sensor-list")) nomid_req(katcp.Message.request("sensor-list", "an.int")) nomid_req(katcp.Message.request("sensor-list", "an.unknown")) nomid_req(katcp.Message.request("sensor-value")) nomid_req(katcp.Message.request("sensor-value", "an.int")) nomid_req(katcp.Message.request("sensor-value", "an.unknown")) nomid_req(katcp.Message.request("sensor-sampling", "an.int")) nomid_req( katcp.Message.request("sensor-sampling", "an.int", "differential", "2")) nomid_req( katcp.Message.request("sensor-sampling", "an.int", "event-rate", "2", "3")) nomid_req(katcp.Message.request("sensor-sampling")) nomid_req( katcp.Message.request("sensor-sampling", "an.unknown", "auto")) nomid_req(katcp.Message.request("sensor-sampling", "an.int", "unknown")) def tst(): self.server.log.trace("trace-msg") self.server.log.debug("debug-msg") self.server.log.info("info-msg") self.server.log.warn("warn-msg") self.server.log.error("error-msg") self.server.log.fatal("fatal-msg") self.server.ioloop.add_callback(tst) self.assertEqual(self.server.restart_queue.get_nowait(), self.server) expected_msgs = [ (r"!watchdog ok", ""), (r"!restart ok", ""), (r"!log-level ok warn", ""), (r"!log-level ok trace", ""), (r"!log-level fail Unknown\_logging\_level\_name\_'unknown'", ""), (r"#help cancel-slow-command Cancel\_slow\_command\_request,\_" "resulting\_in\_it\_replying\_immediately", ""), (r"#help client-list", ""), (r"#help halt", ""), (r"#help help", ""), (r"#help log-level", ""), (r"#help new-command", ""), (r"#help raise-exception", ""), (r"#help raise-fail", ""), (r"#help restart", ""), (r"#help sensor-list", ""), (r"#help sensor-sampling", ""), (r"#help sensor-sampling-clear", ""), (r"#help sensor-value", ""), (r"#help slow-command", ""), (r"#help version-list", ""), (r"#help watchdog", ""), (r"!help ok %d" % NO_HELP_MESSAGES, ""), (r"#help watchdog", ""), (r"!help ok 1", ""), (r"!help fail", ""), (r"#client-list", ""), (r"!client-list ok 1", ""), (r"#version-list katcp-protocol", ""), (r"#version-list katcp-library", ""), (r"#version-list katcp-device", ""), (r"!version-list ok 3", ""), (r"#sensor-list an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list ok 1", ""), (r"#sensor-list an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list ok 1", ""), (r"!sensor-list fail", ""), (r"#sensor-value 12345.000000 1 an.int nominal 3", ""), (r"!sensor-value ok 1", ""), (r"#sensor-value 12345.000000 1 an.int nominal 3", ""), (r"!sensor-value ok 1", ""), (r"!sensor-value fail", ""), (r"!sensor-sampling ok an.int none", ""), (r"#sensor-status 12345.000000 1 an.int nominal 3", ""), (r"!sensor-sampling ok an.int differential 2", ""), (r"#sensor-status 12345.000000 1 an.int nominal 3", ""), (r"!sensor-sampling ok an.int event-rate 2 3", ""), (r"!sensor-sampling fail No\_sensor\_name\_given.", ""), (r"!sensor-sampling fail Unknown\_sensor\_name:\_an.unknown.", ""), (r"!sensor-sampling fail Unknown\_strategy\_name:\_unknown.", ""), (r"#log trace", r"root trace-msg"), (r"#log debug", r"root debug-msg"), (r"#log info", r"root info-msg"), (r"#log warn", r"root warn-msg"), (r"#log error", r"root error-msg"), (r"#log fatal", r"root fatal-msg"), ] self._assert_msgs_like(get_msgs(min_number=len(expected_msgs)), expected_msgs) def test_standard_requests_with_ids(self): """Test standard request and replies with message ids.""" get_msgs = self.client.message_recorder(blacklist=self.BLACKLIST, replies=True) current_id = [0] def mid(): current_id[0] += 1 return str(current_id[0]) def mid_req(*args): return katcp.Message.request(*args, mid=mid()) self.client.request(mid_req("watchdog")) self.client._next_id = mid # mock our mid generator for testing self.client.blocking_request(mid_req("restart")) self.client.request(mid_req("log-level")) self.client.request(mid_req("log-level", "trace")) self.client.request(mid_req("log-level", "unknown")) self.client.request(mid_req("help")) self.client.request(mid_req("help", "watchdog")) self.client.request(mid_req("help", "unknown-request")) self.client.request(mid_req("client-list")) self.client.request(mid_req("version-list")) self.client.request(mid_req("sensor-list")) self.client.request(mid_req("sensor-list", "an.int")) self.client.request(mid_req("sensor-list", "an.unknown")) self.client.request(mid_req("sensor-value")) self.client.request(mid_req("sensor-value", "an.int")) self.client.request(mid_req("sensor-value", "an.unknown")) self.client.blocking_request(mid_req("sensor-sampling", "an.int")) self.client.blocking_request( mid_req("sensor-sampling", "an.int", "differential", "2")) self.client.blocking_request( mid_req("sensor-sampling", "an.int", "event-rate", "2", "3")), self.client.blocking_request(mid_req("sensor-sampling")) self.client.blocking_request( mid_req("sensor-sampling", "an.unknown", "auto")) self.client.blocking_request( mid_req("sensor-sampling", "an.int", "unknown")) def tst(): self.server.log.trace("trace-msg") self.server.log.debug("debug-msg") self.server.log.info("info-msg") self.server.log.warn("warn-msg") self.server.log.error("error-msg") self.server.log.fatal("fatal-msg") self.server.ioloop.add_callback(tst) expected_msgs = [ (r"!watchdog[1] ok", ""), (r"!restart[2] ok", ""), (r"!log-level[3] ok warn", ""), (r"!log-level[4] ok trace", ""), (r"!log-level[5] fail Unknown\_logging\_level\_name\_'unknown'", ""), (r"#help[6] cancel-slow-command Cancel\_slow\_command\_request,\_" "resulting\_in\_it\_replying\_immediately", ""), (r"#help[6] client-list", ""), (r"#help[6] halt", ""), (r"#help[6] help", ""), (r"#help[6] log-level", ""), (r"#help[6] new-command", ""), (r"#help[6] raise-exception", ""), (r"#help[6] raise-fail", ""), (r"#help[6] restart", ""), (r"#help[6] sensor-list", ""), (r"#help[6] sensor-sampling", ""), (r"#help[6] sensor-sampling-clear", ""), (r"#help[6] sensor-value", ""), (r"#help[6] slow-command", ""), (r"#help[6] version-list", ""), (r"#help[6] watchdog", ""), (r"!help[6] ok %d" % NO_HELP_MESSAGES, ""), (r"#help[7] watchdog", ""), (r"!help[7] ok 1", ""), (r"!help[8] fail", ""), (r"#client-list[9]", ""), (r"!client-list[9] ok 1", ""), (r"#version-list[10] katcp-protocol", ""), (r"#version-list[10] katcp-library", ""), (r"#version-list[10] katcp-device", ""), (r"!version-list[10] ok 3", ""), (r"#sensor-list[11] an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list[11] ok 1", ""), (r"#sensor-list[12] an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list[12] ok 1", ""), (r"!sensor-list[13] fail", ""), (r"#sensor-value[14] 12345.000000 1 an.int nominal 3", ""), (r"!sensor-value[14] ok 1", ""), (r"#sensor-value[15] 12345.000000 1 an.int nominal 3", ""), (r"!sensor-value[15] ok 1", ""), (r"!sensor-value[16] fail", ""), (r"!sensor-sampling[17] ok an.int none", ""), (r"#sensor-status 12345.000000 1 an.int nominal 3", ""), (r"!sensor-sampling[18] ok an.int differential 2", ""), (r"#sensor-status 12345.000000 1 an.int nominal 3", ""), (r"!sensor-sampling[19] ok an.int event-rate 2 3", ""), (r"!sensor-sampling[20] fail No\_sensor\_name\_given.", ""), (r"!sensor-sampling[21] fail Unknown\_sensor\_name:\_an.unknown.", ""), (r"!sensor-sampling[22] fail Unknown\_strategy\_name:\_unknown.", ""), (r"#log trace", r"root trace-msg"), (r"#log debug", r"root debug-msg"), (r"#log info", r"root info-msg"), (r"#log warn", r"root warn-msg"), (r"#log error", r"root error-msg"), (r"#log fatal", r"root fatal-msg"), ] self.assertEqual(self.server.restart_queue.get_nowait(), self.server) self._assert_msgs_like(get_msgs(min_number=len(expected_msgs)), expected_msgs) def test_sensor_list_regex(self): reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-list", "/a.*/"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"#sensor-list an.int An\_Integer. count integer -5 5", r"!sensor-list ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-list", "//"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"#sensor-list an.int An\_Integer. count integer -5 5", r"!sensor-list ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-list", "/^int/"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"!sensor-list ok 0", ]) def test_sensor_value_regex(self): reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-value", "/a.*/"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"#sensor-value 12345.000000 1 an.int nominal 3", r"!sensor-value ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-value", "//"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"#sensor-value 12345.000000 1 an.int nominal 3", r"!sensor-value ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-value", "/^int/"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"!sensor-value ok 0", ]) def test_client_list(self): reply, informs = self.client.blocking_request( katcp.Message.request('client-list'), use_mid=False) self.assertEqual(str(reply), '!client-list ok 1') self.assertEqual(len(informs), 1) inform = str(informs[0]) self.assertTrue(inform.startswith('#client-list 127.0.0.1:')) _, addr = inform.split() host, port = addr.split(':') port = int(port) self.assertEqual((host, port), self.client.sockname) def test_halt_request(self): """Test halt request.""" get_msgs = self.client.message_recorder(blacklist=self.BLACKLIST, replies=True) self.client.request(katcp.Message.request("halt")) # hack to hide re-connect exception self.client.connect = lambda: None self.server.join() self._assert_msgs_equal(get_msgs(min_number=2), [ r"!halt[1] ok", r"#disconnect Device\_server\_shutting\_down.", ]) def test_bad_handlers(self): """Test that bad request and inform handlers are picked up.""" try: class BadServer(katcp.DeviceServer): def request_baz(self, req, msg): pass except AssertionError: pass else: self.fail("Server metaclass accepted missing request_ docstring.") try: class BadServer(katcp.DeviceServer): def inform_baz(self, req, msg): pass except AssertionError: pass else: self.fail("Server metaclass accepted missing inform_ docstring.") class SortOfOkayServer(katcp.DeviceServer): request_bar = 1 inform_baz = 2 assert ("bar" not in SortOfOkayServer._request_handlers) assert ("baz" not in SortOfOkayServer._inform_handlers) def test_handler_exceptions(self): """Test handling of failure replies and other exceptions.""" get_msgs = self.client.message_recorder(blacklist=self.BLACKLIST, replies=True) self.assertTrue(self.client.wait_protocol(timeout=1)) self.client.request(katcp.Message.request("raise-exception")) self.client.request(katcp.Message.request("raise-fail")) time.sleep(0.1) self._assert_msgs_like(get_msgs(), [ (r"!raise-exception[1] fail Traceback", ""), (r"!raise-fail[2] fail There\_was\_a\_problem\_with\_your\_request.", ""), ]) def test_stop_and_restart(self): """Test stopping and restarting the device server.""" # So we can wait for the client to disconnect self.client.notify_connected = WaitingMock() self.server.stop(timeout=0.1) self.server.join(timeout=1.0) self.assertFalse(self.server.running()) # Wait for client to be disconnected self.client.notify_connected.assert_wait_call_count(1) self.assertFalse(self.client.is_connected()) self.server.start(timeout=1.0) def test_bad_client_socket(self): """Test what happens when select is called on a dead client socket.""" # close client stream while the server isn't looking then wait for the server to # notice stream, client_conn = self.server._server._connections.items()[0] # Wait for the client to disconnect self.client.notify_connected = WaitingMock() self.server.ioloop.add_callback(stream.close) # Wait for the client to be disconnected, and to connect again self.client.notify_connected.assert_wait_call_count(2) self.server.sync_with_ioloop() # check that client stream was removed from the KATCPServer self.assertTrue( stream not in self.server._server._connections, "Expected %r to not be in %r" % (stream, self.server._server._connections)) # And check that the ClientConnection object was removed from the DeviceServer self.assertTrue( client_conn not in self.server._client_conns, "Expected %r to not be in %r" % (client_conn, self.server._client_conns)) def test_sampling(self): """Test sensor sampling.""" get_msgs = self.client.message_recorder(blacklist=self.BLACKLIST, replies=True) self.client.wait_protocol(timeout=1) self.client.request( katcp.Message.request("sensor-sampling", "an.int", "period", 1 / 32.)) # Wait for the request reply and for the sensor update messages to # arrive. We expect update one the moment the sensor-sampling request is # made, then four more over 4/32. of a second, resutling in 6 # messages. Wait 0.75 of a period longer just to be sure we get everything. self.assertTrue(get_msgs.wait_number(6, timeout=4.75 / 32.)) self.client.assert_request_succeeds("sensor-sampling", "an.int", "none") # Wait for reply to above request get_msgs.wait_number(7) msgs = get_msgs() updates = [x for x in msgs if x.name == "sensor-status"] others = [x for x in msgs if x.name != "sensor-status"] self.assertTrue( abs(len(updates) - 5) < 2, "Expected 5 informs, saw %d." % len(updates)) self._assert_msgs_equal(others, [ r"!sensor-sampling[1] ok an.int period %s" % (1 / 32.), r"!sensor-sampling[2] ok an.int none", ]) self.assertEqual(updates[0].arguments[1:], ["1", "an.int", "nominal", "3"]) ## Now clear the strategies on this sensor # There should only be on connection to the server, so it should be # the test client client_conn = list(self.server._client_conns)[0] self.server.ioloop.add_callback(self.server.clear_strategies, client_conn) self.server.sync_with_ioloop() self.client.assert_request_succeeds("sensor-sampling", "an.int", args_equal=["an.int", "none"]) # Check that we did not accidentally clobber the strategy datastructure # in the proccess self.client.assert_request_succeeds("sensor-sampling", "an.int", "period", 0.125) def test_add_remove_sensors(self): """Test adding and removing sensors from a running device.""" an_int = self.server._sensors["an.int"] self.server.remove_sensor(an_int) # TODO remove_sensor test that checks that everything is indeed gone self.server.add_sensor(an_int) self.test_sampling() def test_async_request_handler(self): """ Request handlers allowing other requests to be handled before replying """ # We use a coroutine running on the client's ioloop for this test @gen.coroutine def do_async_request_test(threadsafe_future, tornado_future): try: slow_wait_time = 20 # Kick off a slow, async request slow_f = self.client.future_request( katcp.Message.request('slow-command', slow_wait_time)) t0 = time.time() # Do another normal request that should reply before the slow # request reply, _ = yield self.client.future_request( katcp.Message.request('watchdog')) self.assertTrue(reply.reply_ok(), 'Normal request should succeed') # Normal request should reply quickly t1 = time.time() self.assertTrue( t1 - t0 < 0.1 * slow_wait_time, 'The normal request should reply almost immediately') # Slow request should still be outstanding self.assertFalse( slow_f.done(), 'slow async request should not be complete yet') # Now cancel the slow command reply, _ = yield self.client.future_request( katcp.Message.request('cancel-slow-command')) self.assertTrue(reply.reply_ok(), '?cancel-slow-command should succeed') slow_reply, _ = yield slow_f self.assertTrue( slow_reply.reply_ok(), '?slow-command should succeed after being cancelled') t2 = time.time() self.assertTrue( t2 - t1 < 0.1 * slow_wait_time, 'Slow request should be cancelled almost immediately') except Exception as exc: tornado_future.set_exc_info(sys.exc_info()) threadsafe_future.set_exception(exc) else: threadsafe_future.set_result(None) tornado_future = gen.Future() threadsafe_future = Future() self.client.ioloop.add_callback(do_async_request_test, threadsafe_future, tornado_future) try: threadsafe_future.result() except Exception: # Use the tornado future to get a usable traceback tornado_future.result()
class TestDeviceServerClientIntegrated(unittest.TestCase, TestUtilMixin): BLACKLIST = ("version-connect", "version", "build-state") def setUp(self): self.server = DeviceTestServer('', 0) self.server.start(timeout=0.1) host, port = self.server._sock.getsockname() self.server_addr = (host, port) self.client = BlockingTestClient(self, host, port) self.client.start(timeout=0.1) self.assertTrue(self.client.wait_protocol(timeout=0.1)) def tearDown(self): if self.client.running(): self.client.stop() self.client.join() if self.server.running(): self.server.stop() self.server.join() def test_log(self): get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) with mock.patch('katcp.server.time.time') as m_time: m_time.return_value = 1234 self.server.log.error('An error') get_msgs.wait_number(1) self._assert_msgs_equal( get_msgs(), [r"#log error 1234.000000 root An\_error"]) def test_simple_connect(self): """Test a simple server setup and teardown with client connect.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) # basic send self.client.request(katcp.Message.request("foo"), use_mid=False) # pipe-lined send self.client.raw_send("?bar-boom\r\n?baz\r") # broken up sends self.client.raw_send("?boo") self.client.raw_send("m arg1 arg2") self.client.raw_send("\n") time.sleep(0.1) self._assert_msgs_equal(get_msgs(), [ r"!foo invalid Unknown\_request.", r"!bar-boom invalid Unknown\_request.", r"!baz invalid Unknown\_request.", r"!boom invalid Unknown\_request.", ]) def test_bad_requests(self): """Test request failure paths in device server.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) self.client.raw_send("bad msg\n") # wait for reply self.client.blocking_request( katcp.Message.request("watchdog"), use_mid=False) self._assert_msgs_like(get_msgs(), [ (r"#log error", "KatcpSyntaxError:" "\_Bad\_type\_character\_'b'.\\n"), (r"!watchdog ok", ""), ]) def test_slow_client(self): # Test that server does not choke sending messages to slow clients self.server.send_timeout = 0.1 # Set a short sending timeout self.client.wait_protocol(1) slow_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) slow_sock.connect(self.server_addr) slow_sock.settimeout(0.1) # Send a bunch of request to the server, but don't read anything from # the server try: slow_sock.sendall('?help\n'*100000) except socket.timeout: pass t0 = time.time() # Request should not have taken a very long time. self.client.assert_request_succeeds('help', informs_count=NO_HELP_MESSAGES) self.assertTrue(time.time() - t0 < 1) def test_server_ignores_informs_and_replies(self): """Test server ignores informs and replies.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) self.client.raw_send("#some inform\n") self.client.raw_send("!some reply\n") time.sleep(0.1) self.assertFalse(get_msgs()) def test_standard_requests(self): """Test standard request and replies.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) nomid_req = partial(self.client.request, use_mid=False) nomid_req(katcp.Message.request("watchdog"), use_mid=False) nomid_req(katcp.Message.request("restart")) nomid_req(katcp.Message.request("log-level")) nomid_req(katcp.Message.request("log-level", "trace")) nomid_req(katcp.Message.request("log-level", "unknown")) nomid_req(katcp.Message.request("help")) nomid_req(katcp.Message.request("help", "watchdog")) nomid_req(katcp.Message.request("help", "unknown-request")) nomid_req(katcp.Message.request("client-list")) nomid_req(katcp.Message.request("version-list")) nomid_req(katcp.Message.request("sensor-list")) nomid_req(katcp.Message.request("sensor-list", "an.int")) nomid_req(katcp.Message.request("sensor-list", "an.unknown")) nomid_req(katcp.Message.request("sensor-value")) nomid_req(katcp.Message.request("sensor-value", "an.int")) nomid_req(katcp.Message.request("sensor-value", "an.unknown")) nomid_req(katcp.Message.request("sensor-sampling", "an.int")) nomid_req(katcp.Message.request("sensor-sampling", "an.int", "differential", "2")) nomid_req(katcp.Message.request("sensor-sampling", "an.int", "event-rate", "2", "3")) nomid_req(katcp.Message.request("sensor-sampling")) nomid_req(katcp.Message.request("sensor-sampling", "an.unknown", "auto")) self.client.blocking_request(katcp.Message.request( "sensor-sampling", "an.int", "unknown"), use_mid=False) time.sleep(0.1) self.server.log.trace("trace-msg") self.server.log.debug("debug-msg") self.server.log.info("info-msg") self.server.log.warn("warn-msg") self.server.log.error("error-msg") self.server.log.fatal("fatal-msg") time.sleep(0.1) self.assertEqual(self.server.restart_queue.get_nowait(), self.server) self._assert_msgs_like(get_msgs(), [ (r"!watchdog ok", ""), (r"!restart ok", ""), (r"!log-level ok warn", ""), (r"!log-level ok trace", ""), (r"!log-level fail Unknown\_logging\_level\_name\_'unknown'", ""), (r"#help cancel-slow-command Cancel\_slow\_command\_request,\_" "resulting\_in\_it\_replying\_immediately", ""), (r"#help client-list", ""), (r"#help halt", ""), (r"#help help", ""), (r"#help log-level", ""), (r"#help new-command", ""), (r"#help raise-exception", ""), (r"#help raise-fail", ""), (r"#help restart", ""), (r"#help sensor-list", ""), (r"#help sensor-sampling", ""), (r"#help sensor-sampling-clear", ""), (r"#help sensor-value", ""), (r"#help slow-command", ""), (r"#help version-list", ""), (r"#help watchdog", ""), (r"!help ok %d" % NO_HELP_MESSAGES, ""), (r"#help watchdog", ""), (r"!help ok 1", ""), (r"!help fail", ""), (r"#client-list", ""), (r"!client-list ok 1", ""), (r"#version-list katcp-protocol", ""), (r"#version-list katcp-library", ""), (r"#version-list katcp-device", ""), (r"!version-list ok 3", ""), (r"#sensor-list an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list ok 1", ""), (r"#sensor-list an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list ok 1", ""), (r"!sensor-list fail", ""), (r"#sensor-value 12345.000000 1 an.int nominal 3", ""), (r"!sensor-value ok 1", ""), (r"#sensor-value 12345.000000 1 an.int nominal 3", ""), (r"!sensor-value ok 1", ""), (r"!sensor-value fail", ""), (r"!sensor-sampling ok an.int none", ""), (r"#sensor-status 12345.000000 1 an.int nominal 3", ""), (r"!sensor-sampling ok an.int differential 2", ""), (r"#sensor-status 12345.000000 1 an.int nominal 3", ""), (r"!sensor-sampling ok an.int event-rate 2 3", ""), (r"!sensor-sampling fail No\_sensor\_name\_given.", ""), (r"!sensor-sampling fail Unknown\_sensor\_name:\_an.unknown.", ""), (r"!sensor-sampling fail Unknown\_strategy\_name:\_unknown.", ""), (r"#log trace", r"root trace-msg"), (r"#log debug", r"root debug-msg"), (r"#log info", r"root info-msg"), (r"#log warn", r"root warn-msg"), (r"#log error", r"root error-msg"), (r"#log fatal", r"root fatal-msg"), ]) def test_standard_requests_with_ids(self): """Test standard request and replies with message ids.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) current_id = [0] def mid(): current_id[0] += 1 return str(current_id[0]) def mid_req(*args): return katcp.Message.request(*args, mid=mid()) self.client.request(mid_req("watchdog")) self.client.request(mid_req("restart")) self.client.request(mid_req("log-level")) self.client.request(mid_req("log-level", "trace")) self.client.request(mid_req("log-level", "unknown")) self.client.request(mid_req("help")) self.client.request(mid_req("help", "watchdog")) self.client.request(mid_req("help", "unknown-request")) self.client.request(mid_req("client-list")) self.client.request(mid_req("version-list")) self.client.request(mid_req("sensor-list")) self.client.request(mid_req("sensor-list", "an.int")) self.client.request(mid_req("sensor-list", "an.unknown")) self.client.request(mid_req("sensor-value")) self.client.request(mid_req("sensor-value", "an.int")) self.client.request(mid_req("sensor-value", "an.unknown")) self.client._next_id = mid # mock our mid generator for testing self.client.blocking_request(mid_req("sensor-sampling", "an.int")) self.client.blocking_request(mid_req( "sensor-sampling", "an.int", "differential", "2")) self.client.blocking_request(mid_req( "sensor-sampling", "an.int", "event-rate", "2", "3")), self.client.blocking_request(mid_req("sensor-sampling")) self.client.blocking_request(mid_req( "sensor-sampling", "an.unknown", "auto")) self.client.blocking_request(mid_req( "sensor-sampling", "an.int", "unknown")) self.server.log.trace("trace-msg") self.server.log.debug("debug-msg") self.server.log.info("info-msg") self.server.log.warn("warn-msg") self.server.log.error("error-msg") self.server.log.fatal("fatal-msg") time.sleep(0.1) self.assertEqual(self.server.restart_queue.get_nowait(), self.server) self._assert_msgs_like(get_msgs(), [ (r"!watchdog[1] ok", ""), (r"!restart[2] ok", ""), (r"!log-level[3] ok warn", ""), (r"!log-level[4] ok trace", ""), (r"!log-level[5] fail Unknown\_logging\_level\_name\_'unknown'", ""), (r"#help[6] cancel-slow-command Cancel\_slow\_command\_request,\_" "resulting\_in\_it\_replying\_immediately", ""), (r"#help[6] client-list", ""), (r"#help[6] halt", ""), (r"#help[6] help", ""), (r"#help[6] log-level", ""), (r"#help[6] new-command", ""), (r"#help[6] raise-exception", ""), (r"#help[6] raise-fail", ""), (r"#help[6] restart", ""), (r"#help[6] sensor-list", ""), (r"#help[6] sensor-sampling", ""), (r"#help[6] sensor-sampling-clear", ""), (r"#help[6] sensor-value", ""), (r"#help[6] slow-command", ""), (r"#help[6] version-list", ""), (r"#help[6] watchdog", ""), (r"!help[6] ok %d" % NO_HELP_MESSAGES, ""), (r"#help[7] watchdog", ""), (r"!help[7] ok 1", ""), (r"!help[8] fail", ""), (r"#client-list[9]", ""), (r"!client-list[9] ok 1", ""), (r"#version-list[10] katcp-protocol", ""), (r"#version-list[10] katcp-library", ""), (r"#version-list[10] katcp-device", ""), (r"!version-list[10] ok 3", ""), (r"#sensor-list[11] an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list[11] ok 1", ""), (r"#sensor-list[12] an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list[12] ok 1", ""), (r"!sensor-list[13] fail", ""), (r"#sensor-value[14] 12345.000000 1 an.int nominal 3", ""), (r"!sensor-value[14] ok 1", ""), (r"#sensor-value[15] 12345.000000 1 an.int nominal 3", ""), (r"!sensor-value[15] ok 1", ""), (r"!sensor-value[16] fail", ""), (r"!sensor-sampling[17] ok an.int none", ""), (r"#sensor-status 12345.000000 1 an.int nominal 3", ""), (r"!sensor-sampling[18] ok an.int differential 2", ""), (r"#sensor-status 12345.000000 1 an.int nominal 3", ""), (r"!sensor-sampling[19] ok an.int event-rate 2 3", ""), (r"!sensor-sampling[20] fail No\_sensor\_name\_given.", ""), (r"!sensor-sampling[21] fail Unknown\_sensor\_name:\_an.unknown.", ""), (r"!sensor-sampling[22] fail Unknown\_strategy\_name:\_unknown.", ""), (r"#log trace", r"root trace-msg"), (r"#log debug", r"root debug-msg"), (r"#log info", r"root info-msg"), (r"#log warn", r"root warn-msg"), (r"#log error", r"root error-msg"), (r"#log fatal", r"root fatal-msg"), ]) def test_sensor_list_regex(self): reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-list", "/a.*/"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"#sensor-list an.int An\_Integer. count integer -5 5", r"!sensor-list ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-list", "//"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"#sensor-list an.int An\_Integer. count integer -5 5", r"!sensor-list ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-list", "/^int/"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"!sensor-list ok 0", ]) def test_sensor_value_regex(self): reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-value", "/a.*/"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"#sensor-value 12345.000000 1 an.int nominal 3", r"!sensor-value ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-value", "//"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"#sensor-value 12345.000000 1 an.int nominal 3", r"!sensor-value ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-value", "/^int/"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"!sensor-value ok 0", ]) def test_client_list(self): reply, informs = self.client.blocking_request( katcp.Message.request('client-list'), use_mid=False) self.assertEqual(str(reply), '!client-list ok 1') self.assertEqual(len(informs), 1) inform = str(informs[0]) self.assertTrue(inform.startswith('#client-list 127.0.0.1:')) _, addr = inform.split() host, port = addr.split(':') port = int(port) self.assertEqual((host, port), self.client._sock.getsockname()) def test_halt_request(self): """Test halt request.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) self.client.request(katcp.Message.request("halt")) # hack to hide re-connect exception self.client.connect = lambda: None self.server.join() time.sleep(0.1) self._assert_msgs_equal(get_msgs(), [ r"!halt[1] ok", r"#disconnect Device\_server\_shutting\_down.", ]) def test_bad_handlers(self): """Test that bad request and inform handlers are picked up.""" try: class BadServer(katcp.DeviceServer): def request_baz(self, req, msg): pass except AssertionError: pass else: self.fail("Server metaclass accepted missing request_ docstring.") try: class BadServer(katcp.DeviceServer): def inform_baz(self, req, msg): pass except AssertionError: pass else: self.fail("Server metaclass accepted missing inform_ docstring.") class SortOfOkayServer(katcp.DeviceServer): request_bar = 1 inform_baz = 2 assert("bar" not in SortOfOkayServer._request_handlers) assert("baz" not in SortOfOkayServer._inform_handlers) def test_handler_exceptions(self): """Test handling of failure replies and other exceptions.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) self.assertTrue(self.client.wait_protocol(timeout=1)) self.client.request(katcp.Message.request("raise-exception")) self.client.request(katcp.Message.request("raise-fail")) time.sleep(0.1) self._assert_msgs_like(get_msgs(), [ (r"!raise-exception[1] fail Traceback", ""), (r"!raise-fail[2] fail There\_was\_a\_problem\_with\_your\_request.", ""), ]) def test_stop_and_restart(self): """Test stopping and restarting the device server.""" self.server.stop(timeout=0.1) self.server.join(timeout=1.0) self.assertEqual(self.server._thread, None) self.assertFalse(self.server._running.isSet()) self.server.start(timeout=1.0) def test_bad_client_socket(self): """Test what happens when select is called on a dead client socket.""" # wait for client to arrive time.sleep(0.1) # close socket while the server isn't looking # then wait for the server to notice sock = self.server._socks[0] sock.close() time.sleep(0.75) # check that client was removed self.assertTrue(sock not in self.server._socks, "Expected %r to not be in %r" % (sock, self.server._socks)) def test_bad_server_socket(self): """Test what happens when select is called on a dead server socket.""" # wait for client to arrive time.sleep(0.1) # close socket while the server isn't looking # then wait for the server to notice sock = self.server._sock sockname = sock.getsockname() sock.close() time.sleep(0.75) # check that server restarted self.assertTrue(sock is not self.server._sock, "Expected %r to not be %r" % (sock, self.server._sock)) self.assertEqual(sockname, self.server._sock.getsockname()) def test_daemon_value(self): """Test passing in a daemon value to server start method.""" self.server.stop(timeout=0.1) self.server.join(timeout=1.0) self.server.start(timeout=0.1, daemon=True) self.assertTrue(self.server._thread.isDaemon()) def test_excepthook(self): """Test passing in an excepthook to server start method.""" exceptions = [] except_event = threading.Event() def excepthook(etype, value, traceback): """Keep track of exceptions.""" exceptions.append(etype) except_event.set() self.server.stop(timeout=0.1) self.server.join(timeout=1.5) self.server.start(timeout=0.1, excepthook=excepthook) # force exception by deleteing _running old_running = self.server._running try: del self.server._running except_event.wait(1.5) self.assertEqual(exceptions, [AttributeError]) finally: self.server._running = old_running # close socket -- server didn't shut down correctly self.server._sock.close() self.server.stop(timeout=0.1) self.server.join(timeout=1.5) except_event.clear() del exceptions[:] self.server.start(timeout=0.1, excepthook=excepthook) # force exception in sample reactor and check that it makes # it back up reactor = self.server._reactor old_stop = reactor._stopEvent try: del reactor._stopEvent reactor._wakeEvent.set() except_event.wait(0.1) self.assertEqual(exceptions, [AttributeError]) finally: reactor._stopEvent = old_stop # close socket -- server didn't shut down correctly self.server._sock.close() def test_sampling(self): """Test sensor sampling.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) self.client.wait_protocol(timeout=1) self.client.request(katcp.Message.request( "sensor-sampling", "an.int", "period", 1/32.)) # Wait for the request reply and for the sensor update messages to # arrive. We expect update one the moment the sensor-sampling request is # made, then four more over 4/32. of a second, resutling in 6 # messages. Wait half a period longer just to be sure we get everything. self.assertTrue(get_msgs.wait_number(6, timeout=4.5/32.)) self.client.assert_request_succeeds("sensor-sampling", "an.int", "none") # Wait for reply to above request get_msgs.wait_number(7) msgs = get_msgs() updates = [x for x in msgs if x.name == "sensor-status"] others = [x for x in msgs if x.name != "sensor-status"] self.assertTrue(abs(len(updates) - 5) < 2, "Expected 5 informs, saw %d." % len(updates)) self._assert_msgs_equal(others, [ r"!sensor-sampling[1] ok an.int period %s" % (1/32.), r"!sensor-sampling[2] ok an.int none", ]) self.assertEqual(updates[0].arguments[1:], ["1", "an.int", "nominal", "3"]) ## Now clear the strategies on this sensor # There should only be on connection to the server, so it should be # the test client client_conn = self.server._sock_connections.values()[0] self.server.clear_strategies(client_conn) self.client.assert_request_succeeds("sensor-sampling", "an.int", args_equal=["an.int", "none"]) # Check that we did not accidentally clobber the strategy datastructure # in the proccess self.client.assert_request_succeeds( "sensor-sampling", "an.int", "period", 0.125) def test_add_remove_sensors(self): """Test adding and removing sensors from a running device.""" an_int = self.server._sensors["an.int"] self.server.remove_sensor(an_int) self.server.add_sensor(an_int) self.test_sampling()
class TestDeviceServerClientIntegrated(unittest.TestCase, TestUtilMixin): BLACKLIST = ("version-connect", "version", "build-state") def setUp(self): self.server = DeviceTestServer('', 0) self.server.start(timeout=0.1) host, port = self.server._sock.getsockname() self.server_addr = (host, port) self.client = BlockingTestClient(self, host, port) self.client.start(timeout=0.1) self.assertTrue(self.client.wait_protocol(timeout=0.1)) def tearDown(self): if self.client.running(): self.client.stop() self.client.join() if self.server.running(): self.server.stop() self.server.join() def test_log(self): get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) with mock.patch('katcp.server.time.time') as m_time: m_time.return_value = 1234 self.server.log.error('An error') get_msgs.wait_number(1) self._assert_msgs_equal( get_msgs(), [r"#log error 1234.000000 root An\_error"]) def test_simple_connect(self): """Test a simple server setup and teardown with client connect.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) # basic send self.client.request(katcp.Message.request("foo"), use_mid=False) # pipe-lined send self.client.raw_send("?bar-boom\r\n?baz\r") # broken up sends self.client.raw_send("?boo") self.client.raw_send("m arg1 arg2") self.client.raw_send("\n") time.sleep(0.1) self._assert_msgs_equal(get_msgs(), [ r"!foo invalid Unknown\_request.", r"!bar-boom invalid Unknown\_request.", r"!baz invalid Unknown\_request.", r"!boom invalid Unknown\_request.", ]) def test_bad_requests(self): """Test request failure paths in device server.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) self.client.raw_send("bad msg\n") # wait for reply self.client.blocking_request( katcp.Message.request("watchdog"), use_mid=False) self._assert_msgs_like(get_msgs(), [ (r"#log error", "KatcpSyntaxError:" "\_Bad\_type\_character\_'b'.\\n"), (r"!watchdog ok", ""), ]) def test_slow_client(self): # Test that server does not choke sending messages to slow clients self.server.send_timeout = 0.1 # Set a short sending timeout self.client.wait_protocol(1) slow_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) slow_sock.connect(self.server_addr) slow_sock.settimeout(0.1) # Send a bunch of request to the server, but don't read anything from # the server try: slow_sock.sendall('?help\n'*100000) except socket.timeout: pass t0 = time.time() # Request should not have taken a very long time. self.client.assert_request_succeeds('help', informs_count=NO_HELP_MESSAGES) self.assertTrue(time.time() - t0 < 1) def test_server_ignores_informs_and_replies(self): """Test server ignores informs and replies.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) self.client.raw_send("#some inform\n") self.client.raw_send("!some reply\n") time.sleep(0.1) self.assertFalse(get_msgs()) def test_standard_requests(self): """Test standard request and replies.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) nomid_req = partial(self.client.request, use_mid=False) nomid_req(katcp.Message.request("watchdog"), use_mid=False) nomid_req(katcp.Message.request("restart")) nomid_req(katcp.Message.request("log-level")) nomid_req(katcp.Message.request("log-level", "trace")) nomid_req(katcp.Message.request("log-level", "unknown")) nomid_req(katcp.Message.request("help")) nomid_req(katcp.Message.request("help", "watchdog")) nomid_req(katcp.Message.request("help", "unknown-request")) nomid_req(katcp.Message.request("client-list")) nomid_req(katcp.Message.request("version-list")) nomid_req(katcp.Message.request("sensor-list")) nomid_req(katcp.Message.request("sensor-list", "an.int")) nomid_req(katcp.Message.request("sensor-list", "an.unknown")) nomid_req(katcp.Message.request("sensor-value")) nomid_req(katcp.Message.request("sensor-value", "an.int")) nomid_req(katcp.Message.request("sensor-value", "an.unknown")) nomid_req(katcp.Message.request("sensor-sampling", "an.int")) nomid_req(katcp.Message.request("sensor-sampling", "an.int", "differential", "2")) nomid_req(katcp.Message.request("sensor-sampling", "an.int", "event-rate", "2", "3")) nomid_req(katcp.Message.request("sensor-sampling")) nomid_req(katcp.Message.request("sensor-sampling", "an.unknown", "auto")) self.client.blocking_request(katcp.Message.request( "sensor-sampling", "an.int", "unknown"), use_mid=False) time.sleep(0.1) self.server.log.trace("trace-msg") self.server.log.debug("debug-msg") self.server.log.info("info-msg") self.server.log.warn("warn-msg") self.server.log.error("error-msg") self.server.log.fatal("fatal-msg") time.sleep(0.1) self.assertEqual(self.server.restart_queue.get_nowait(), self.server) self._assert_msgs_like(get_msgs(), [ (r"!watchdog ok", ""), (r"!restart ok", ""), (r"!log-level ok warn", ""), (r"!log-level ok trace", ""), (r"!log-level fail Unknown\_logging\_level\_name\_'unknown'", ""), (r"#help cancel-slow-command Cancel\_slow\_command\_request,\_" "resulting\_in\_it\_replying\_immediately", ""), (r"#help client-list", ""), (r"#help halt", ""), (r"#help help", ""), (r"#help log-level", ""), (r"#help new-command", ""), (r"#help raise-exception", ""), (r"#help raise-fail", ""), (r"#help restart", ""), (r"#help sensor-list", ""), (r"#help sensor-sampling", ""), (r"#help sensor-sampling-clear", ""), (r"#help sensor-value", ""), (r"#help slow-command", ""), (r"#help version-list", ""), (r"#help watchdog", ""), (r"!help ok %d" % NO_HELP_MESSAGES, ""), (r"#help watchdog", ""), (r"!help ok 1", ""), (r"!help fail", ""), (r"#client-list", ""), (r"!client-list ok 1", ""), (r"#version-list katcp-protocol", ""), (r"#version-list katcp-library", ""), (r"#version-list katcp-device", ""), (r"!version-list ok 3", ""), (r"#sensor-list an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list ok 1", ""), (r"#sensor-list an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list ok 1", ""), (r"!sensor-list fail", ""), (r"#sensor-value 12345.000000 1 an.int nominal 3", ""), (r"!sensor-value ok 1", ""), (r"#sensor-value 12345.000000 1 an.int nominal 3", ""), (r"!sensor-value ok 1", ""), (r"!sensor-value fail", ""), (r"!sensor-sampling ok an.int none", ""), (r"#sensor-status 12345.000000 1 an.int nominal 3", ""), (r"!sensor-sampling ok an.int differential 2", ""), (r"#sensor-status 12345.000000 1 an.int nominal 3", ""), (r"!sensor-sampling ok an.int event-rate 2 3", ""), (r"!sensor-sampling fail No\_sensor\_name\_given.", ""), (r"!sensor-sampling fail Unknown\_sensor\_name:\_an.unknown.", ""), (r"!sensor-sampling fail Unknown\_strategy\_name:\_unknown.", ""), (r"#log trace", r"root trace-msg"), (r"#log debug", r"root debug-msg"), (r"#log info", r"root info-msg"), (r"#log warn", r"root warn-msg"), (r"#log error", r"root error-msg"), (r"#log fatal", r"root fatal-msg"), ]) def test_standard_requests_with_ids(self): """Test standard request and replies with message ids.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) current_id = [0] def mid(): current_id[0] += 1 return str(current_id[0]) def mid_req(*args): return katcp.Message.request(*args, mid=mid()) self.client.request(mid_req("watchdog")) self.client.request(mid_req("restart")) self.client.request(mid_req("log-level")) self.client.request(mid_req("log-level", "trace")) self.client.request(mid_req("log-level", "unknown")) self.client.request(mid_req("help")) self.client.request(mid_req("help", "watchdog")) self.client.request(mid_req("help", "unknown-request")) self.client.request(mid_req("client-list")) self.client.request(mid_req("version-list")) self.client.request(mid_req("sensor-list")) self.client.request(mid_req("sensor-list", "an.int")) self.client.request(mid_req("sensor-list", "an.unknown")) self.client.request(mid_req("sensor-value")) self.client.request(mid_req("sensor-value", "an.int")) self.client.request(mid_req("sensor-value", "an.unknown")) self.client._next_id = mid # mock our mid generator for testing self.client.blocking_request(mid_req("sensor-sampling", "an.int")) self.client.blocking_request(mid_req( "sensor-sampling", "an.int", "differential", "2")) self.client.blocking_request(mid_req( "sensor-sampling", "an.int", "event-rate", "2", "3")), self.client.blocking_request(mid_req("sensor-sampling")) self.client.blocking_request(mid_req( "sensor-sampling", "an.unknown", "auto")) self.client.blocking_request(mid_req( "sensor-sampling", "an.int", "unknown")) self.server.log.trace("trace-msg") self.server.log.debug("debug-msg") self.server.log.info("info-msg") self.server.log.warn("warn-msg") self.server.log.error("error-msg") self.server.log.fatal("fatal-msg") time.sleep(0.1) self.assertEqual(self.server.restart_queue.get_nowait(), self.server) self._assert_msgs_like(get_msgs(), [ (r"!watchdog[1] ok", ""), (r"!restart[2] ok", ""), (r"!log-level[3] ok warn", ""), (r"!log-level[4] ok trace", ""), (r"!log-level[5] fail Unknown\_logging\_level\_name\_'unknown'", ""), (r"#help[6] cancel-slow-command Cancel\_slow\_command\_request,\_" "resulting\_in\_it\_replying\_immediately", ""), (r"#help[6] client-list", ""), (r"#help[6] halt", ""), (r"#help[6] help", ""), (r"#help[6] log-level", ""), (r"#help[6] new-command", ""), (r"#help[6] raise-exception", ""), (r"#help[6] raise-fail", ""), (r"#help[6] restart", ""), (r"#help[6] sensor-list", ""), (r"#help[6] sensor-sampling", ""), (r"#help[6] sensor-sampling-clear", ""), (r"#help[6] sensor-value", ""), (r"#help[6] slow-command", ""), (r"#help[6] version-list", ""), (r"#help[6] watchdog", ""), (r"!help[6] ok %d" % NO_HELP_MESSAGES, ""), (r"#help[7] watchdog", ""), (r"!help[7] ok 1", ""), (r"!help[8] fail", ""), (r"#client-list[9]", ""), (r"!client-list[9] ok 1", ""), (r"#version-list[10] katcp-protocol", ""), (r"#version-list[10] katcp-library", ""), (r"#version-list[10] katcp-device", ""), (r"!version-list[10] ok 3", ""), (r"#sensor-list[11] an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list[11] ok 1", ""), (r"#sensor-list[12] an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list[12] ok 1", ""), (r"!sensor-list[13] fail", ""), (r"#sensor-value[14] 12345.000000 1 an.int nominal 3", ""), (r"!sensor-value[14] ok 1", ""), (r"#sensor-value[15] 12345.000000 1 an.int nominal 3", ""), (r"!sensor-value[15] ok 1", ""), (r"!sensor-value[16] fail", ""), (r"!sensor-sampling[17] ok an.int none", ""), (r"#sensor-status 12345.000000 1 an.int nominal 3", ""), (r"!sensor-sampling[18] ok an.int differential 2", ""), (r"#sensor-status 12345.000000 1 an.int nominal 3", ""), (r"!sensor-sampling[19] ok an.int event-rate 2 3", ""), (r"!sensor-sampling[20] fail No\_sensor\_name\_given.", ""), (r"!sensor-sampling[21] fail Unknown\_sensor\_name:\_an.unknown.", ""), (r"!sensor-sampling[22] fail Unknown\_strategy\_name:\_unknown.", ""), (r"#log trace", r"root trace-msg"), (r"#log debug", r"root debug-msg"), (r"#log info", r"root info-msg"), (r"#log warn", r"root warn-msg"), (r"#log error", r"root error-msg"), (r"#log fatal", r"root fatal-msg"), ]) def test_sensor_list_regex(self): reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-list", "/a.*/"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"#sensor-list an.int An\_Integer. count integer -5 5", r"!sensor-list ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-list", "//"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"#sensor-list an.int An\_Integer. count integer -5 5", r"!sensor-list ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-list", "/^int/"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"!sensor-list ok 0", ]) def test_sensor_value_regex(self): reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-value", "/a.*/"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"#sensor-value 12345.000000 1 an.int nominal 3", r"!sensor-value ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-value", "//"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"#sensor-value 12345.000000 1 an.int nominal 3", r"!sensor-value ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request( "sensor-value", "/^int/"), use_mid=False) self._assert_msgs_equal(informs + [reply], [ r"!sensor-value ok 0", ]) def test_client_list(self): reply, informs = self.client.blocking_request( katcp.Message.request('client-list'), use_mid=False) self.assertEqual(str(reply), '!client-list ok 1') self.assertEqual(len(informs), 1) inform = str(informs[0]) self.assertTrue(inform.startswith('#client-list 127.0.0.1:')) _, addr = inform.split() host, port = addr.split(':') port = int(port) self.assertEqual((host, port), self.client._sock.getsockname()) def test_halt_request(self): """Test halt request.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) self.client.request(katcp.Message.request("halt")) # hack to hide re-connect exception self.client.connect = lambda: None self.server.join() time.sleep(0.1) self._assert_msgs_equal(get_msgs(), [ r"!halt[1] ok", r"#disconnect Device\_server\_shutting\_down.", ]) def test_bad_handlers(self): """Test that bad request and inform handlers are picked up.""" try: class BadServer(katcp.DeviceServer): def request_baz(self, req, msg): pass except AssertionError: pass else: self.fail("Server metaclass accepted missing request_ docstring.") try: class BadServer(katcp.DeviceServer): def inform_baz(self, req, msg): pass except AssertionError: pass else: self.fail("Server metaclass accepted missing inform_ docstring.") class SortOfOkayServer(katcp.DeviceServer): request_bar = 1 inform_baz = 2 assert("bar" not in SortOfOkayServer._request_handlers) assert("baz" not in SortOfOkayServer._inform_handlers) def test_handler_exceptions(self): """Test handling of failure replies and other exceptions.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) self.assertTrue(self.client.wait_protocol(timeout=1)) self.client.request(katcp.Message.request("raise-exception")) self.client.request(katcp.Message.request("raise-fail")) time.sleep(0.1) self._assert_msgs_like(get_msgs(), [ (r"!raise-exception[1] fail Traceback", ""), (r"!raise-fail[2] fail There\_was\_a\_problem\_with\_your\_request.", ""), ]) def test_stop_and_restart(self): """Test stopping and restarting the device server.""" self.server.stop(timeout=0.1) self.server.join(timeout=1.0) self.assertEqual(self.server._thread, None) self.assertFalse(self.server._running.isSet()) self.server.start(timeout=1.0) def test_bad_client_socket(self): """Test what happens when select is called on a dead client socket.""" # wait for client to arrive time.sleep(0.1) # close socket while the server isn't looking # then wait for the server to notice sock = self.server._socks[0] sock.close() time.sleep(0.75) # check that client was removed self.assertTrue(sock not in self.server._socks, "Expected %r to not be in %r" % (sock, self.server._socks)) def test_bad_server_socket(self): """Test what happens when select is called on a dead server socket.""" # wait for client to arrive time.sleep(0.1) # close socket while the server isn't looking # then wait for the server to notice sock = self.server._sock sockname = sock.getsockname() sock.close() time.sleep(0.75) # check that server restarted self.assertTrue(sock is not self.server._sock, "Expected %r to not be %r" % (sock, self.server._sock)) self.assertEqual(sockname, self.server._sock.getsockname()) def test_daemon_value(self): """Test passing in a daemon value to server start method.""" self.server.stop(timeout=0.1) self.server.join(timeout=1.0) self.server.start(timeout=0.1, daemon=True) self.assertTrue(self.server._thread.isDaemon()) def test_excepthook(self): """Test passing in an excepthook to server start method.""" exceptions = [] except_event = threading.Event() def excepthook(etype, value, traceback): """Keep track of exceptions.""" exceptions.append(etype) except_event.set() self.server.stop(timeout=0.1) self.server.join(timeout=1.5) self.server.start(timeout=0.1, excepthook=excepthook) # force exception by deleteing _running old_running = self.server._running try: del self.server._running except_event.wait(1.5) self.assertEqual(exceptions, [AttributeError]) finally: self.server._running = old_running # close socket -- server didn't shut down correctly self.server._sock.close() self.server.stop(timeout=0.1) self.server.join(timeout=1.5) except_event.clear() del exceptions[:] self.server.start(timeout=0.1, excepthook=excepthook) # force exception in sample reactor and check that it makes # it back up reactor = self.server._reactor old_stop = reactor._stopEvent try: del reactor._stopEvent reactor._wakeEvent.set() except_event.wait(0.1) self.assertEqual(exceptions, [AttributeError]) finally: reactor._stopEvent = old_stop # close socket -- server didn't shut down correctly self.server._sock.close() def test_sampling(self): """Test sensor sampling.""" get_msgs = self.client.message_recorder( blacklist=self.BLACKLIST, replies=True) self.client.wait_protocol(timeout=1) self.client.request(katcp.Message.request( "sensor-sampling", "an.int", "period", 1/32.)) # Wait for the request reply and for the sensor update messages to # arrive. We expect update one the moment the sensor-sampling request is # made, then four more over 4/32. of a second, resutling in 6 # messages. Wait half a period longer just to be sure we get everything. self.assertTrue(get_msgs.wait_number(6, timeout=4.5/32.)) self.client.assert_request_succeeds("sensor-sampling", "an.int", "none") # Wait for reply to above request get_msgs.wait_number(7) msgs = get_msgs() updates = [x for x in msgs if x.name == "sensor-status"] others = [x for x in msgs if x.name != "sensor-status"] self.assertTrue(abs(len(updates) - 5) < 2, "Expected 5 informs, saw %d." % len(updates)) self._assert_msgs_equal(others, [ r"!sensor-sampling[1] ok an.int period %s" % (1/32.), r"!sensor-sampling[2] ok an.int none", ]) self.assertEqual(updates[0].arguments[1:], ["1", "an.int", "nominal", "3"]) ## Now clear the strategies on this sensor # There should only be on connection to the server, so it should be # the test client client_conn = self.server._sock_connections.values()[0] self.server.clear_strategies(client_conn) self.client.assert_request_succeeds("sensor-sampling", "an.int", args_equal=["an.int", "none"]) # Check that we did not accidentally clobber the strategy datastructure # in the proccess self.client.assert_request_succeeds( "sensor-sampling", "an.int", "period", 0.125) def test_add_remove_sensors(self): """Test adding and removing sensors from a running device.""" an_int = self.server._sensors["an.int"] self.server.remove_sensor(an_int) self.server.add_sensor(an_int) self.test_sampling()
class TestCallbackClient(unittest.TestCase, TestUtilMixin): def setUp(self): self.server = DeviceTestServer('', 0) self.server.start(timeout=0.1) host, port = self.server._sock.getsockname() self.client = CallbackTestClient(host, port) self.client.start(timeout=0.1) def tearDown(self): if self.client.running(): self.client.stop() self.client.join() if self.server.running(): self.server.stop() self.server.join() def test_callback_request(self): """Test callback request.""" watchdog_replies = [] def watchdog_reply(reply): self.assertEqual(reply.name, "watchdog") self.assertEqual(reply.arguments, ["ok"]) watchdog_replies.append(reply) self.client.request( katcp.Message.request("watchdog"), reply_cb=watchdog_reply, ) time.sleep(0.1) self.assertTrue(watchdog_replies) help_replies = [] help_informs = [] def help_reply(reply): self.assertEqual(reply.name, "help") self.assertEqual(reply.arguments, ["ok", "13"]) self.assertEqual(len(help_informs), int(reply.arguments[1])) help_replies.append(reply) def help_inform(inform): self.assertEqual(inform.name, "help") self.assertEqual(len(inform.arguments), 2) help_informs.append(inform) self.client.request( katcp.Message.request("help"), reply_cb=help_reply, inform_cb=help_inform, ) time.sleep(0.2) self.assertEqual(len(help_replies), 1) self.assertEqual(len(help_informs), 13) def test_no_callback(self): """Test request without callback.""" self.client.request( katcp.Message.request("help") ) time.sleep(0.1) msgs = self.client.messages() self._assert_msgs_like(msgs, [("#version ", "")] + [("#build-state ", "")] + [("#help ", "")]*13 + [("!help ok 13", "")] ) def test_timeout(self): """Test requests that timeout.""" replies = [] informs = [] timeout = 0.001 def reply_cb(msg): replies.append(msg) def inform_cb(msg): informs.append(msg) self.client.request( katcp.Message.request("slow-command", "0.1"), reply_cb=reply_cb, inform_cb=inform_cb, timeout=timeout, ) time.sleep(0.2) self.assertEqual(len(replies), 1) self.assertEqual(len(informs), 0) self.assertEqual([msg.name for msg in replies], ["slow-command"]) self.assertEqual([msg.arguments for msg in replies], [["fail", "Timed out after %f seconds" % timeout]]) del replies[:] del informs[:] # test next request succeeds self.client.request( katcp.Message.request("slow-command", "0.05"), reply_cb=reply_cb, inform_cb=inform_cb, ) time.sleep(0.2) self.assertEqual(len(replies), 1) self.assertEqual(len(informs), 0) self.assertEqual([msg.name for msg in replies + informs], ["slow-command"]*len(replies+informs)) self.assertEqual([msg.arguments for msg in replies], [["ok"]]) def test_user_data(self): """Test callbacks with user data.""" help_replies = [] help_informs = [] def help_reply(reply, x, y): self.assertEqual(reply.name, "help") self.assertEqual(x, 5) self.assertEqual(y, "foo") help_replies.append(reply) def help_inform(inform, x, y): self.assertEqual(inform.name, "help") self.assertEqual(x, 5) self.assertEqual(y, "foo") help_informs.append(inform) self.client.request( katcp.Message.request("help"), reply_cb=help_reply, inform_cb=help_inform, user_data=(5, "foo") ) time.sleep(0.1) self.assertEqual(len(help_replies), 1) self.assertEqual(len(help_informs), 13) def test_twenty_thread_mayhem(self): """Test using callbacks from twenty threads simultaneously.""" num_threads = 50 # map from thread_id -> (replies, informs) results = {} # list of thread objects threads = [] def reply_cb(reply, thread_id): results[thread_id][0].append(reply) results[thread_id][2].set() def inform_cb(inform, thread_id): results[thread_id][1].append(inform) def worker(thread_id, request): self.client.request( request, reply_cb=reply_cb, inform_cb=inform_cb, user_data=(thread_id,), ) request = katcp.Message.request("help") for thread_id in range(num_threads): results[thread_id] = ([], [], threading.Event()) for thread_id in range(num_threads): thread = threading.Thread(target=worker, args=(thread_id, request)) threads.append(thread) for thread in threads: thread.start() for thread in threads: thread.join() for thread_id in range(num_threads): replies, informs, done = results[thread_id] done.wait(1.0) self.assertEqual(len(replies), 1) self.assertEqual(replies[0].arguments[0], "ok") if len(informs) != 13: print thread_id, len(informs) print [x.arguments[0] for x in informs] self.assertEqual(len(informs), 13) def test_blocking_request(self): """Test the callback client's blocking request.""" reply, informs = self.client.blocking_request( katcp.Message.request("help"), ) self.assertEqual(reply.name, "help") self.assertEqual(reply.arguments, ["ok", "13"]) self.assertEqual(len(informs), 13) reply, informs = self.client.blocking_request( katcp.Message.request("slow-command", "0.5"), timeout = 0.001, ) self.assertEqual(reply.name, "slow-command") self.assertEqual(reply.arguments[0], "fail") self.assertTrue(reply.arguments[1].startswith("Timed out after")) def test_use_ids(self): """Test the callbak client's use of message ids.""" self.client._use_ids = True watchdog_replies = [] def watchdog_reply(reply): self.assertEqual(reply.name, "watchdog") self.assertEqual(reply.arguments, ["ok"]) watchdog_replies.append(reply) self.client.request( katcp.Message.request("watchdog"), reply_cb=watchdog_reply, ) time.sleep(0.1) self.assertTrue(watchdog_replies) help_replies = [] help_informs = [] def help_reply(reply): self.assertEqual(reply.name, "help") self.assertEqual(reply.arguments, ["ok", "13"]) self.assertEqual(len(help_informs), int(reply.arguments[1])) help_replies.append(reply) def help_inform(inform): self.assertEqual(inform.name, "help") self.assertEqual(len(inform.arguments), 2) help_informs.append(inform) self.client.request( katcp.Message.request("help"), reply_cb=help_reply, inform_cb=help_inform, ) time.sleep(0.2) self.assertEqual(len(help_replies), 1) self.assertEqual(len(help_informs), 13) def test_request_fail_on_raise(self): """Test that the callback is called even if send_message raises KatcpClientError.""" def raise_error(msg): raise katcp.KatcpClientError("Error %s" % msg.name) self.client.send_message = raise_error replies = [] def reply_cb(msg): replies.append(msg) self.client.request(katcp.Message.request("foo"), reply_cb=reply_cb, ) self.assertEqual(len(replies), 1) self.assertEqual(replies[0].name, "foo") self.assertEqual(replies[0].arguments, ["fail", "Error foo"])
class TestDeviceServer(unittest.TestCase, TestUtilMixin): def setUp(self): self.server = DeviceTestServer('', 0) self.server.start(timeout=0.1) host, port = self.server._sock.getsockname() self.client = BlockingTestClient(self, host, port) self.client.start(timeout=0.1) def tearDown(self): if self.client.running(): self.client.stop() self.client.join() if self.server.running(): self.server.stop() self.server.join() def test_simple_connect(self): """Test a simple server setup and teardown with client connect.""" # basic send self.client.request(katcp.Message.request("foo")) # pipe-lined send self.client.raw_send("?bar-boom\r\n?baz\r") # broken up sends self.client.raw_send("?boo") self.client.raw_send("m arg1 arg2") self.client.raw_send("\n") time.sleep(0.1) msgs = self.client.messages() self._assert_msgs_equal(msgs, [ r"#version device_stub-0.1", r"#build-state name-0.1", r"!foo invalid Unknown\_request.", r"!bar-boom invalid Unknown\_request.", r"!baz invalid Unknown\_request.", r"!boom invalid Unknown\_request.", ]) def test_bad_requests(self): """Test request failure paths in device server.""" self.client.raw_send("bad msg\n") # wait for reply self.client.blocking_request(katcp.Message.request("watchdog")) msgs = self.client.messages() self._assert_msgs_like(msgs, [ (r"#version device_stub-0.1", ""), (r"#build-state name-0.1", ""), (r"#log error", "KatcpSyntaxError:\_Bad\_type\_character\_'b'.\\n"), (r"!watchdog ok", ""), ]) def test_server_ignores_informs_and_replies(self): """Test server ignores informs and replies.""" self.client.raw_send("#some inform\n") self.client.raw_send("!some reply\n") time.sleep(0.1) msgs = self.client.messages() self._assert_msgs_like(msgs, [ (r"#version device_stub-0.1", ""), (r"#build-state name-0.1", ""), ]) def test_standard_requests(self): """Test standard request and replies.""" self.client.request(katcp.Message.request("watchdog")) self.client.request(katcp.Message.request("restart")) self.client.request(katcp.Message.request("log-level")) self.client.request(katcp.Message.request("log-level", "trace")) self.client.request(katcp.Message.request("log-level", "unknown")) self.client.request(katcp.Message.request("help")) self.client.request(katcp.Message.request("help", "watchdog")) self.client.request(katcp.Message.request("help", "unknown-request")) self.client.request(katcp.Message.request("client-list")) self.client.request(katcp.Message.request("sensor-list")) self.client.request(katcp.Message.request("sensor-list", "an.int")) self.client.request(katcp.Message.request("sensor-list", "an.unknown")) self.client.request(katcp.Message.request("sensor-value")) self.client.request(katcp.Message.request("sensor-value", "an.int")) self.client.request(katcp.Message.request("sensor-value", "an.unknown")) self.client.request(katcp.Message.request("sensor-sampling", "an.int")) self.client.request(katcp.Message.request("sensor-sampling", "an.int", "differential", "2")) self.client.request(katcp.Message.request("sensor-sampling")) self.client.request(katcp.Message.request("sensor-sampling", "an.unknown", "auto")) self.client.blocking_request(katcp.Message.request("sensor-sampling", "an.int", "unknown")) time.sleep(0.1) self.server.log.trace("trace-msg") self.server.log.debug("debug-msg") self.server.log.info("info-msg") self.server.log.warn("warn-msg") self.server.log.error("error-msg") self.server.log.fatal("fatal-msg") time.sleep(0.1) msgs = self.client.messages() self.assertEqual(self.server.restart_queue.get_nowait(), self.server) self._assert_msgs_like(msgs, [ (r"#version device_stub-0.1", ""), (r"#build-state name-0.1", ""), (r"!watchdog ok", ""), (r"!restart ok", ""), (r"!log-level ok warn", ""), (r"!log-level ok trace", ""), (r"!log-level fail Unknown\_logging\_level\_name\_'unknown'", ""), (r"#help client-list", ""), (r"#help halt", ""), (r"#help help", ""), (r"#help log-level", ""), (r"#help new-command", ""), (r"#help raise-exception", ""), (r"#help raise-fail", ""), (r"#help restart", ""), (r"#help sensor-list", ""), (r"#help sensor-sampling", ""), (r"#help sensor-value", ""), (r"#help slow-command", ""), (r"#help watchdog", ""), (r"!help ok 13", ""), (r"#help watchdog", ""), (r"!help ok 1", ""), (r"!help fail", ""), (r"#client-list", ""), (r"!client-list ok 1", ""), (r"#sensor-list an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list ok 1", ""), (r"#sensor-list an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list ok 1", ""), (r"!sensor-list fail", ""), (r"#sensor-value 12345000 1 an.int nominal 3", ""), (r"!sensor-value ok 1", ""), (r"#sensor-value 12345000 1 an.int nominal 3", ""), (r"!sensor-value ok 1", ""), (r"!sensor-value fail", ""), (r"!sensor-sampling ok an.int none", ""), (r"#sensor-status 12345000 1 an.int nominal 3", ""), (r"!sensor-sampling ok an.int differential 2", ""), (r"!sensor-sampling fail No\_sensor\_name\_given.", ""), (r"!sensor-sampling fail Unknown\_sensor\_name.", ""), (r"!sensor-sampling fail Unknown\_strategy\_name.", ""), (r"#log trace", r"root trace-msg"), (r"#log debug", r"root debug-msg"), (r"#log info", r"root info-msg"), (r"#log warn", r"root warn-msg"), (r"#log error", r"root error-msg"), (r"#log fatal", r"root fatal-msg"), ]) def test_standard_requests_with_ids(self): """Test standard request and replies with message ids.""" current_id = [0] def mid(): current_id[0] += 1 return current_id[0] self.client.request(katcp.Message.request("watchdog", mid=mid())) self.client.request(katcp.Message.request("restart", mid=mid())) self.client.request(katcp.Message.request("log-level", mid=mid())) self.client.request(katcp.Message.request("log-level", "trace", mid=mid())) self.client.request(katcp.Message.request("log-level", "unknown", mid=mid())) self.client.request(katcp.Message.request("help", mid=mid())) self.client.request(katcp.Message.request("help", "watchdog", mid=mid())) self.client.request(katcp.Message.request("help", "unknown-request", mid=mid())) self.client.request(katcp.Message.request("client-list", mid=mid())) self.client.request(katcp.Message.request("sensor-list", mid=mid())) self.client.request(katcp.Message.request("sensor-list", "an.int", mid=mid())) self.client.request(katcp.Message.request("sensor-list", "an.unknown", mid=mid())) self.client.request(katcp.Message.request("sensor-value", mid=mid())) self.client.request(katcp.Message.request("sensor-value", "an.int", mid=mid())) self.client.request(katcp.Message.request("sensor-value", "an.unknown", mid=mid())) self.client.blocking_request(katcp.Message.request("sensor-sampling", "an.int", mid=mid())) self.client.blocking_request(katcp.Message.request("sensor-sampling", "an.int", "differential", "2", mid=mid())) self.client.blocking_request(katcp.Message.request("sensor-sampling", mid=mid())) self.client.blocking_request(katcp.Message.request("sensor-sampling", "an.unknown", "auto", mid=mid())) self.client.blocking_request(katcp.Message.request("sensor-sampling", "an.int", "unknown", mid=mid())) self.server.log.trace("trace-msg") self.server.log.debug("debug-msg") self.server.log.info("info-msg") self.server.log.warn("warn-msg") self.server.log.error("error-msg") self.server.log.fatal("fatal-msg") time.sleep(0.1) msgs = self.client.messages() self.assertEqual(self.server.restart_queue.get_nowait(), self.server) self._assert_msgs_like(msgs, [ (r"#version device_stub-0.1", ""), (r"#build-state name-0.1", ""), (r"!watchdog[1] ok", ""), (r"!restart[2] ok", ""), (r"!log-level[3] ok warn", ""), (r"!log-level[4] ok trace", ""), (r"!log-level[5] fail Unknown\_logging\_level\_name\_'unknown'", ""), (r"#help[6] client-list", ""), (r"#help[6] halt", ""), (r"#help[6] help", ""), (r"#help[6] log-level", ""), (r"#help[6] new-command", ""), (r"#help[6] raise-exception", ""), (r"#help[6] raise-fail", ""), (r"#help[6] restart", ""), (r"#help[6] sensor-list", ""), (r"#help[6] sensor-sampling", ""), (r"#help[6] sensor-value", ""), (r"#help[6] slow-command", ""), (r"#help[6] watchdog", ""), (r"!help[6] ok 13", ""), (r"#help[7] watchdog", ""), (r"!help[7] ok 1", ""), (r"!help[8] fail", ""), (r"#client-list[9]", ""), (r"!client-list[9] ok 1", ""), (r"#sensor-list[10] an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list[10] ok 1", ""), (r"#sensor-list[11] an.int An\_Integer. count integer -5 5", ""), (r"!sensor-list[11] ok 1", ""), (r"!sensor-list[12] fail", ""), (r"#sensor-value[13] 12345000 1 an.int nominal 3", ""), (r"!sensor-value[13] ok 1", ""), (r"#sensor-value[14] 12345000 1 an.int nominal 3", ""), (r"!sensor-value[14] ok 1", ""), (r"!sensor-value[15] fail", ""), (r"!sensor-sampling[16] ok an.int none", ""), (r"#sensor-status 12345000 1 an.int nominal 3", ""), (r"!sensor-sampling[17] ok an.int differential 2", ""), (r"!sensor-sampling[18] fail No\_sensor\_name\_given.", ""), (r"!sensor-sampling[19] fail Unknown\_sensor\_name.", ""), (r"!sensor-sampling[20] fail Unknown\_strategy\_name.", ""), (r"#log trace", r"root trace-msg"), (r"#log debug", r"root debug-msg"), (r"#log info", r"root info-msg"), (r"#log warn", r"root warn-msg"), (r"#log error", r"root error-msg"), (r"#log fatal", r"root fatal-msg"), ]) def test_sensor_list_regex(self): reply, informs = self.client.blocking_request(katcp.Message.request("sensor-list", "/a.*/")) self._assert_msgs_equal(informs + [reply], [ r"#sensor-list an.int An\_Integer. count integer -5 5", r"!sensor-list ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request("sensor-list", "//")) self._assert_msgs_equal(informs + [reply], [ r"#sensor-list an.int An\_Integer. count integer -5 5", r"!sensor-list ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request("sensor-list", "/^int/")) self._assert_msgs_equal(informs + [reply], [ r"!sensor-list ok 0", ]) def test_sensor_value_regex(self): reply, informs = self.client.blocking_request(katcp.Message.request("sensor-value", "/a.*/")) self._assert_msgs_equal(informs + [reply], [ r"#sensor-value 12345000 1 an.int nominal 3", r"!sensor-value ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request("sensor-value", "//")) self._assert_msgs_equal(informs + [reply], [ r"#sensor-value 12345000 1 an.int nominal 3", r"!sensor-value ok 1", ]) reply, informs = self.client.blocking_request(katcp.Message.request("sensor-value", "/^int/")) self._assert_msgs_equal(informs + [reply], [ r"!sensor-value ok 0", ]) def test_halt_request(self): """Test halt request.""" self.client.request(katcp.Message.request("halt")) # hack to hide re-connect exception self.client.connect = lambda: None self.server.join() time.sleep(0.1) msgs = self.client.messages() self._assert_msgs_equal(msgs, [ r"#version device_stub-0.1", r"#build-state name-0.1", r"!halt ok", r"#disconnect Device\_server\_shutting\_down.", ]) def test_bad_handlers(self): """Test that bad request and inform handlers are picked up.""" try: class BadServer(katcp.DeviceServer): def request_baz(self, sock, msg): pass except AssertionError: pass else: self.fail("Server metaclass accepted missing request_ docstring.") try: class BadServer(katcp.DeviceServer): def inform_baz(self, sock, msg): pass except AssertionError: pass else: self.fail("Server metaclass accepted missing inform_ docstring.") class SortOfOkayServer(katcp.DeviceServer): request_bar = 1 inform_baz = 2 assert("bar" not in SortOfOkayServer._request_handlers) assert("baz" not in SortOfOkayServer._inform_handlers) def test_handler_exceptions(self): """Test handling of failure replies and other exceptions.""" self.client.request(katcp.Message.request("raise-exception")) self.client.request(katcp.Message.request("raise-fail")) time.sleep(0.1) msgs = self.client.messages() self._assert_msgs_like(msgs, [ (r"#version device_stub-0.1", ""), (r"#build-state name-0.1", ""), (r"!raise-exception fail Traceback", ""), (r"!raise-fail fail There\_was\_a\_problem\_with\_your\_request.", ""), ]) def test_stop_and_restart(self): """Test stopping and restarting the device server.""" self.server.stop(timeout=0.1) self.server.join(timeout=1.0) self.assertEqual(self.server._thread, None) self.assertFalse(self.server._running.isSet()) self.server.start(timeout=1.0) def test_bad_client_socket(self): """Test what happens when select is called on a dead client socket.""" # wait for client to arrive time.sleep(0.1) # close socket while the server isn't looking # then wait for the server to notice sock = self.server._socks[0] sock.close() time.sleep(0.75) # check that client was removed self.assertTrue(sock not in self.server._socks, "Expected %r to not be in %r" % (sock, self.server._socks)) def test_bad_server_socket(self): """Test what happens when select is called on a dead server socket.""" # wait for client to arrive time.sleep(0.1) # close socket while the server isn't looking # then wait for the server to notice sock = self.server._sock sockname = sock.getsockname() sock.close() time.sleep(0.75) # check that server restarted self.assertTrue(sock is not self.server._sock, "Expected %r to not be %r" % (sock, self.server._sock)) self.assertEqual(sockname, self.server._sock.getsockname()) def test_daemon_value(self): """Test passing in a daemon value to server start method.""" self.server.stop(timeout=0.1) self.server.join(timeout=1.0) self.server.start(timeout=0.1, daemon=True) self.assertTrue(self.server._thread.isDaemon()) def test_excepthook(self): """Test passing in an excepthook to server start method.""" exceptions = [] except_event = threading.Event() def excepthook(etype, value, traceback): """Keep track of exceptions.""" exceptions.append(etype) except_event.set() self.server.stop(timeout=0.1) self.server.join(timeout=1.5) self.server.start(timeout=0.1, excepthook=excepthook) # force exception by deleteing _running old_running = self.server._running try: del self.server._running except_event.wait(1.5) self.assertEqual(exceptions, [AttributeError]) finally: self.server._running = old_running # close socket -- server didn't shut down correctly self.server._sock.close() self.server.stop(timeout=0.1) self.server.join(timeout=1.5) except_event.clear() del exceptions[:] self.server.start(timeout=0.1, excepthook=excepthook) # force exception in sample reactor and check that it makes # it back up reactor = self.server._reactor old_stop = reactor._stopEvent try: del reactor._stopEvent reactor._wakeEvent.set() except_event.wait(0.1) self.assertEqual(exceptions, [AttributeError]) finally: reactor._stopEvent = old_stop # close socket -- server didn't shut down correctly self.server._sock.close() def test_sampling(self): """Test sensor sampling.""" self.client.request(katcp.Message.request("sensor-sampling", "an.int", "period", 100)) time.sleep(1.0) self.client.request(katcp.Message.request("sensor-sampling", "an.int", "none")) time.sleep(0.5) msgs = self.client.messages() updates = [x for x in msgs if x.name == "sensor-status"] others = [x for x in msgs if x.name != "sensor-status"] self.assertTrue(abs(len(updates) - 12) < 2, "Expected 12 informs, saw %d." % len(updates)) self._assert_msgs_equal(others, [ r"#version device_stub-0.1", r"#build-state name-0.1", r"!sensor-sampling ok an.int period 100", r"!sensor-sampling ok an.int none", ]) self.assertEqual(updates[0].arguments[1:], ["1", "an.int", "nominal", "3"]) def test_add_remove_sensors(self): """Test adding and removing sensors from a running device.""" an_int = self.server._sensors["an.int"] self.server.remove_sensor(an_int) self.server.add_sensor(an_int) self.test_sampling()