示例#1
0
def client_test_write_property(servient, protocol_client_cls, timeout=None):
    """Helper function to test Property writes on bindings clients."""

    exposed_thing = next(servient.exposed_things)

    prop_name = uuid.uuid4().hex

    exposed_thing.add_property(prop_name, PropertyFragmentDict({
        "type": "string",
        "observable": True
    }), value=Faker().sentence())

    servient.refresh_forms()

    td = ThingDescription.from_thing(exposed_thing.thing)

    @tornado.gen.coroutine
    def test_coroutine():
        protocol_client = protocol_client_cls()
        prop_value = Faker().sentence()

        prev_value = yield exposed_thing.properties[prop_name].read()
        assert prev_value != prop_value

        yield protocol_client.write_property(td, prop_name, prop_value, timeout=timeout)

        curr_value = yield exposed_thing.properties[prop_name].read()
        assert curr_value == prop_value

    run_test_coroutine(test_coroutine)
示例#2
0
    def test_coroutine():
        td = ThingDescription.from_thing(exposed_thing.thing)
        event_name = next(six.iterkeys(td.events))

        future_conn = Future()
        future_event = Future()

        payload = Faker().sentence()

        def on_next(ev):
            if not future_conn.done():
                future_conn.set_result(True)
                return

            if ev.data == payload:
                future_event.set_result(True)

        subscription = subscribe_func(event_name, on_next)

        while not future_conn.done():
            yield tornado.gen.sleep(0)
            exposed_thing.emit_event(event_name, Faker().sentence())

        exposed_thing.emit_event(event_name, payload)

        yield future_event

        assert future_event.result()

        subscription.dispose()
示例#3
0
文件: proxy.py 项目: agmangas/wot-py
async def expose_proxy(wot, consumed_thing):
    """Takes a Consumed Thing and exposes an Exposed Thing that acts as a proxy."""

    description = {
        "id": THING_ID,
        "name": "Thing Proxy: {}".format(consumed_thing.name)
    }

    td_dict = consumed_thing.td.to_dict()

    for intrct_key in ['properties', 'actions', 'events']:
        description.update({intrct_key: td_dict.get(intrct_key, {})})

    exposed_thing = wot.produce(json.dumps(description))

    for name in six.iterkeys(description.get('properties')):
        exposed_thing.set_property_read_handler(
            name, build_prop_read_proxy(consumed_thing, name))
        exposed_thing.set_property_write_handler(
            name, build_prop_write_proxy(consumed_thing, name))

    for name in six.iterkeys(description.get('actions')):
        exposed_thing.set_action_handler(
            name, build_action_invoke_proxy(consumed_thing, name))

    for name in six.iterkeys(description.get('events')):
        subscribe_event(consumed_thing, exposed_thing, name)

    exposed_thing.expose()

    logger.info("Exposed Thing proxy TD:\n{}".format(
        pprint.pformat(
            ThingDescription.from_thing(exposed_thing.thing).to_dict())))

    return exposed_thing
示例#4
0
def client_test_invoke_action(servient, protocol_client_cls, timeout=None):
    """Helper function to test Action invocations on bindings clients."""

    exposed_thing = next(servient.exposed_things)

    action_name = uuid.uuid4().hex

    @tornado.gen.coroutine
    def action_handler(parameters):
        input_value = parameters.get("input")
        yield tornado.gen.sleep(random.random() * 0.1)
        raise tornado.gen.Return("{:f}".format(input_value))

    exposed_thing.add_action(action_name, ActionFragmentDict({
        "input": {"type": "number"},
        "output": {"type": "string"}
    }), action_handler)

    servient.refresh_forms()

    td = ThingDescription.from_thing(exposed_thing.thing)

    @tornado.gen.coroutine
    def test_coroutine():
        protocol_client = protocol_client_cls()

        input_value = Faker().pyint()

        result = yield protocol_client.invoke_action(td, action_name, input_value, timeout=timeout)
        result_expected = yield action_handler({"input": input_value})

        assert result == result_expected

    run_test_coroutine(test_coroutine)
