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)
示例#2
0
class test_AsyncClientIntegratedBase(TimewarpAsyncTestCase):
    def setUp(self):
        super(test_AsyncClientIntegratedBase, self).setUp()
        self.server = DeviceTestServer('localhost', 0)
        self.server.set_ioloop(self.io_loop)
        self.server.set_concurrency_options(thread_safe=False, handler_thread=False)
        self.server.start()

        host, port = self.server.bind_address
        logger.info('host, port: {}:{}'.format(host, port))
        self.client = katcp.CallbackClient(host, port)
        self.client.set_ioloop(self.io_loop)
示例#3
0
class test_AsyncClientIntegrated(tornado.testing.AsyncTestCase, TestUtilMixin):
    def setUp(self):
        super(test_AsyncClientIntegrated, self).setUp()
        self.server = DeviceTestServer('', 0)
        self.server.set_ioloop(self.io_loop)
        self.server.set_concurrency_options(thread_safe=False, handler_thread=False)
        self.server.start()

        host, port = self.server.bind_address
        self.client = katcp.CallbackClient(host, port)
        self.client.set_ioloop(self.io_loop)
        self.client.start()

    @tornado.testing.gen_test
    def test_future_request_simple(self):
        yield self.client.until_connected()
        reply, informs = yield self.client.future_request(Message.request('watchdog'))
        self.assertEqual(len(informs), 0)
        self.assertEqual(reply.name, "watchdog")
        self.assertEqual(reply.arguments, ["ok"])

    @tornado.testing.gen_test
    def test_future_request_with_informs(self):
        yield self.client.until_connected()
        reply, informs = yield self.client.future_request(Message.request('help'))
        self.assertEqual(reply.name, "help")
        self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES])
        self.assertEqual(len(informs), NO_HELP_MESSAGES)

    @tornado.testing.gen_test
    def test_disconnect_cleanup(self):
        yield self.client.until_protocol()
        mid = 55
        future_reply = self.client.future_request(Message.request(
            'slow-command', 1, mid=mid))
        # Force a disconnect
        self.client._disconnect()
        reply, informs = yield future_reply
        self.assertEqual(reply, Message.reply(
            'slow-command', 'fail', 'Connection closed before reply was received',
            mid=mid))

    @tornado.testing.gen_test
    def test_stop_cleanup(self):
        yield self.client.until_protocol()
        mid = 564
        future_reply = self.client.future_request(Message.request(
            'slow-command', 1, mid=mid))
        # Stop client
        self.client.stop()
        reply, informs = yield future_reply
        self.assertEqual(reply, Message.reply(
            'slow-command', 'fail', 'Client stopped before reply was received', mid=mid))
示例#4
0
class test_AsyncClientIntegratedBase(TimewarpAsyncTestCase):
    def setUp(self):
        super(test_AsyncClientIntegratedBase, self).setUp()
        self.server = DeviceTestServer('localhost', 0)
        self.server.set_ioloop(self.io_loop)
        self.server.set_concurrency_options(thread_safe=False,
                                            handler_thread=False)
        self.server.start()

        host, port = self.server.bind_address
        logger.info('host, port: {}:{}'.format(host, port))
        self.client = katcp.CallbackClient(host, port)
        self.client.set_ioloop(self.io_loop)
示例#5
0
class test_AsyncClientTimeoutsIntegrated(TimewarpAsyncTestCase):
    def setUp(self):
        super(test_AsyncClientTimeoutsIntegrated, self).setUp()
        self.server = DeviceTestServer('', 0)
        self.server.set_ioloop(self.io_loop)
        self.server.set_concurrency_options(thread_safe=False, handler_thread=False)
        self.server.start()

        host, port = self.server.bind_address
        self.client = katcp.CallbackClient(host, port)
        self.client.set_ioloop(self.io_loop)
        self.client.start()


    @tornado.testing.gen_test(timeout=10)
    # We are using time-warping, so the timeout should be longer than the fake-duration
    def test_future_request_default_timeout(self):
        # Test the default timeout of 5s
        yield self._test_timeout(5)

    @tornado.testing.gen_test()
    def test_future_request_change_default_timeout(self):
        self.client._request_timeout = 3
        yield self._test_timeout(3)

    @tornado.testing.gen_test()
    def test_future_request_request_timeout(self):
        yield self._test_timeout(1, set_request_timeout=True)

    @gen.coroutine
    def _test_timeout(self, timeout, set_request_timeout=False):
        request_timeout = timeout if set_request_timeout else None
        yield self.client.until_connected()
        t0 = self.io_loop.time()
        reply_future = self.client.future_request(Message.request('slow-command', timeout + 1),
                                                  timeout=request_timeout)
        # Warp to just before the timeout expires and check that the future is not yet
        # resolved
        self.set_ioloop_time(t0 + timeout*0.9999)
        yield self.wake_ioloop()
        self.assertFalse(reply_future.done())
        # Warp to just after the timeout expires, and check that it gives us a timeout
        # error reply
        self.set_ioloop_time(t0 + timeout*1.0001)
        yield self.wake_ioloop()
        self.assertTrue(reply_future.done())
        reply, informs = reply_future.result()
        self.assertFalse(reply.reply_ok())
        self.assertRegexpMatches(
            reply.arguments[1],
            r"Request slow-command timed out after .* seconds.")
