def test_current_resource_value(self): device_id1 = 'ABCD_E' device_id2 = 'ABCD_F' resource_path = '/3/4/5' subs = SubscriptionsManager(mock.MagicMock()) channel_a = channels.CurrentResourceValue(device_id=device_id1, resource_path=resource_path) observer_a = subs.subscribe(channel_a) channel_b = channels.CurrentResourceValue(device_id=device_id2, resource_path=resource_path) observer_b = subs.subscribe(channel_b) subs.notify({ channels.ChannelIdentifiers.async_responses: [ { 'id': channel_a.async_id }, { 'id': 'unlikely' }, ] }) self.assertEqual(1, observer_a.notify_count) self.assertEqual(0, observer_b.notify_count)
def test_routing_performance(self): subs = SubscriptionsManager(mock.MagicMock()) sample_id = 42 sample = None # register a bunch of subscribers for i in range(1000): subs.subscribe(channels.ResourceValues(device_id=i)) obs = subs.subscribe(channels.ResourceValues(device_id=i, resource_path=['/3/0/*', '/4/0/1'])) if i == sample_id: sample = obs subs.subscribe(channels.ResourceValues(**{'device_id': i, str(i): 'abc'})) notification_count = 33 start = timeit.default_timer() for i in range(notification_count): subs.notify({'notifications': [{'endpoint-name': str(42), 'resource-path': '/3/0/1'}]}) subs.notify({'notifications': [{'endpoint-name': str(42), 'resource-path': '/3/1'}]}) subs.notify({'notifications': [{'endpoint-name': str(5), 'resource-path': '/3/1'}]}) stop = timeit.default_timer() duration = stop - start self.assertEqual(notification_count, sample.notify_count) # mildly contrived, but for cross-machine consistency we need # to use a benchmark to compare the lookup performance with self.assertLess(duration, 100 * self.bench)
def test_parameters_as_lists(self): subs = SubscriptionsManager(mock.MagicMock()) observer_a = subs.subscribe( channels.ResourceValues(device_id='a', resource_path=['a', 'b'])) observer_b = subs.subscribe( channels.ResourceValues(device_id=['x', 'y'], resource_path=['a', 'b'])) subs.notify({ channels.ChannelIdentifiers.notifications: [ # should trigger A { 'ep': 'a', 'path': 'a' }, # should trigger A { 'ep': 'a', 'path': 'b' }, # should trigger B { 'ep': 'x', 'path': 'b' }, ] }) self.assertEqual(2, observer_a.notify_count) self.assertEqual(1, observer_b.notify_count)
def test_subscribe(self): subs = SubscriptionsManager(mock.MagicMock()) observer_a = subs.subscribe(channels.DeviceStateChanges(device_id='A')) observer_b = subs.subscribe(channels.DeviceStateChanges(device_id='B')) observer_c = subs.subscribe(channels.DeviceStateChanges()) self.assertIsNot(observer_a, observer_b) self.assertIsNot(observer_b, observer_c) future_a = observer_a.next().defer() future_b = observer_b.next().defer() future_c = observer_c.next().defer() self.assertIsNot(future_a, future_b) self.assertIsNot(future_b, future_c) # a valid, but irrelevant channel subs.notify({ channels.ChannelIdentifiers.async_responses: [ dict(a=1, b=2, device_id='A') ] }) time.sleep(0.01) self.assertFalse(future_a.ready()) self.assertFalse(future_b.ready()) self.assertFalse(future_c.ready()) # a valid channel, relevant for DeviceState subs.notify({ channels.ChannelIdentifiers.reg_updates: [ dict(a=1, b=2, device_id='A') ] }) result = future_a.get(timeout=2) self.assertDictContainsSubset(dict(a=1, b=2), result) self.assertFalse(future_b.ready()) # possible race condition, so an unreliable check. # just because one observer (`a`) is ready, does not mean another (`c`) is. # self.assertTrue(future_c.ready()) result = future_c.get() self.assertDictContainsSubset(dict(a=1, b=2), result)
def test_subscribe_custom_threadpool(self): subs = SubscriptionsManager(mock.MagicMock()) custom_threads = ThreadPool(processes=1) observer_a = subs.subscribe(channels.DeviceStateChanges(device_id='A'), provider=custom_threads) with mock.patch.object(custom_threads, 'apply_async') as custom: observer_a.next().defer() observer_a.next().defer() # prove that we used the threadpool we provided self.assertEqual(custom_threads.apply_async.call_count, 2)
def test_notify_de_registrations(self): subs = SubscriptionsManager(mock.MagicMock()) observer = subs.subscribe(channels.DeviceStateChanges()) future = observer.next().defer() subs.notify( {channels.ChannelIdentifiers.de_registrations: ["device_id_test"]}) output = future.get(timeout=2) self.assertEqual(output, { "device_id": "device_id_test", "channel": "de_registrations" })
def test_subscribe_conflict(self): subs = SubscriptionsManager(mock.MagicMock()) observer_a = subs.subscribe( channels.ResourceValueCurrent(device_id='A', resource_path=5)) observer_b = subs.subscribe( channels.ResourceValueCurrent(device_id='B', resource_path=5)) channel_c = channels.ResourceValueCurrent(device_id='A', resource_path=2) observer_c = subs.subscribe(channel_c) subs.notify({ channels._API_CHANNELS.async_responses: [ dict(a=1, b=2, device_id='A', resource_path=2, id=channel_c.async_id) ] }) result = observer_c.next().defer().get(timeout=2) self.assertDictContainsSubset(dict(a=1, b=2), result) self.assertDictContainsSubset(dict(a=1, b=2), result)
def test_notify_reg_updates(self): subs = SubscriptionsManager(mock.MagicMock()) observer = subs.subscribe(channels.DeviceStateChanges()) future = observer.next().defer() subs.notify({ channels.ChannelIdentifiers.reg_updates: [{ "ep": "test_endpoint_name", "original_ep": "test_original_ep", "ept": "test_ept", "q": "test_q", "resources": [{ "path": "test_path", "if": "test_if", "rt": "test_rt", "ct": "test_ct", "obs": "test_obs" }] }] }) output = future.get(timeout=2) self.assertEqual( output, { "channel": "reg_updates", "device_id": "test_endpoint_name", "alias": "test_original_ep", "device_type": "test_ept", "queue_mode": "test_q", "resources": [{ "path": "test_path", "type": "test_rt", "content_type": "test_ct", "observable": "test_obs" }] })
def test_payload(self): # subscription to wildcard value should be triggered when receiving specific value device_id1 = 'ABCD_E' subs = SubscriptionsManager(mock.MagicMock()) observer_a = subs.subscribe( channels.ResourceValues(device_id=device_id1, resource_path='/10255/0/4')) subs.notify({ channels.ChannelIdentifiers.notifications: [ { "ct": "application/vnd.oma.lwm2m+tlv", "ep": device_id1, "max-age": 0, "path": "/10255/0/4", "payload": "iAsLSAAIAAAAAAAAAAA=", 'unexpected': 'extra', }, ], }) self.assertEqual(1, observer_a.notify_count) received_payload = observer_a.next().block(1) self.assertEqual( received_payload, { 'ct': 'application/vnd.oma.lwm2m+tlv', 'device_id': device_id1, 'max-age': 0, 'resource_path': '/10255/0/4', 'payload': { '11': { '0': 0 } }, 'channel': 'notifications', 'unexpected': 'extra', })
def test_wildcards(self): # subscription to wildcard value should be triggered when receiving specific value device_id1 = 'ABCDEF' device_id2 = 'ABC123' subs = SubscriptionsManager(mock.MagicMock()) # channels A = subs.channels.ResourceValues(device_id=device_id1, resource_path=999) B = subs.channels.ResourceValues(device_id='ABCD*') C = subs.channels.ResourceValues(device_id='ABC*', resource_path=5) D = subs.channels.ResourceValues(device_id=device_id1, custom_attr='x') E = subs.channels.ResourceValues(device_id='*') F = subs.channels.ResourceValues(resource_path=[4, 5, 6, 7]) # set names for clarity of logging A.name = 'A' B.name = 'B' C.name = 'C' D.name = 'D' E.name = 'E' F.name = 'F' sequence = [A, B, C, D, E, F] # set up the channels for channel in sequence: subs.subscribe(channel) # notification payloads notifications = [ # device id matches ALL but doesn't match A, C, F because of resource paths, D due to custom attr ({ 'ep': device_id1 }, (B, E)), # device id matches ALL but doesn't match A because of resource paths, D due to custom attr ({ 'ep': device_id1, 'path': '5' }, (B, C, E, F)), # should work as above ({ 'ep': device_id1, 'path': '5', 'unexpected': 'extra' }, (B, C, E, F)), # only matches D due to custom attr, E due to having any device id ({ 'ep': device_id1, 'custom_attr': 'x' }, (B, D, E)), # matches C and F due to resource path ({ 'ep': device_id2, 'path': '5' }, (C, E, F)), # should not trigger anything ({ 'endpoint': device_id1, 'custom_attr': 'x' }, tuple()), ] # trigger one-by-one for notification, expected_triggers in notifications: triggered = subs.notify( {channels.ChannelIdentifiers.notifications: [notification]}) expected_names = [c.name for c in expected_triggers] actual_names = [c.name for c in triggered] self.assertEqual(sorted(expected_names), sorted(actual_names)) # trigger in one go triggered = subs.notify({ channels.ChannelIdentifiers.notifications: [n[0] for n in notifications] }) all_expected_channels = [c.name for n in notifications for c in n[1]] actual_names = [c.name for c in triggered] self.assertEqual(sorted(all_expected_channels), sorted(actual_names)) self.assertEqual(8, B.observer.notify_count) self.assertEqual(2, D.observer.notify_count)