示例#5
0
    def test_coroutine():
        td = ThingDescription.from_thing(exposed_thing.thing)
        prop_name = next(six.iterkeys(td.properties))

        future_conn = Future()
        future_change = Future()

        prop_value = Faker().sentence()

        def on_next(ev):
            if not future_conn.done():
                future_conn.set_result(True)
                return

            if ev.data.value == prop_value:
                future_change.set_result(True)

        subscription = subscribe_func(prop_name, on_next)

        while not future_conn.done():
            yield tornado.gen.sleep(0)
            yield exposed_thing.write_property(prop_name, Faker().sentence())

        yield exposed_thing.write_property(prop_name, prop_value)

        yield future_change

        assert future_change.result()

        subscription.dispose()
示例#6
0
def client_test_invoke_action_error(servient, protocol_client_cls):
    """Helper function to test Action invocations that raise errors on bindings clients."""

    exposed_thing = next(servient.exposed_things)

    action_name = uuid.uuid4().hex

    err_message = Faker().sentence()

    # noinspection PyUnusedLocal
    def action_handler(parameters):
        raise ValueError(err_message)

    exposed_thing.add_action(action_name, ActionFragmentDict({
        "input": {"type": "number"},
        "output": {"type": "string"}
    }), action_handler)

    servient.refresh_forms()

    td = ThingDescription.from_thing(exposed_thing.thing)

    @tornado.gen.coroutine
    def test_coroutine():
        protocol_client = protocol_client_cls()

        try:
            yield protocol_client.invoke_action(td, action_name, Faker().pyint())
            raise AssertionError("Did not raise Exception")
        except Exception as ex:
            assert err_message in str(ex)

    run_test_coroutine(test_coroutine)
示例#7
0
def consumed_exposed_pair():
    """Returns a dict with two keys:
    * consumed_thing: A ConsumedThing instance. The Servient instance that contains this
    ConsumedThing has been patched to use the ExposedThingProxyClient Protocol Binding client.
    * exposed_thing: The ExposedThing behind the previous ConsumedThing (for assertion purposes)."""

    servient = Servient()

    exp_thing = ExposedThing(servient=servient,
                             thing=Thing(id=uuid.uuid4().urn))

    servient.select_client = MagicMock(
        return_value=ExposedThingProxyClient(exp_thing))

    @tornado.gen.coroutine
    def lower(parameters):
        input_value = parameters.get("input")
        yield tornado.gen.sleep(0)
        raise tornado.gen.Return(str(input_value).lower())

    exp_thing.add_property(uuid.uuid4().hex, _build_property_fragment())
    exp_thing.add_action(uuid.uuid4().hex, _build_action_fragment(), lower)
    exp_thing.add_event(uuid.uuid4().hex, _build_event_fragment())

    td = ThingDescription.from_thing(exp_thing.thing)

    return {
        "consumed_thing": ConsumedThing(servient=servient, td=td),
        "exposed_thing": exp_thing
    }
示例#8
0
    def get_property(prop_name):
        """Gets the given property using the WS Link contained in the thing description."""

        td = ThingDescription.from_thing(exposed_thing.thing)
        consumed_thing = ConsumedThing(servient, td=td)

        value = yield consumed_thing.read_property(prop_name)

        raise tornado.gen.Return(value)
示例#9
0
    def _is_fragment_match(cls, item, thing_filter):
        """Returns True if the given item (an ExposedThing, Thing or TD)
        matches the fragment in the given Thing filter."""

        td = None

        if isinstance(item, ExposedThing):
            td = ThingDescription.from_thing(item.thing)
        elif isinstance(item, Thing):
            td = ThingDescription.from_thing(item)
        elif isinstance(item, ThingDescription):
            td = item

        assert td

        fragment_dict = thing_filter.fragment if thing_filter.fragment else {}

        return all(item in six.iteritems(td.to_dict())
                   for item in six.iteritems(fragment_dict))
示例#10
0
    def _build_local_discover_observable(self, thing_filter):
        """Builds an Observable to discover Things using the local method."""

        found_tds = [
            ThingDescription.from_thing(exposed_thing.thing).to_str()
            for exposed_thing in self._servient.exposed_things
            if self._is_fragment_match(exposed_thing, thing_filter)
        ]

        # noinspection PyUnresolvedReferences
        return Observable.of(*found_tds)
示例#11
0
    def get(self, thing_url_name):
        exp_thing = self.servient.exposed_thing_set.find_by_thing_id(
            thing_url_name)

        td_doc = ThingDescription.from_thing(exp_thing.thing).to_dict()
        base_url = self.servient.get_thing_base_url(exp_thing)

        if base_url:
            td_doc.update({"base": base_url})

        self.write(td_doc)