示例#6
0
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:
示例#7
0
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))
示例#8
0
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"))
示例#9
0
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
示例#10
0
class TestCallbackClient(unittest.TestCase, TestUtilMixin):

    def setUp(self):
        self.addCleanup(self.stop_server_client)
        self.server = DeviceTestServer('', 0)
        self.server.start(timeout=0.1)

        host, port = self.server.bind_address

        self.client = katcp.CallbackClient(host, port)
        self.client.start(timeout=1)
        self.assertTrue(self.client.wait_protocol(timeout=1))

    def stop_server_client(self):
        if hasattr(self, 'client') and self.client.running():
            self.client.stop()
            self.client.join()
        if hasattr(self, 'server') and self.server.running():
            self.server.stop()
            self.server.join()

    def test_callback_request(self):
        """Test callback request."""

        watchdog_replies = []
        watchdog_replied = threading.Event()

        def watchdog_reply(reply):
            self.assertEqual(reply.name, "watchdog")
            self.assertEqual(reply.arguments, ["ok"])
            watchdog_replies.append(reply)
            watchdog_replied.set()

        self.assertTrue(self.client.wait_protocol(0.5))
        self.client.callback_request(
            Message.request("watchdog"),
            reply_cb=watchdog_reply,
        )

        watchdog_replied.wait(0.5)
        self.assertTrue(watchdog_replies)

        help_replies = []
        help_informs = []
        help_replied = threading.Event()

        def help_reply(reply):
            self.assertEqual(reply.name, "help")
            self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES])
            self.assertEqual(len(help_informs), int(reply.arguments[1]))
            help_replies.append(reply)
            help_replied.set()

        def help_inform(inform):
            self.assertEqual(inform.name, "help")
            self.assertEqual(len(inform.arguments), 2)
            help_informs.append(inform)


        self.client.callback_request(
            Message.request("help"),
            reply_cb=help_reply,
            inform_cb=help_inform,
        )

        help_replied.wait(1)
        self.assertTrue(help_replied.isSet())
        help_replied.clear()
        help_replied.wait(0.05)   # Check if (unwanted) late help replies arrive
        self.assertFalse(help_replied.isSet())
        self.assertEqual(len(help_replies), 1)
        self.assertEqual(len(help_informs), NO_HELP_MESSAGES)

    def test_callback_request_mid(self):
        ## Test that the client does the right thing with message identifiers

        # Wait for the client to detect the server protocol. Server should
        # support message identifiers
        self.assertTrue(self.client.wait_protocol(0.2))
        # Replace send_message so that we can check the message
        self.client.send_message = mock.Mock(wraps=self.client.send_message)

        # By default message identifiers should be enabled, and should start
        # counting at 1
        cb = counting_callback(number_of_calls=3)(lambda *x: x)
        self.client.callback_request(Message.request('watchdog'), reply_cb=cb)
        self.client.callback_request(Message.request('watchdog'), reply_cb=cb)
        self.client.callback_request(Message.request('watchdog'), reply_cb=cb)
        cb.assert_wait()
        # Extract Message object .mid attributes from the mock calls to
        # send_message
        mids = [args[0].mid              # args[0] should be the Message() object
                for args, kwargs in self.client.send_message.call_args_list]
        self.assertEqual(mids, ['1','2','3'])
        self.client.send_message.reset_mock()

        # Explicitly ask for no mid to be used
        cb = counting_callback(number_of_calls=1)(lambda *x: x)
        self.client.callback_request(
            Message.request('watchdog'), use_mid=False, reply_cb=cb)
        cb.assert_wait()
        mid = self.client.send_message.call_args[0][0].mid
        self.assertEqual(mid, None)

        # Ask for a specific mid to be used
        self.client.send_message.reset_mock()
        cb = counting_callback(number_of_calls=1)(lambda *x: x)
        self.client.callback_request(
            Message.request('watchdog', mid=42), reply_cb=cb)
        cb.assert_wait()
        mid = self.client.send_message.call_args[0][0].mid
        self.assertEqual(mid, '42')

        ## Check situation for a katcpv4 server
        self.client._server_supports_ids = False

        # Should fail if an mid is passed
        reply = [None]
        @counting_callback()
        def cb(msg):
            reply[0] = msg
        self.client.callback_request(
            Message.request('watchdog', mid=42), reply_cb=cb)
        cb.assert_wait()
        self.assertFalse(reply[0].reply_ok())

        # Should fail if an mid is requested
        reply = [None]
        cb.reset()
        self.client.callback_request(
            Message.request('watchdog'), use_mid=True, reply_cb=cb)
        cb.assert_wait()
        self.assertFalse(reply[0].reply_ok())

        # Should use no mid by default
        self.client.send_message.reset_mock()
        cb = counting_callback(number_of_calls=1)(lambda *x: x)
        self.client.callback_request(Message.request('watchdog'), reply_cb=cb)
        cb.assert_wait(timeout=1)
        mid = self.client.send_message.call_args[0][0].mid
        self.assertEqual(mid, None)

    def test_no_callback(self):
        """Test request without callback."""

        help_messages = []
        help_completed = threading.Event()

        def handle_help_message(client, msg):
            help_messages.append(msg)
            if msg.mtype == msg.REPLY:
                help_completed.set()

        self.client._inform_handlers["help"] = handle_help_message
        self.client._reply_handlers["help"] = handle_help_message
        # Set client._last_msg_id so we know that the ID is. Should be
        # _last_msg_id + 1
        self.client._last_msg_id = 0
        self.assertTrue(self.client.wait_protocol(0.2))
        self.client.callback_request(Message.request("help"))
        help_completed.wait(1)
        self.assertTrue(help_completed.isSet())

        self._assert_msgs_like(help_messages,
            [("#help[1] ", "")] * NO_HELP_MESSAGES +
            [("!help[1] ok %d" % NO_HELP_MESSAGES, "")])

    def test_timeout(self):
        self._test_timeout()

    def test_timeout_nomid(self, use_mid=False):
        self._test_timeout(use_mid=False)

    def _test_timeout(self, use_mid=None):
        """Test requests that timeout."""

        replies = []
        replied = threading.Event()
        informs = []
        timeout = 0.001

        @counting_callback()
        def reply_cb(msg):
            replies.append(msg)
            replied.set()

        def inform_cb(msg):
            informs.append(msg)

        self.assertTrue(self.client.wait_protocol(0.2))
        self.client.callback_request(
            Message.request("slow-command", "0.1"),
            use_mid=use_mid,
            reply_cb=reply_cb,
            inform_cb=inform_cb,
            timeout=timeout,
        )

        reply_cb.assert_wait(1)
        self.client.request(Message.request('cancel-slow-command'))
        msg = replies[0]
        self.assertEqual(msg.name, "slow-command")
        self.assertFalse(msg.reply_ok())
        self.assertRegexpMatches(msg.arguments[1],
                                 r"Request slow-command timed out after 0\..* seconds.")
        self.assertEqual(len(remove_version_connect(informs)), 0)
        self.assertEqual(len(replies), 1)

        del replies[:]
        del informs[:]
        reply_cb.reset()

        # test next request succeeds
        self.client.callback_request(
            Message.request("slow-command", "0.05"),
            reply_cb=reply_cb,
            inform_cb=inform_cb,
        )

        reply_cb.assert_wait()
        self.assertEqual(len(replies), 1)
        self.assertEqual(len(informs), 0)
        self.assertEqual([msg.name for msg in replies + informs],
                         ["slow-command"] * len(replies + informs))
        self.assertEqual([msg.arguments for msg in replies], [["ok"]])

    def test_timeout_nocb(self):
        """Test requests that timeout with no callbacks."""
        # Included to test https://katfs.kat.ac.za/mantis/view.php?id=1722
        # Situation can occur during a race between the timeout handler and the
        # receipt of a reply -- the reply can arrive after the timeout timer has
        # expired but before the request has been popped off the stack with
        # client._pop_async_request(). The normal request handler then pops off
        # the request first, resulting in the timeout handler getting a bunch of
        # None's. It should handle this gracefully.

        # Running the handler with a fake msg_id should have the same result as
        # running it after a real request has already been popped. The expected
        # result is that no assertions are raised.

        # NM 2014-09-26: This is probably no longer an issue with the tornado-based client
        # implementatin, but leaving the test for good measure
        f = Future()
        @gen.coroutine
        def cb():
            self.client._handle_timeout('fake_msg_id', time.time())
        self.client.ioloop.add_callback(lambda : gen.chain_future(cb(), f))
        f.result(timeout=1)


    def test_user_data(self):
        """Test callbacks with user data."""
        help_replies = []
        help_informs = []
        done = threading.Event()

        def help_reply(reply, x, y):
            self.assertEqual(reply.name, "help")
            self.assertEqual(x, 5)
            self.assertEqual(y, "foo")
            help_replies.append(reply)
            done.set()

        def help_inform(inform, x, y):
            self.assertEqual(inform.name, "help")
            self.assertEqual(x, 5)
            self.assertEqual(y, "foo")
            help_informs.append(inform)

        self.client.callback_request(
            Message.request("help"),
            reply_cb=help_reply,
            inform_cb=help_inform,
            user_data=(5, "foo"))

        done.wait(1)
        # Wait a bit longer to see if spurious replies arrive
        time.sleep(0.01)
        self.assertEqual(len(help_replies), 1)
        self.assertEqual(len(remove_version_connect(help_informs)),
                         NO_HELP_MESSAGES)

    def test_fifty_thread_mayhem(self):
        """Test using callbacks from fifty threads simultaneously."""
        num_threads = 50
        # map from thread_id -> (replies, informs)
        results = {}
        # list of thread objects
        threads = []

        def reply_cb(reply, thread_id):
            results[thread_id][0].append(reply)
            results[thread_id][2].set()

        def inform_cb(inform, thread_id):
            results[thread_id][1].append(inform)

        def worker(thread_id, request):
            self.client.callback_request(
                request.copy(),
                reply_cb=reply_cb,
                inform_cb=inform_cb,
                user_data=(thread_id,),
            )

        request = Message.request("help")

        for thread_id in range(num_threads):
            results[thread_id] = ([], [], threading.Event())

        for thread_id in range(num_threads):
            thread = threading.Thread(target=worker, args=(thread_id, request))
            threads.append(thread)

        for thread in threads:
            thread.start()

        for thread in threads:
            thread.join()

        for thread_id in range(num_threads):
            replies, informs, done = results[thread_id]
            done.wait(5.0)
            self.assertTrue(done.isSet())
            self.assertEqual(len(replies), 1)
            self.assertEqual(replies[0].arguments[0], "ok")
            informs = remove_version_connect(informs)
            if len(informs) != NO_HELP_MESSAGES:
                print thread_id, len(informs)
                print [x.arguments[0] for x in informs]
            self.assertEqual(len(informs), NO_HELP_MESSAGES)

    def test_blocking_request(self):
        """Test the callback client's blocking request."""
        reply, informs = self.client.blocking_request(
            Message.request("help"),
        )

        self.assertEqual(reply.name, "help")
        self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES])
        self.assertEqual(len(remove_version_connect(informs)), NO_HELP_MESSAGES)

        reply, informs = self.client.blocking_request(
            Message.request("slow-command", "0.5"),
            timeout=0.001)

        self.assertEqual(reply.name, "slow-command")
        self.assertEqual(reply.arguments[0], "fail")
        self.assertRegexpMatches(
            reply.arguments[1],
            r"Request slow-command timed out after 0\..* seconds.")

    def test_blocking_request_mid(self):
        ## Test that the blocking client does the right thing with message
        ## identifiers

        # Wait for the client to detect the server protocol. Server should
        # support message identifiers
        self.assertTrue(self.client.wait_protocol(1))
        # Replace send_message so that we can check the message
        self.client.send_message = mock.Mock()

        # By default message identifiers should be enabled, and should start
        # counting at 1
        self.client.blocking_request(Message.request('watchdog'), timeout=0)
        self.client.blocking_request(Message.request('watchdog'), timeout=0)
        self.client.blocking_request(Message.request('watchdog'), timeout=0)
        # Extract Message object .mid attributes from the mock calls to
        # send_message
        mids = [args[0].mid              # arg[0] should be the Message() object
                for args, kwargs in self.client.send_message.call_args_list]
        self.assertEqual(mids, ['1','2','3'])
        self.client.send_message.reset_mock()

        # Explicitly ask for no mid to be used
        self.client.blocking_request(Message.request('watchdog'),
                                     use_mid=False, timeout=0)
        mid = self.client.send_message.call_args[0][0].mid
        self.assertEqual(mid, None)

        # Ask for a specific mid to be used
        self.client.send_message.reset_mock()
        self.client.blocking_request(Message.request(
            'watchdog', mid=42), timeout=0)
        mid = self.client.send_message.call_args[0][0].mid
        self.assertEqual(mid, '42')

        ## Check situation for a katcpv4 server
        self.client._server_supports_ids = False

        # Should fail if an mid is passed
        reply, inform = self.client.blocking_request(Message.request(
                'watchdog', mid=42), timeout=0)
        self.assertFalse(reply.reply_ok())

        # Should fail if an mid is requested
        reply, inform = self.client.blocking_request(
            Message.request('watchdog'), use_mid=True, timeout=0)
        self.assertFalse(reply.reply_ok())

        # Should use no mid by default
        self.client.send_message.reset_mock()
        self.client.blocking_request(Message.request(
            'watchdog'), timeout=0)
        mid = self.client.send_message.call_args[0][0].mid
        self.assertEqual(mid, None)

    def test_request_fail_on_raise(self):
        """Test that the callback is called even if send_message raises
           KatcpClientError."""
        def raise_error(msg, timeout=None):
            raise katcp.KatcpClientError("Error %s" % msg.name)
        self.client.send_message = raise_error

        replies = []

        @counting_callback()
        def reply_cb(msg):
            replies.append(msg)

        self.client.callback_request(Message.request("foo"),
            reply_cb=reply_cb,
        )

        reply_cb.assert_wait()
        self.assertEqual(len(replies), 1)
        self.assertEqual(replies[0].name, "foo")
        self.assertEqual(replies[0].arguments, ["fail", "Error foo"])

    def test_stop_cleanup(self):
        self.client.wait_protocol(timeout=1)
        mid = 56
        future_reply = Future()
        self.client.ioloop.add_callback(
            lambda : gen.chain_future(self.client.future_request(Message.request(
                'slow-command', 1, mid=mid)), future_reply))
        # Force a disconnect
        self.client.stop()
        reply, informs = future_reply.result(timeout=1)
        self.assertEqual(reply, Message.reply(
            'slow-command', 'fail', 'Client stopped before reply was received', mid=mid))
