class test_ThreadSafeKATCPClientResourceWrapper(unittest.TestCase): def setUp(self): self.server = DeviceTestServer('', 0) start_thread_with_cleanup(self, self.server) self.ioloop_manager = ioloop_manager.IOLoopManager(managed_default=True) self.io_loop = self.ioloop_manager.get_ioloop() self.host, self.port = self.server.bind_address self.default_resource_spec = dict( name='thething', address=self.server.bind_address, controlled=True) self.client_resource = resource_client.KATCPClientResource( self.default_resource_spec) self.client_resource.set_ioloop(self.io_loop) self.io_loop.add_callback(self.client_resource.start) self.ioloop_thread_wrapper = resource_client.IOLoopThreadWrapper(self.io_loop) start_thread_with_cleanup(self, self.ioloop_manager, start_timeout=1) self.ioloop_thread_wrapper.default_timeout = 1 self.DUT = resource_client.ThreadSafeKATCPClientResourceWrapper( self.client_resource, self.ioloop_thread_wrapper) self.DUT.until_synced() def test_wrapped_timeout(self): self.assertEqual(self.client_resource.state, 'synced') # Test timeout self.ioloop_thread_wrapper.default_timeout = 0.001 t0 = time.time() with self.assertRaises(TimeoutError): self.DUT.until_state('disconnected') self.assertLess(time.time() - t0, 0.2) # Now make sure we can actualy still wait on the state self.ioloop_thread_wrapper.default_timeout = 1 self.server.stop() self.server.join() self.DUT.until_state('disconnected') self.assertEqual(self.client_resource.state, 'disconnected') self.server.start() self.DUT.until_state('synced') self.assertEqual(self.client_resource.state, 'synced') def test_request(self): reply = self.DUT.req.sensor_value('an.int') last_server_msg = self.server.messages[-1] self.assertTrue(reply.succeeded) self.assertEqual(str(last_server_msg), '?sensor-value[{}] an.int'.format(reply.reply.mid)) def test_sensor(self): server_sensor = self.server.get_sensor('an.int') reading = self.DUT.sensor.an_int.get_reading() self.assertEqual(reading.value, server_sensor.read().value) server_sensor.set_value(server_sensor.read().value + 5) reading = self.DUT.sensor.an_int.get_reading() self.assertEqual(reading.value, server_sensor.read().value)
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._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 TestDeviceClientIntegrated(unittest.TestCase, TestUtilMixin): def setUp(self): self.server = DeviceTestServer('', 0) start_thread_with_cleanup(self, self.server, start_timeout=1) host, port = self.server.bind_address self.client = katcp.DeviceClient(host, port) self.client.enable_thread_safety() start_thread_with_cleanup(self, self.client, start_timeout=1) self.client.wait_connected(timeout=1) def test_versions(self): """Test that the versions parameter is populated.""" preamble_done = self.client.handle_reply = WaitingMock() # Do a request and wait for it to be done so that we can be sure we received the # full connection-header before testing self.client.request(Message.request('watchdog')) preamble_done.assert_wait_call_count(1, timeout=1) versions = self.client.versions self.assertIn('katcp', ' '.join(versions['katcp-library'])) self.assertIn('device', ' '.join(versions['katcp-device'])) self.assertTrue(' '.join(versions['katcp-protocol'])) def test_request(self): """Test request method.""" self.assertTrue(self.client.wait_protocol(1)) self.client.send_request(Message.request("watchdog")) self.client._server_supports_ids = False with self.assertRaises(katcp.core.KatcpVersionError): self.client.send_request(Message.request("watchdog", mid=56)) self.client._server_supports_ids = True self.client.send_request(Message.request("watchdog", mid=55)) msgs = self.server.until_messages(2).result(timeout=1) self._assert_msgs_equal(msgs, [ r"?watchdog", r"?watchdog[55]", ]) def test_send_message(self): """Test send_message method.""" self.client.send_message(Message.inform("random-inform")) msgs = self.server.until_messages(1).result(timeout=1) self._assert_msgs_equal(msgs, [ r"#random-inform", ]) def test_stop_and_restart(self): """Test stopping and then restarting a client.""" self.client.wait_running(timeout=1) before_threads = threading.enumerate() self.client.stop(timeout=1) self.client.join(timeout=1) # Test that we have fewer threads than before self.assertLess(len(threading.enumerate()), len(before_threads)) self.assertFalse(self.client._running.isSet()) self.client.start(timeout=1) self.client.wait_running(timeout=1) # Now we should have the original number of threads again self.assertEqual(len(threading.enumerate()), len(before_threads)) def test_is_connected(self): """Test is_connected method.""" self.assertTrue(self.client.is_connected()) # Use client.notify_connected to synchronise to the disconnection disconnected = threading.Event() self.client.notify_connected = ( lambda connected: disconnected.set() if not connected else None) self.server.stop(timeout=1.) # Restart server during cleanup to keep teardown happy self.addCleanup(self.server.start) self.server.join(timeout=1.) # Wait for the client to be disconnected disconnected.wait(1.5) self.assertFalse(self.client.is_connected()) def test_wait_connected(self): """Test wait_connected method.""" start = time.time() # Ensure that we are connected at the start of the test self.assertTrue(self.client.wait_connected(1.0)) # Check that we did not wait more than the timeout. self.assertTrue(time.time() - start < 1.0) # Now we will cause the client to disconnect by stopping the server, and then # checking that wait_connected returns fails, and waits approximately the right # amount of time # Use client.notify_connected to synchronise to the disconnection disconnected = threading.Event() self.client.notify_connected = ( lambda connected: disconnected.set() if not connected else None) self.server.stop(timeout=0.1) self.server.join(timeout=1) # Restart server during cleanup to keep teardown happy self.addCleanup(self.server.start) # Wait for the client to be disconnected disconnected.wait(1) # Now check that wait_connected returns false start = time.time() self.assertFalse(self.client.wait_connected(0.1)) # And waited more or less the timeout. self.assertTrue(0.05 < time.time() - start <= 0.15) def test_bad_socket(self): """Test what happens when select is called on a dead socket.""" # wait for client to connect self.client.wait_connected(timeout=1) f = Future() def notify_connected(connected): if connected: f.set_result(connected) self.client.notify_connected = notify_connected # close stream while the client isn't looking # then wait for the client to notice stream = self.client._stream sockname = stream.socket.getpeername() self.client.ioloop.add_callback(stream.close) f.result(timeout=1.25) # check that client reconnected self.assertTrue(stream is not self.client._stream, "Expected %r to not be %r" % (stream, self.client._stream)) self.assertEqual(sockname, self.client._stream.socket.getpeername())
class TestInspectingClientAsyncStateCallback(tornado.testing.AsyncTestCase): longMessage = True maxDiff = None def setUp(self): super(TestInspectingClientAsyncStateCallback, self).setUp() self.server = DeviceTestServer('', 0) start_thread_with_cleanup(self, self.server, start_timeout=1) self.host, self.port = self.server.bind_address self.state_cb_future = tornado.concurrent.Future() self.client = InspectingClientAsync(self.host, self.port, ioloop=self.io_loop) # Set a short initial_resync timeout to make resync tests quick self.client.initial_resync_timeout = 0.001 self.client.set_state_callback(self._test_state_cb) self.done_state_cb_futures = [] self.cnt_state_cb_futures = collections.defaultdict(tornado.concurrent.Future) def _test_state_cb(self, state, model_changes): f = self.state_cb_future self.state_cb_future = tornado.concurrent.Future() self.done_state_cb_futures.append(f) num_calls = len(self.done_state_cb_futures) f.set_result((state, model_changes)) self.cnt_state_cb_futures[num_calls].set_result(None) @tornado.gen.coroutine def _check_cb_count(self, expected_count): """Let the ioloop run and assert that the callback has been called the expected number of times""" yield tornado.gen.moment self.assertEqual(len(self.done_state_cb_futures), expected_count) @tornado.testing.gen_test(timeout=1) def test_from_connect(self): # Hold back #version-connect informs num_calls_before = len(self.done_state_cb_futures) logger.debug('before starting client, num_calls_before:{}'.format(num_calls_before)) self.server.proceed_on_client_connect.clear() self.client.connect() state, model_changes = yield self.state_cb_future self.assertEqual(state, inspecting_client.InspectingClientStateType( connected=False, synced=False, model_changed=False, data_synced=False)) state, model_changes = yield self.state_cb_future self.assertEqual(state, inspecting_client.InspectingClientStateType( connected=True, synced=False, model_changed=False, data_synced=False)) self.assertIs(model_changes, None) # Due to the structure of the state loop the initial state may be sent # twice before we get here, and was the case for the initial # implementation. Changes made on 2015-01-26 caused it to happen only # once, hence + 1. If the implementation changes having + 2 would also # be OK. As, indeed, changes made on 2016-11-03 caused again :) yield self._check_cb_count(num_calls_before + 2) # Now let the server send #version-connect informs num_calls_before = len(self.done_state_cb_futures) self.server.ioloop.add_callback(self.server.proceed_on_client_connect.set) # We're expecting two calls hard on each other's heels, so lets wait for them yield self.cnt_state_cb_futures[num_calls_before + 2] # We expected two status callbacks, and no more after yield self._check_cb_count(num_calls_before + 2) state, model_changes = yield self.done_state_cb_futures[-2] state2, model_changes2 = yield self.done_state_cb_futures[-1] self.assertEqual(state, inspecting_client.InspectingClientStateType( connected=True, synced=False, model_changed=True, data_synced=True)) # Check that the expected model changes came from the callback self._test_expected_model_changes(model_changes) self.assertEqual(state2, inspecting_client.InspectingClientStateType( connected=True, synced=True, model_changed=False, data_synced=True)) self.assertEqual(model_changes2, None) def _test_expected_model_changes(self, model_changes): # Check that the model_changes reflect the sensors and requests of the # test sever (self.server) server_sensors = self.server._sensors.keys() server_requests = self.server._request_handlers.keys() self.assertEqual(model_changes, dict( sensors=dict(added=set(server_sensors), removed=set()), requests=dict(added=set(server_requests), removed=set()))) @tornado.testing.gen_test(timeout=1) def test_reconnect(self): self.client.connect() yield self.client.until_synced() yield tornado.gen.moment # Make sure the ioloop is 'caught up' # cause a disconnection and check that the callback is called num_calls_before = len(self.done_state_cb_futures) self.server.stop() self.server.join() state, model_changes = yield self.state_cb_future self.assertEqual(state, inspecting_client.InspectingClientStateType( connected=False, synced=False, model_changed=False, data_synced=False)) self.assertIs(model_changes, None) yield self._check_cb_count(num_calls_before + 1) @tornado.gen.coroutine def _test_inspection_error(self, break_var, break_message): # Test that the client retries if there is an error in the inspection # process setattr(self.server, break_var, break_message) self.client.connect() # Wait for the client to be connected yield self.client.until_connected() # Wait for the state loop to send another update or 2 yield self.state_cb_future state, _ = yield self.state_cb_future # Check that data is still not synced self.assertFalse(state.synced) self.assertFalse(state.data_synced) # Now fix the inspection request, client should sync up. setattr(self.server, break_var, False) # Check that the server's sensors and request are reflected in the model # changes. # changes_state = inspecting_client.InspectingClientStateType( connected=True, synced=False, model_changed=True, data_synced=True) yield self.client.until_state(changes_state) state, model_changes = self.done_state_cb_futures[-1].result() assert state == changes_state self._test_expected_model_changes(model_changes) yield self.client.until_synced() self.assertTrue(self.client.synced) @tornado.testing.gen_test(timeout=1) def test_help_inspection_error(self): yield self._test_inspection_error('break_help', 'Help is broken') @tornado.testing.gen_test(timeout=1) def test_sensor_list_inspection_error(self): yield self._test_inspection_error( 'break_sensor_list', 'Sensor-list is broken')
class TestDeviceClientIntegrated(unittest.TestCase, TestUtilMixin): def setUp(self): self.server = DeviceTestServer('', 0) start_thread_with_cleanup(self, self.server, start_timeout=1) host, port = self.server.bind_address self.client = katcp.DeviceClient(host, port) self.client.enable_thread_safety() start_thread_with_cleanup(self, self.client, start_timeout=1) self.client.wait_connected(timeout=1) def test_versions(self): """Test that the versions parameter is populated.""" preamble_done = self.client.handle_reply = WaitingMock() # Do a request and wait for it to be done so that we can be sure we received the # full connection-header before testing self.client.request(Message.request('watchdog')) preamble_done.assert_wait_call_count(1, timeout=1) versions = self.client.versions self.assertIn('katcp', ' '.join(versions['katcp-library'])) self.assertIn('device', ' '.join(versions['katcp-device'])) self.assertTrue(' '.join(versions['katcp-protocol'])) def test_request(self): """Test request method.""" self.assertTrue(self.client.wait_protocol(1)) self.client.send_request(Message.request("watchdog")) self.client._server_supports_ids = False with self.assertRaises(katcp.core.KatcpVersionError): self.client.send_request(Message.request("watchdog", mid=56)) self.client._server_supports_ids = True self.client.send_request(Message.request("watchdog", mid=55)) msgs = self.server.until_messages(2).result(timeout=1) self._assert_msgs_equal(msgs, [ r"?watchdog", r"?watchdog[55]", ]) def test_send_message(self): """Test send_message method.""" self.client.send_message(Message.inform("random-inform")) msgs = self.server.until_messages(1).result(timeout=1) self._assert_msgs_equal(msgs, [ r"#random-inform", ]) def test_stop_and_restart(self): """Test stopping and then restarting a client.""" self.client.wait_running(timeout=1) before_threads = threading.enumerate() self.client.stop(timeout=1) self.client.join(timeout=1) # Test that we have fewer threads than before self.assertLess(len(threading.enumerate()), len(before_threads)) self.assertFalse(self.client._running.isSet()) self.client.start(timeout=1) self.client.wait_running(timeout=1) # Now we should have the original number of threads again self.assertEqual(len(threading.enumerate()), len(before_threads)) def test_is_connected(self): """Test is_connected method.""" self.assertTrue(self.client.is_connected()) # Use client.notify_connected to synchronise to the disconnection disconnected = threading.Event() self.client.notify_connected = (lambda connected: disconnected.set() if not connected else None) self.server.stop(timeout=1.) # Restart server during cleanup to keep teardown happy self.addCleanup(self.server.start) self.server.join(timeout=1.) # Wait for the client to be disconnected disconnected.wait(1.5) self.assertFalse(self.client.is_connected()) def test_wait_connected(self): """Test wait_connected method.""" start = time.time() # Ensure that we are connected at the start of the test self.assertTrue(self.client.wait_connected(1.0)) # Check that we did not wait more than the timeout. self.assertTrue(time.time() - start < 1.0) # Now we will cause the client to disconnect by stopping the server, and then # checking that wait_connected returns fails, and waits approximately the right # amount of time # Use client.notify_connected to synchronise to the disconnection disconnected = threading.Event() self.client.notify_connected = (lambda connected: disconnected.set() if not connected else None) self.server.stop(timeout=0.1) self.server.join(timeout=1) # Restart server during cleanup to keep teardown happy self.addCleanup(self.server.start) # Wait for the client to be disconnected disconnected.wait(1) # Now check that wait_connected returns false start = time.time() self.assertFalse(self.client.wait_connected(0.1)) # And waited more or less the timeout. self.assertTrue(0.05 < time.time() - start <= 0.15) def test_bad_socket(self): """Test what happens when select is called on a dead socket.""" # wait for client to connect self.client.wait_connected(timeout=1) f = Future() def notify_connected(connected): if connected: f.set_result(connected) self.client.notify_connected = notify_connected # close stream while the client isn't looking # then wait for the client to notice stream = self.client._stream sockname = stream.socket.getpeername() self.client.ioloop.add_callback(stream.close) f.result(timeout=1.25) # check that client reconnected self.assertTrue( stream is not self.client._stream, "Expected %r to not be %r" % (stream, self.client._stream)) self.assertEqual(sockname, self.client._stream.socket.getpeername())
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 test_KATCPClientResource_IntegratedTimewarp(TimewarpAsyncTestCase): def setUp(self): super(test_KATCPClientResource_IntegratedTimewarp, self).setUp() self.server = DeviceTestServer('', 0) start_thread_with_cleanup(self, self.server) self.host, self.port = self.server.bind_address self.default_resource_spec = dict( name='thething', address=self.server.bind_address, controlled=True) @tornado.gen.coroutine def _get_DUT_and_sync(self, resource_spec): DUT = resource_client.KATCPClientResource(self.default_resource_spec) DUT.start() yield DUT.until_state('synced') raise tornado.gen.Return(DUT) @tornado.testing.gen_test def test_disconnect(self): # Test that a device disconnect / reconnect is correctly handled DUT = yield self._get_DUT_and_sync(self.default_resource_spec) initial_reqs = set(DUT.req) initial_sensors = set(DUT.sensor) self.server.stop() self.server.join(timeout=1) yield DUT.until_state('disconnected') # Test that requests fail rep = yield DUT.req.watchdog() self.assertFalse(rep.succeeded) # Restart device so that we can reconnect self.server.start() # timewarp beyond reconect delay self.set_ioloop_time(self.ioloop_time + 1) yield DUT.until_state('syncing') yield DUT.until_state('synced') # check that sensors / requests are unchanged self.assertEqual(set(DUT.req), initial_reqs) self.assertEqual(set(DUT.sensor), initial_sensors) # Now disconnect and change the device, to check that it is properly resynced. self.server.stop() self.server.join(timeout=1) yield DUT.until_state('disconnected') # Add a new request to the server def request_sparkling_new(self, req, msg): """A new command.""" return Message.reply(msg.name, "ok", "bling1", "bling2") self.server._request_handlers['sparkling-new'] = request_sparkling_new # Check that the request does not exist currently self.assertNotIn('sparkling_new', initial_reqs) # Add a new sensor to the server sensor = DeviceTestSensor(DeviceTestSensor.INTEGER, "another.int", "An Integer.", "count", [-5, 5], timestamp=self.io_loop.time(), status=DeviceTestSensor.NOMINAL, value=3) self.server.add_sensor(sensor) # Check that the sensor does not exist currently escaped_new_sensor = resource.escape_name(sensor.name) self.assertNotIn(resource.escape_name(sensor.name), initial_sensors) # Restart device so that we can reconnect self.server.start() # timewarp beyond reconect delay self.set_ioloop_time(self.ioloop_time + 1) yield DUT.until_state('syncing') yield DUT.until_state('synced') # check that sensors / requests are correctly updated self.assertEqual(set(DUT.req), initial_reqs | set(['sparkling_new'])) self.assertEqual(set(DUT.sensor), initial_sensors | set([escaped_new_sensor])) @tornado.testing.gen_test(timeout=1000) def test_set_sensor_sampling(self): self.server.stop() self.server.join() DUT = resource_client.KATCPClientResource(self.default_resource_spec) DUT.start() yield tornado.gen.moment test_strategy = ('period', '2.5') yield DUT.set_sensor_strategy('an_int', test_strategy) # Double-check that the sensor does not yet exist self.assertNotIn('an_int', DUT.sensor) self.server.start() self.server.wait_running(timeout=1) advancer = TimewarpAsyncTestCaseTimeAdvancer(self, quantum=0.55) advancer.start() yield DUT.until_synced() self.assertEqual(DUT.sensor.an_int.sampling_strategy, test_strategy) # Now call set_sensor_strategy with a different strategy and check that it is # applied to the real sensor new_test_strategy = ('event',) yield DUT.set_sensor_strategy('an_int', new_test_strategy) self.assertEqual(DUT.sensor.an_int.sampling_strategy, new_test_strategy) @tornado.testing.gen_test(timeout=1000) def test_set_sensor_listener(self): self.server.stop() self.server.join() resource_spec = self.default_resource_spec DUT = resource_client.KATCPClientResource(resource_spec) DUT.start() yield tornado.gen.moment test_listener1 = lambda *x : None test_listener2 = lambda *y : None DUT.set_sensor_listener('an_int', test_listener1) # Double-check that the sensor does not yet exist self.assertNotIn('an_int', DUT.sensor) self.server.start() self.server.wait_running(timeout=1) advancer = TimewarpAsyncTestCaseTimeAdvancer(self, quantum=0.55) advancer.start() yield DUT.until_synced() self.assertTrue(DUT.sensor.an_int.is_listener, test_listener1) # Now call set_sensor_lister with a different listener and check that it is # also subscribed DUT.set_sensor_listener('an_int', test_listener2) self.assertTrue(DUT.sensor.an_int.is_listener, test_listener2)
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 TestInspectingClientAsyncStateCallback(tornado.testing.AsyncTestCase): def setUp(self): super(TestInspectingClientAsyncStateCallback, self).setUp() self.server = DeviceTestServer('', 0) start_thread_with_cleanup(self, self.server, start_timeout=1) self.host, self.port = self.server.bind_address self.state_cb_future = tornado.concurrent.Future() self.client = InspectingClientAsync(self.host, self.port, ioloop=self.io_loop) self.client.set_state_callback(self._test_state_cb) self.done_state_cb_futures = [] self.cnt_state_cb_futures = collections.defaultdict(tornado.concurrent.Future) def _test_state_cb(self, state, model_changes): f = self.state_cb_future self.state_cb_future = tornado.concurrent.Future() self.done_state_cb_futures.append(f) num_calls = len(self.done_state_cb_futures) f.set_result((state, model_changes)) self.cnt_state_cb_futures[num_calls].set_result(None) @tornado.gen.coroutine def _check_no_cb(self, no_expected): """Let the ioloop run and assert that the callback was not called""" yield tornado.gen.moment self.assertEqual(len(self.done_state_cb_futures), no_expected) @tornado.testing.gen_test(timeout=1) def test_from_connect(self): # Hold back #version-connect informs num_calls_before = len(self.done_state_cb_futures) logger.debug('before starting client, num_calls_before:{}'.format(num_calls_before)) self.server.proceed_on_client_connect.clear() self.client.connect() state, model_changes = yield self.state_cb_future self.assertEqual(state, inspecting_client.InspectingClientStateType( connected=True, synced=False, model_changed=False, data_synced=False)) self.assertIs(model_changes, None) # Due to the structure of the state loop the initial state may be sent twice # before we get her, and was the case for the initial implementation. Changes made # on 2015-01-26 caused it to happy only once, hence + 1. If the implementation # changes having + 2 would also be OK. yield self._check_no_cb(num_calls_before + 1) # Now let the server send #version-connect informs num_calls_before = len(self.done_state_cb_futures) self.server.ioloop.add_callback(self.server.proceed_on_client_connect.set) # We're expecting two calls hard on each other's heels, so lets wait for them yield self.cnt_state_cb_futures[num_calls_before + 2] # We expected two status callbacks, and no more after yield self._check_no_cb(num_calls_before + 2) state, model_changes = yield self.done_state_cb_futures[-2] state2, model_changes2 = yield self.done_state_cb_futures[-1] self.assertEqual(state, inspecting_client.InspectingClientStateType( connected=True, synced=False, model_changed=True, data_synced=True)) server_sensors = self.server._sensors.keys() server_requests = self.server._request_handlers.keys() self.assertEqual(model_changes, dict( sensors=dict(added=set(server_sensors), removed=set()), requests=dict(added=set(server_requests), removed=set()))) self.assertEqual(state2, inspecting_client.InspectingClientStateType( connected=True, synced=True, model_changed=False, data_synced=True)) self.assertEqual(model_changes2, None) @tornado.testing.gen_test(timeout=1) def test_reconnect(self): self.client.connect() yield self.client.until_synced() yield tornado.gen.moment # Make sure the ioloop is 'caught up' # cause a disconnection and check that the callback is called num_calls_before = len(self.done_state_cb_futures) self.server.stop() self.server.join() state, model_changes = yield self.state_cb_future self.assertEqual(state, inspecting_client.InspectingClientStateType( connected=False, synced=False, model_changed=False, data_synced=False)) self.assertIs(model_changes, None) yield self._check_no_cb(num_calls_before + 1)
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 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 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()