示例#12
0
def test_all_protocols_combined(all_protocols_servient):
    """Protocol bindings work as expected when multiple
    servers are combined within the same Servient."""

    exposed_thing = next(all_protocols_servient.exposed_things)
    td = ThingDescription.from_thing(exposed_thing.thing)

    clients = [WebsocketClient(), HTTPClient()]

    if is_coap_supported():
        from wotpy.protocols.coap.client import CoAPClient
        clients.append(CoAPClient())

    if is_mqtt_supported():
        from tests.protocols.mqtt.broker import is_test_broker_online
        from wotpy.protocols.mqtt.client import MQTTClient
        if is_test_broker_online():
            clients.append(MQTTClient())

    prop_name = next(six.iterkeys(td.properties))

    @tornado.gen.coroutine
    def read_property(the_client):
        prop_value = Faker().sentence()

        curr_value = yield the_client.read_property(td, prop_name)
        assert curr_value != prop_value

        yield exposed_thing.properties[prop_name].write(prop_value)

        curr_value = yield the_client.read_property(td, prop_name)
        assert curr_value == prop_value

    @tornado.gen.coroutine
    def write_property(the_client):
        updated_value = Faker().sentence()

        curr_value = yield exposed_thing.properties[prop_name].read()
        assert curr_value != updated_value

        yield the_client.write_property(td, prop_name, updated_value)

        curr_value = yield exposed_thing.properties[prop_name].read()
        assert curr_value == updated_value

    @tornado.gen.coroutine
    def test_coroutine():
        for client in clients:
            yield read_property(client)
            yield write_property(client)

    run_test_coroutine(test_coroutine)
示例#13
0
def test_read_property_unknown(websocket_servient):
    """The Websockets client raises an error when attempting to read an unknown property."""

    exposed_thing = next(websocket_servient.exposed_things)
    td = ThingDescription.from_thing(exposed_thing.thing)

    @tornado.gen.coroutine
    def test_coroutine():
        ws_client = WebsocketClient()

        with pytest.raises(ProtocolClientException):
            yield ws_client.read_property(td, uuid.uuid4().hex)

    run_test_coroutine(test_coroutine)
示例#14
0
def client_test_on_property_change(servient, protocol_client_cls):
    """Helper function to test observation of Property updates on bindings clients."""

    exposed_thing = next(servient.exposed_things)

    prop_name = uuid.uuid4().hex

    exposed_thing.add_property(prop_name, PropertyFragmentDict({
        "type": "string",
        "observable": True
    }), value=Faker().sentence())

    servient.refresh_forms()

    td = ThingDescription.from_thing(exposed_thing.thing)

    @tornado.gen.coroutine
    def test_coroutine():
        protocol_client = protocol_client_cls()

        values = [Faker().sentence() for _ in range(10)]
        values_observed = {value: tornado.concurrent.Future() for value in values}

        @tornado.gen.coroutine
        def write_next():
            try:
                next_value = next(val for val, fut in six.iteritems(values_observed) if not fut.done())
                yield exposed_thing.properties[prop_name].write(next_value)
            except StopIteration:
                pass

        def on_next(ev):
            prop_value = ev.data.value
            if prop_value in values_observed and not values_observed[prop_value].done():
                values_observed[prop_value].set_result(True)

        observable = protocol_client.on_property_change(td, prop_name)

        subscription = observable.subscribe_on(IOLoopScheduler()).subscribe(on_next)

        periodic_emit = tornado.ioloop.PeriodicCallback(write_next, 10)
        periodic_emit.start()

        yield list(values_observed.values())

        periodic_emit.stop()
        subscription.dispose()

    run_test_coroutine(test_coroutine)
示例#15
0
def client_test_on_property_change_error(servient, protocol_client_cls):
    """Helper function to test propagation of errors raised
    during observation of Property updates on bindings clients."""

    exposed_thing = next(servient.exposed_things)

    prop_name = uuid.uuid4().hex

    exposed_thing.add_property(prop_name, PropertyFragmentDict({
        "type": "string",
        "observable": True
    }), value=Faker().sentence())

    servient.refresh_forms()

    td = ThingDescription.from_thing(exposed_thing.thing)

    @tornado.gen.coroutine
    def test_coroutine():
        protocol_client = protocol_client_cls()

        yield servient.shutdown()

        future_err = tornado.concurrent.Future()

        # noinspection PyUnusedLocal
        def on_next(item):
            future_err.set_exception(Exception("Should not have emitted any items"))

        def on_error(err):
            future_err.set_result(err)

        observable = protocol_client.on_property_change(td, prop_name)

        subscribe_kwargs = {
            "on_next": on_next,
            "on_error": on_error
        }

        subscription = observable.subscribe_on(IOLoopScheduler()).subscribe(**subscribe_kwargs)

        observe_err = yield future_err

        assert isinstance(observe_err, Exception)

        subscription.dispose()

    run_test_coroutine(test_coroutine)