class TestCallbackClient(unittest.TestCase, TestUtilMixin):
    def setUp(self):
        self.addCleanup(self.stop_server_client)
        self.server = DeviceTestServer('', 0)
        self.server.start(timeout=0.1)

        host, port = self.server._sock.getsockname()

        self.client = katcp.CallbackClient(host, port)
        self.client.start(timeout=0.1)
        self.assertTrue(self.client.wait_protocol(timeout=1))

    def stop_server_client(self):
        if hasattr(self, 'client') and self.client.running():
            self.client.stop()
            self.client.join()
        if hasattr(self, 'server') and self.server.running():
            self.server.stop()
            self.server.join()

    def test_callback_request(self):
        """Test callback request."""

        watchdog_replies = []
        watchdog_replied = threading.Event()

        def watchdog_reply(reply):
            self.assertEqual(reply.name, "watchdog")
            self.assertEqual(reply.arguments, ["ok"])
            watchdog_replies.append(reply)
            watchdog_replied.set()

        self.assertTrue(self.client.wait_protocol(0.2))
        self.client.callback_request(
            katcp.Message.request("watchdog"),
            reply_cb=watchdog_reply,
        )

        watchdog_replied.wait(0.2)
        self.assertTrue(watchdog_replies)

        help_replies = []
        help_informs = []
        help_replied = threading.Event()

        def help_reply(reply):
            self.assertEqual(reply.name, "help")
            self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES])
            self.assertEqual(len(help_informs), int(reply.arguments[1]))
            help_replies.append(reply)
            help_replied.set()

        def help_inform(inform):
            self.assertEqual(inform.name, "help")
            self.assertEqual(len(inform.arguments), 2)
            help_informs.append(inform)

        self.client.callback_request(
            katcp.Message.request("help"),
            reply_cb=help_reply,
            inform_cb=help_inform,
        )

        help_replied.wait(1)
        self.assertTrue(help_replied.isSet())
        help_replied.clear()
        help_replied.wait(0.05)  # Check if (unwanted) late help replies arrive
        self.assertFalse(help_replied.isSet())
        self.assertEqual(len(help_replies), 1)
        self.assertEqual(len(help_informs), NO_HELP_MESSAGES)

    def test_callback_request_mid(self):
        ## Test that the client does the right thing with message identifiers

        # Wait for the client to detect the server protocol. Server should
        # support message identifiers
        self.assertTrue(self.client.wait_protocol(0.2))
        # Replace send_message so that we can check the message
        self.client.send_message = mock.Mock()

        # By default message identifiers should be enabled, and should start
        # counting at 1
        self.client.callback_request(katcp.Message.request('watchdog'))
        self.client.callback_request(katcp.Message.request('watchdog'))
        self.client.callback_request(katcp.Message.request('watchdog'))
        # Extract katcp.Message object .mid attributes from the mock calls to
        # send_message
        mids = [
            args[0].mid  # arg[0] should be the Message() object
            for args, kwargs in self.client.send_message.call_args_list
        ]
        self.assertEqual(mids, ['1', '2', '3'])
        self.client.send_message.reset_mock()

        # Explicitly ask for no mid to be used
        self.client.callback_request(katcp.Message.request('watchdog'),
                                     use_mid=False)
        mid = self.client.send_message.call_args[0][0].mid
        self.assertEqual(mid, None)

        # Ask for a specific mid to be used
        self.client.send_message.reset_mock()
        self.client.callback_request(katcp.Message.request('watchdog', mid=42))
        mid = self.client.send_message.call_args[0][0].mid
        self.assertEqual(mid, '42')

        ## Check situation for a katcpv4 server
        self.client._server_supports_ids = False

        # Should fail if an mid is passed
        with self.assertRaises(katcp.core.KatcpVersionError):
            self.client.callback_request(
                katcp.Message.request('watchdog', mid=42))

        # Should fail if an mid is requested
        with self.assertRaises(katcp.core.KatcpVersionError):
            self.client.callback_request(katcp.Message.request('watchdog'),
                                         use_mid=True)

        # Should use no mid by default
        self.client.send_message.reset_mock()
        self.client.callback_request(katcp.Message.request('watchdog'))
        mid = self.client.send_message.call_args[0][0].mid
        self.assertEqual(mid, None)

    def test_no_callback(self):
        """Test request without callback."""

        help_messages = []
        help_completed = threading.Event()

        def handle_help_message(client, msg):
            help_messages.append(msg)
            if msg.mtype == msg.REPLY:
                help_completed.set()

        self.client._inform_handlers["help"] = handle_help_message
        self.client._reply_handlers["help"] = handle_help_message
        # Set client._last_msg_id so we know that the ID is. Should be
        # _last_msg_id + 1
        self.client._last_msg_id = 0
        self.assertTrue(self.client.wait_protocol(0.2))
        self.client.callback_request(katcp.Message.request("help"))
        help_completed.wait(1)
        self.assertTrue(help_completed.isSet())

        self._assert_msgs_like(help_messages,
                               [("#help[1] ", "")] * NO_HELP_MESSAGES +
                               [("!help[1] ok %d" % NO_HELP_MESSAGES, "")])

    def test_no_timeout(self):
        self.client._request_timeout = None
        replies = []
        informs = []
        replied = threading.Event()

        def reply_handler(reply):
            replies.append(reply)
            replied.set()

        def inform_handler(reply):
            informs.append(reply)

        with mock.patch('katcp.client.threading.Timer') as MockTimer:
            self.client.callback_request(katcp.Message.request("help"),
                                         reply_cb=reply_handler,
                                         inform_cb=inform_handler)
        replied.wait(1)
        # With no timeout no Timer object should have been instantiated
        self.assertEqual(MockTimer.call_count, 0)
        self.assertEqual(len(replies), 1)
        self.assertEqual(len(remove_version_connect(informs)),
                         NO_HELP_MESSAGES)

    def test_timeout(self):
        self._test_timeout()

    def test_timeout_nomid(self, use_mid=False):
        self._test_timeout(use_mid=False)

    def _test_timeout(self, use_mid=None):
        """Test requests that timeout."""

        replies = []
        replied = threading.Event()
        informs = []
        timeout = 0.001

        @counting_callback()
        def reply_cb(msg):
            replies.append(msg)
            replied.set()

        def inform_cb(msg):
            informs.append(msg)

        self.assertTrue(self.client.wait_protocol(0.2))
        self.client.callback_request(
            katcp.Message.request("slow-command", "0.1"),
            use_mid=use_mid,
            reply_cb=reply_cb,
            inform_cb=inform_cb,
            timeout=timeout,
        )

        reply_cb.assert_wait(1)
        self.assertEqual(len(replies), 1)
        self.assertEqual([msg.name for msg in replies], ["slow-command"])
        self.assertEqual(
            [msg.arguments for msg in replies],
            [["fail", "Timed out after %f seconds" % timeout]])
        self.assertEqual(len(remove_version_connect(informs)), 0)

        del replies[:]
        del informs[:]
        reply_cb.reset()

        # test next request succeeds
        self.client.callback_request(
            katcp.Message.request("slow-command", "0.05"),
            reply_cb=reply_cb,
            inform_cb=inform_cb,
        )

        reply_cb.assert_wait(1)
        self.assertEqual(len(replies), 1)
        self.assertEqual(len(informs), 0)
        self.assertEqual([msg.name for msg in replies + informs],
                         ["slow-command"] * len(replies + informs))
        self.assertEqual([msg.arguments for msg in replies], [["ok"]])

    def test_timeout_nocb(self):
        """Test requests that timeout with no callbacks."""
        # Included to test https://katfs.kat.ac.za/mantis/view.php?id=1722
        # Situation can occur during a race between the timeout handler and the
        # receipt of a reply -- the reply can arrive after the timeout timer has
        # expired but before the request has been popped off the stack with
        # client._pop_async_request(). The normal request handler then pops off
        # the request first, resulting in the timeout handler getting a bunch of
        # None's. It should handle this gracefully.

        # Running the handler with a fake msg_id should have the same result as
        # running it after a real request has already been popped. The expected
        # result is that no assertions are raised.
        self.client._handle_timeout('fake_msg_id')

    def test_user_data(self):
        """Test callbacks with user data."""
        help_replies = []
        help_informs = []
        done = threading.Event()

        def help_reply(reply, x, y):
            self.assertEqual(reply.name, "help")
            self.assertEqual(x, 5)
            self.assertEqual(y, "foo")
            help_replies.append(reply)
            done.set()

        def help_inform(inform, x, y):
            self.assertEqual(inform.name, "help")
            self.assertEqual(x, 5)
            self.assertEqual(y, "foo")
            help_informs.append(inform)

        self.client.callback_request(katcp.Message.request("help"),
                                     reply_cb=help_reply,
                                     inform_cb=help_inform,
                                     user_data=(5, "foo"))

        done.wait(1)
        # Wait a bit longer to see if spurious replies arrive
        time.sleep(0.01)
        self.assertEqual(len(help_replies), 1)
        self.assertEqual(len(remove_version_connect(help_informs)),
                         NO_HELP_MESSAGES)

    def test_fifty_thread_mayhem(self):
        """Test using callbacks from fifty threads simultaneously."""
        num_threads = 50
        # map from thread_id -> (replies, informs)
        results = {}
        # list of thread objects
        threads = []

        def reply_cb(reply, thread_id):
            results[thread_id][0].append(reply)
            results[thread_id][2].set()

        def inform_cb(inform, thread_id):
            results[thread_id][1].append(inform)

        def worker(thread_id, request):
            self.client.callback_request(
                request.copy(),
                reply_cb=reply_cb,
                inform_cb=inform_cb,
                user_data=(thread_id, ),
            )

        request = katcp.Message.request("help")

        for thread_id in range(num_threads):
            results[thread_id] = ([], [], threading.Event())

        for thread_id in range(num_threads):
            thread = threading.Thread(target=worker, args=(thread_id, request))
            threads.append(thread)

        for thread in threads:
            thread.start()

        for thread in threads:
            thread.join()

        for thread_id in range(num_threads):
            replies, informs, done = results[thread_id]
            done.wait(5.0)
            self.assertTrue(done.isSet())
            self.assertEqual(len(replies), 1)
            self.assertEqual(replies[0].arguments[0], "ok")
            informs = remove_version_connect(informs)
            if len(informs) != NO_HELP_MESSAGES:
                print thread_id, len(informs)
                print[x.arguments[0] for x in informs]
            self.assertEqual(len(informs), NO_HELP_MESSAGES)

    def test_blocking_request(self):
        """Test the callback client's blocking request."""
        reply, informs = self.client.blocking_request(
            katcp.Message.request("help"), )

        self.assertEqual(reply.name, "help")
        self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES])
        self.assertEqual(len(remove_version_connect(informs)),
                         NO_HELP_MESSAGES)

        reply, informs = self.client.blocking_request(katcp.Message.request(
            "slow-command", "0.5"),
                                                      timeout=0.001)

        self.assertEqual(reply.name, "slow-command")
        self.assertEqual(reply.arguments[0], "fail")
        self.assertTrue(reply.arguments[1].startswith("Timed out after"))

    def test_blocking_request_mid(self):
        ## Test that the blocking client does the right thing with message
        ## identifiers

        # Wait for the client to detect the server protocol. Server should
        # support message identifiers
        self.assertTrue(self.client.wait_protocol(0.2))
        # Replace send_message so that we can check the message
        self.client.send_message = mock.Mock()

        # By default message identifiers should be enabled, and should start
        # counting at 1
        self.client.blocking_request(katcp.Message.request('watchdog'),
                                     timeout=0)
        self.client.blocking_request(katcp.Message.request('watchdog'),
                                     timeout=0)
        self.client.blocking_request(katcp.Message.request('watchdog'),
                                     timeout=0)
        # Extract katcp.Message object .mid attributes from the mock calls to
        # send_message
        mids = [
            args[0].mid  # arg[0] should be the Message() object
            for args, kwargs in self.client.send_message.call_args_list
        ]
        self.assertEqual(mids, ['1', '2', '3'])
        self.client.send_message.reset_mock()

        # Explicitly ask for no mid to be used
        self.client.blocking_request(katcp.Message.request('watchdog'),
                                     use_mid=False,
                                     timeout=0)
        mid = self.client.send_message.call_args[0][0].mid
        self.assertEqual(mid, None)

        # Ask for a specific mid to be used
        self.client.send_message.reset_mock()
        self.client.blocking_request(katcp.Message.request('watchdog', mid=42),
                                     timeout=0)
        mid = self.client.send_message.call_args[0][0].mid
        self.assertEqual(mid, '42')

        ## Check situation for a katcpv4 server
        self.client._server_supports_ids = False

        # Should fail if an mid is passed
        with self.assertRaises(katcp.core.KatcpVersionError):
            self.client.blocking_request(katcp.Message.request('watchdog',
                                                               mid=42),
                                         timeout=0)

        # Should fail if an mid is requested
        with self.assertRaises(katcp.core.KatcpVersionError):
            self.client.blocking_request(katcp.Message.request('watchdog'),
                                         use_mid=True,
                                         timeout=0)

        # Should use no mid by default
        self.client.send_message.reset_mock()
        self.client.blocking_request(katcp.Message.request('watchdog'),
                                     timeout=0)
        mid = self.client.send_message.call_args[0][0].mid
        self.assertEqual(mid, None)

    def test_request_fail_on_raise(self):
        """Test that the callback is called even if send_message raises
           KatcpClientError."""
        def raise_error(msg, timeout=None):
            raise katcp.KatcpClientError("Error %s" % msg.name)

        self.client.send_message = raise_error

        replies = []

        def reply_cb(msg):
            replies.append(msg)

        self.client.callback_request(
            katcp.Message.request("foo"),
            reply_cb=reply_cb,
        )

        self.assertEqual(len(replies), 1)
        self.assertEqual(replies[0].name, "foo")
        self.assertEqual(replies[0].arguments, ["fail", "Error foo"])

    def test_stop_join(self):
        # Set up a slow command to ensure that there is something in
        # the async queue
        with mock.patch('katcp.client.threading.Timer') as MockTimer:
            instance = MockTimer.return_value
            self.client.callback_request(katcp.Message.request(
                "slow-command", "10000"),
                                         timeout=10000.1)
        # Exactly one instance of MockTimer should have been instantiated
        self.assertEqual(MockTimer.call_count, 1)
        self.client.stop(timeout=0.5)
        # Check that the timer's cancel() method has been called
        instance.cancel.assert_called_once_with()
        self.client.join(timeout=0.45)
        instance.join.assert_called_once_with(timeout=0.45)
        # It's OK not to have this in teardown since the client will
        # itself cancel the slow_command when it is stop()ed
        self.client.blocking_request(
            katcp.Message.request("cancel-slow-command"))
示例#12
0
from tornado.util import ObjectDict
from tornado.concurrent import Future as tornado_Future
from katcp import Sensor

from katcp.testutils import DeviceTestServer
from katcp.client import *

logging.basicConfig(
    format="%(asctime)s %(name)s %(levelname)s %(funcName)s(%(filename)s:%(lineno)d)%(message)s",
    level=logging.DEBUG
)

def cb(*args):
    print args

try:
    d = DeviceTestServer('', 0)
    d.start(timeout=1)
    logging.info('Server started at port {0}'.format(d.bind_address[1]))
    c = AsyncClient('127.0.0.1', d.bind_address[1])
    c.enable_thread_safety()
    c.start(timeout=1)

#     rm = Message.request

#     #time.sleep(10000000)
    import IPython ; IPython.embed()
finally:
    d.stop()
    c.stop()
示例#13
0
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)
示例#15
0
from katcp import Sensor
from katcp.client import *
from katcp.testutils import DeviceTestServer

logging.basicConfig(
    format=
    "%(asctime)s %(name)s %(levelname)s %(funcName)s(%(filename)s:%(lineno)d)%(message)s",
    level=logging.DEBUG)


def cb(*args):
    print(args)


try:
    d = DeviceTestServer('', 0)
    d.start(timeout=1)
    logging.info('Server started at port {0}'.format(d.bind_address[1]))
    c = AsyncClient('127.0.0.1', d.bind_address[1])
    c.enable_thread_safety()
    c.start(timeout=1)

    #     rm = Message.request

    #     #time.sleep(10000000)
    import IPython
    IPython.embed()