示例#16
0
    def get(self):
        response = {}

        for exp_thing in self.servient.enabled_exposed_things:
            thing_id = exp_thing.thing.id

            if self.get_argument("expanded", False):
                val = ThingDescription.from_thing(exp_thing.thing).to_dict()
                val.update(
                    {"base": self.servient.get_thing_base_url(exp_thing)})
            else:
                val = "/{}".format(exp_thing.thing.url_name)

            response[thing_id] = val

        self.write(response)
示例#17
0
def test_from_thing():
    """ThingDescription objects can be built from Thing objects."""

    fake = Faker()

    thing_id = uuid.uuid4().urn
    action_id = uuid.uuid4().hex
    prop_id = uuid.uuid4().hex
    event_id = uuid.uuid4().hex
    action_form_href = fake.url()
    prop_form_href = fake.url()

    thing = Thing(id=thing_id)

    action = Action(thing=thing, name=action_id)
    action_form = Form(interaction=action,
                       protocol=Protocols.HTTP,
                       href=action_form_href)
    action.add_form(action_form)
    thing.add_interaction(action)

    prop = Property(thing=thing, name=prop_id, type="string")
    prop_form = Form(interaction=prop,
                     protocol=Protocols.HTTP,
                     href=prop_form_href)
    prop.add_form(prop_form)
    thing.add_interaction(prop)

    event = Event(thing=thing, name=event_id)
    thing.add_interaction(event)

    json_td = ThingDescription.from_thing(thing)
    td_dict = json_td.to_dict()

    assert td_dict["id"] == thing.id
    assert td_dict["title"] == thing.title
    assert len(td_dict["properties"]) == 1
    assert len(td_dict["actions"]) == 1
    assert len(td_dict["events"]) == 1
    assert len(td_dict["actions"][action_id]["forms"]) == 1
    assert len(td_dict["properties"][prop_id]["forms"]) == 1
    assert td_dict["actions"][action_id]["forms"][0][
        "href"] == action_form_href
    assert td_dict["properties"][prop_id]["forms"][0]["href"] == prop_form_href
示例#18
0
def client_test_on_event(servient, protocol_client_cls):
    """Helper function to test observation of Events on bindings clients."""

    exposed_thing = next(servient.exposed_things)

    event_name = uuid.uuid4().hex

    exposed_thing.add_event(event_name, EventFragmentDict({
        "type": "number"
    }))

    servient.refresh_forms()

    td = ThingDescription.from_thing(exposed_thing.thing)

    @tornado.gen.coroutine
    def test_coroutine():
        protocol_client = protocol_client_cls()

        payloads = [Faker().pyint() for _ in range(10)]
        future_payloads = {key: tornado.concurrent.Future() for key in payloads}

        @tornado.gen.coroutine
        def emit_next():
            next_value = next(val for val, fut in six.iteritems(future_payloads) if not fut.done())
            exposed_thing.events[event_name].emit(next_value)

        def on_next(ev):
            if ev.data in future_payloads and not future_payloads[ev.data].done():
                future_payloads[ev.data].set_result(True)

        observable = protocol_client.on_event(td, event_name)

        subscription = observable.subscribe_on(IOLoopScheduler()).subscribe(on_next)

        periodic_emit = tornado.ioloop.PeriodicCallback(emit_next, 10)
        periodic_emit.start()

        yield list(future_payloads.values())

        periodic_emit.stop()
        subscription.dispose()

    run_test_coroutine(test_coroutine)