finally:
    d.stop()
    c.stop()
示例#16
0
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()
示例#17
0
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()
示例#18
0
class test_AsyncClientIntegrated(tornado.testing.AsyncTestCase, TestUtilMixin):
    def setUp(self):
        super(test_AsyncClientIntegrated, self).setUp()
        self.server = DeviceTestServer('', 0)
        self.server.set_ioloop(self.io_loop)
        self.server.set_concurrency_options(thread_safe=False,
                                            handler_thread=False)
        self.server.start()

        host, port = self.server.bind_address
        self.client = katcp.CallbackClient(host, port)
        self.client.set_ioloop(self.io_loop)
        self.client.start()

    @tornado.testing.gen_test
    def test_timeout_of_until_connected(self):
        # Test for timing out
        with self.assertRaises(tornado.gen.TimeoutError):
            yield self.client.until_connected(timeout=0.0001)
        # Test for NOT timing out
        host, port = self.server.bind_address
        client2 = katcp.CallbackClient(host, port)
        client2.set_ioloop(self.io_loop)
        client2.start()
        yield client2.until_connected(timeout=0.5)
        self.assertTrue(client2.is_connected)

    @tornado.testing.gen_test
    def test_timeout_of_until_protocol(self):
        # Test for timing out
        with self.assertRaises(tornado.gen.TimeoutError):
            yield self.client.until_protocol(timeout=0.0001)
        # Test for NOT timing out
        host, port = self.server.bind_address
        client2 = katcp.CallbackClient(host, port)
        client2.set_ioloop(self.io_loop)
        client2.start()
        yield client2.until_protocol(timeout=0.5)

    @tornado.testing.gen_test
    def test_future_request_simple(self):
        yield self.client.until_connected()
        reply, informs = yield self.client.future_request(
            Message.request('watchdog'))
        self.assertEqual(len(informs), 0)
        self.assertEqual(reply.name, "watchdog")
        self.assertEqual(reply.arguments, ["ok"])

    @tornado.testing.gen_test
    def test_future_request_with_informs(self):
        yield self.client.until_connected()
        reply, informs = yield self.client.future_request(
            Message.request('help'))
        self.assertEqual(reply.name, "help")
        self.assertEqual(reply.arguments, ["ok", "%d" % NO_HELP_MESSAGES])
        self.assertEqual(len(informs), NO_HELP_MESSAGES)

    @tornado.testing.gen_test
    def test_disconnect_cleanup(self):
        yield self.client.until_protocol()
        mid = 55
        future_reply = self.client.future_request(
            Message.request('slow-command', 1, mid=mid))
        # Force a disconnect
        self.client._disconnect()
        reply, informs = yield future_reply
        self.assertEqual(
            reply,
            Message.reply('slow-command',
                          'fail',
                          'Connection closed before reply was received',
                          mid=mid))

    @tornado.testing.gen_test
    def test_stop_cleanup(self):
        yield self.client.until_protocol()
        mid = 564
        future_reply = self.client.future_request(
            Message.request('slow-command', 1, mid=mid))
        # Stop client
        self.client.stop()
        reply, informs = yield future_reply
        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)
        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
示例#20
0
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()
示例#21
0
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"])