示例#19
0
def test_timeout_write_property(mqtt_servient):
    """Timeouts can be defined on Property writes."""

    exposed_thing = next(mqtt_servient.exposed_things)
    prop_name = next(six.iterkeys(exposed_thing.properties))
    td = ThingDescription.from_thing(exposed_thing.thing)
    mqtt_mock = _build_hbmqtt_mock(_effect_raise_timeout)

    timeout = random.random()

    @tornado.gen.coroutine
    def test_coroutine():
        with patch('wotpy.protocols.mqtt.client.hbmqtt.client.MQTTClient', new=mqtt_mock):
            mqtt_client = MQTTClient()

            with pytest.raises(ClientRequestTimeout):
                yield mqtt_client.write_property(td, prop_name, Faker().pystr(), timeout=timeout)

    run_test_coroutine(test_coroutine)
示例#20
0
文件: thing.py 项目: agmangas/wot-py
    def add_event(self, name, event_init):
        """Adds an event to the Thing object as defined by the event argument
        of type ThingEventInit and updates the Thing Description."""

        if isinstance(event_init, dict):
            event_init = EventFragmentDict(event_init)

        event = Event(thing=self._thing, name=name, init_dict=event_init)

        self._thing.add_interaction(event)

        event_data = ThingDescriptionChangeEventInit(
            td_change_type=TDChangeType.EVENT,
            method=TDChangeMethod.ADD,
            name=name,
            data=event_init.to_dict(),
            description=ThingDescription.from_thing(self.thing).to_dict())

        self._events_stream.on_next(
            ThingDescriptionChangeEmittedEvent(init=event_data))
示例#21
0
文件: thing.py 项目: agmangas/wot-py
    def add_property(self, name, property_init, value=None):
        """Adds a Property defined by the argument and updates the Thing Description.
        Takes an instance of ThingPropertyInit as argument."""

        if isinstance(property_init, dict):
            property_init = PropertyFragmentDict(property_init)

        prop = Property(thing=self._thing, name=name, init_dict=property_init)

        self._thing.add_interaction(prop)
        self._set_property_value(prop, value)

        event_data = ThingDescriptionChangeEventInit(
            td_change_type=TDChangeType.PROPERTY,
            method=TDChangeMethod.ADD,
            name=name,
            data=property_init.to_dict(),
            description=ThingDescription.from_thing(self.thing).to_dict())

        self._events_stream.on_next(
            ThingDescriptionChangeEmittedEvent(init=event_data))
示例#22
0
def test_stop_timeout(mqtt_servient):
    """Attempting to stop an unresponsive connection does not result in an indefinite wait."""

    exposed_thing = next(mqtt_servient.exposed_things)
    prop_name = next(six.iterkeys(exposed_thing.properties))
    td = ThingDescription.from_thing(exposed_thing.thing)

    timeout = random.random()

    assert (timeout * 3) < DEFAULT_TIMEOUT_SECS

    mqtt_mock = _build_hbmqtt_mock(_build_effect_sleep(DEFAULT_TIMEOUT_SECS * 10))

    @tornado.gen.coroutine
    def test_coroutine():
        with patch('wotpy.protocols.mqtt.client.hbmqtt.client.MQTTClient', new=mqtt_mock):
            mqtt_client = MQTTClient(stop_loop_timeout_secs=timeout)

            with pytest.raises(ClientRequestTimeout):
                yield mqtt_client.read_property(td, prop_name, timeout=timeout)

    run_test_coroutine(test_coroutine)
示例#23
0
文件: thing.py 项目: agmangas/wot-py
    def add_action(self, name, action_init, action_handler=None):
        """Adds an Action to the Thing object as defined by the action
        argument of type ThingActionInit and updates th,e Thing Description."""

        if isinstance(action_init, dict):
            action_init = ActionFragmentDict(action_init)

        action = Action(thing=self._thing, name=name, init_dict=action_init)

        self._thing.add_interaction(action)

        event_data = ThingDescriptionChangeEventInit(
            td_change_type=TDChangeType.ACTION,
            method=TDChangeMethod.ADD,
            name=name,
            data=action_init.to_dict(),
            description=ThingDescription.from_thing(self.thing).to_dict())

        self._events_stream.on_next(
            ThingDescriptionChangeEmittedEvent(init=event_data))

        if action_handler:
            self.set_action_handler(name, action_handler)
示例#24
0
def test_empty_thing_valid():
    """An empty Thing initialized by default has a valid JSON-LD serialization."""

    thing = Thing(id=uuid.uuid4().urn)
    json_td = ThingDescription.from_thing(thing)
    ThingDescription.validate(json_td.to_dict())
示例#25
0
def test_on_property_change(websocket_servient):
    """The Websockets client can observe property changes."""

    exposed_thing = next(websocket_servient.exposed_things)
    td = ThingDescription.from_thing(exposed_thing.thing)

    @tornado.gen.coroutine
    def test_coroutine():
        ws_client = WebsocketClient()

        prop_names = list(td.properties.keys())
        prop_name_01 = prop_names[0]
        prop_name_02 = prop_names[1]

        obsv_01 = ws_client.on_property_change(td, prop_name_01)
        obsv_02 = ws_client.on_property_change(td, prop_name_02)

        prop_values_01 = [uuid.uuid4().hex for _ in range(10)]
        prop_values_02 = [uuid.uuid4().hex for _ in range(90)]

        future_values_01 = {key: Future() for key in prop_values_01}
        future_values_02 = {key: Future() for key in prop_values_02}

        future_conn_01 = Future()
        future_conn_02 = Future()

        def build_on_next(fut_conn, fut_vals):
            def on_next(ev):
                if not fut_conn.done():
                    fut_conn.set_result(True)

                if ev.data.value in fut_vals:
                    fut_vals[ev.data.value].set_result(True)

            return on_next

        on_next_01 = build_on_next(future_conn_01, future_values_01)
        on_next_02 = build_on_next(future_conn_02, future_values_02)

        subscription_01 = obsv_01.subscribe_on(
            IOLoopScheduler()).subscribe(on_next_01)
        subscription_02 = obsv_02.subscribe_on(
            IOLoopScheduler()).subscribe(on_next_02)

        while not future_conn_01.done() or not future_conn_02.done():
            yield exposed_thing.write_property(prop_name_01, uuid.uuid4().hex)
            yield exposed_thing.write_property(prop_name_02, uuid.uuid4().hex)
            yield tornado.gen.sleep(0)

        assert len(prop_values_01) < len(prop_values_02)

        for idx in range(len(prop_values_01)):
            yield exposed_thing.write_property(prop_name_01,
                                               prop_values_01[idx])
            yield exposed_thing.write_property(prop_name_02,
                                               prop_values_02[idx])

        yield list(future_values_01.values())

        assert next(fut for fut in six.itervalues(future_values_02)
                    if not fut.done())

        subscription_01.dispose()

        for val in prop_values_02[len(prop_values_01):]:
            yield exposed_thing.write_property(prop_name_02, val)

        yield list(future_values_02.values())

        subscription_02.dispose()

    run_test_coroutine(test_coroutine)
示例#26
0
def test_consumed_client_protocols_preference():
    """The Servient selects different protocol clients to consume Things
    depending on the protocol choices displayed on the Thing Description."""

    servient = Servient(catalogue_port=None)

    @tornado.gen.coroutine
    def servient_start():
        raise tornado.gen.Return((yield servient.start()))

    @tornado.gen.coroutine
    def servient_shutdown():
        yield servient.shutdown()

    http_port = find_free_port()
    http_server = HTTPServer(port=http_port)

    servient.add_server(http_server)

    ws_port = find_free_port()
    ws_server = WebsocketServer(port=ws_port)

    servient.add_server(ws_server)

    client_server_map = {
        HTTPClient: http_server,
        WebsocketClient: ws_server
    }

    wot = tornado.ioloop.IOLoop.current().run_sync(servient_start)

    prop_name = uuid.uuid4().hex

    td_produce = ThingDescription({
        "id": uuid.uuid4().urn,
        "name": uuid.uuid4().hex,
        "properties": {
            prop_name: {
                "observable": True,
                "type": "string"
            }
        }
    })

    exposed_thing = wot.produce(td_produce.to_str())
    exposed_thing.expose()

    td_forms_all = ThingDescription.from_thing(exposed_thing.thing)

    client_01 = servient.select_client(td_forms_all, prop_name)
    client_01_class = client_01.__class__

    assert client_01_class in six.iterkeys(client_server_map)

    tornado.ioloop.IOLoop.current().run_sync(servient_shutdown)
    servient.remove_server(client_server_map[client_01_class].protocol)
    tornado.ioloop.IOLoop.current().run_sync(servient_start)

    td_forms_removed = ThingDescription.from_thing(exposed_thing.thing)

    client_02 = servient.select_client(td_forms_removed, prop_name)
    client_02_class = client_02.__class__

    assert client_02_class != client_01_class
    assert client_02_class in six.iterkeys(client_server_map)

    tornado.ioloop.IOLoop.current().run_sync(servient_shutdown)