def test_EntryNewRemote(nt_live, server_cb): nt_server, nt_client = nt_live nt_server_api = nt_server._api nt_client_api = nt_client._api nt_server_api.addEntryListenerById(nt_server_api.getEntryId("/foo"), server_cb, NT_NOTIFY_NEW) # Trigger an event nt_client_api.setEntryValueById(nt_client_api.getEntryId("/foo/bar"), Value.makeDouble(2.0)) nt_client_api.setEntryValueById(nt_client_api.getEntryId("/foo"), Value.makeDouble(1.0)) nt_client_api.flush() assert nt_server_api.waitForEntryListenerQueue(1.0) # Check the event events = server_cb.wait(1) # assert events[0].listener == handle assert events[0].local_id == nt_server_api.getEntryId("/foo") assert events[0].name == "/foo" assert events[0].value == Value.makeDouble(1.0) assert events[0].flags == NT_NOTIFY_NEW
def test_Double(): v = Value.makeDouble(0.5) assert NT_DOUBLE == v.type assert 0.5 == v.value v = Value.makeDouble(0.25) assert NT_DOUBLE == v.type assert 0.25 == v.value
def test_LoadPersistentUpdateValueFlags(storage_populated, dispatcher, entry_notifier, is_server): storage = storage_populated fp = StringIO('[NetworkTables Storage 3.0]\ndouble "foo2"=1.0\n') assert storage.loadPersistent(fp=fp) is None entry = storage.m_entries.get("foo2") assert Value.makeDouble(1.0) == entry.value assert NT_PERSISTENT == entry.flags assert entry.isPersistent # client shouldn't send an update as id not assigned yet if is_server: # id assigned as this is the server; seq_num incremented dispatcher._queueOutgoing.assert_has_calls([ call(Message.entryUpdate(1, 2, entry.value), None, None), call(Message.flagsUpdate(1, NT_PERSISTENT), None, None), ]) else: assert dispatcher._queueOutgoing.call_count == 0 entry_notifier.notifyEntry.assert_has_calls([ call( 1, "foo2", entry.value, NT_NOTIFY_FLAGS | NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL, ) ]) if not is_server: assert 2 == storage.m_entries.get( "foo2").seq_num # still should be incremented
def test_DeleteEntryExist(storage_populated, dispatcher, entry_notifier, is_server): storage = storage_populated storage.deleteEntry("foo2") entry = storage.m_entries.get("foo2") assert entry is not None assert entry.value is None assert entry.id == 0xFFFF assert entry.local_write == False # client shouldn't send an update as id not assigned yet if is_server: # id assigned as this is the server dispatcher._queueOutgoing.assert_has_calls( [call(Message.entryDelete(1), None, None)]) else: # shouldn't send an update id not assigned yet assert dispatcher._queueOutgoing.call_count == 0 entry_notifier.notifyEntry.assert_has_calls([ call(1, "foo2", Value.makeDouble(0.0), NT_NOTIFY_DELETE | NT_NOTIFY_LOCAL) ]) if is_server: assert len(storage.m_idmap) >= 2 assert not storage.m_idmap[1]
def generateNotifications(notifier): # All flags combos that can be generated by Storage flags = [ # "normal" notifications NT_NOTIFY_NEW, NT_NOTIFY_DELETE, NT_NOTIFY_UPDATE, NT_NOTIFY_FLAGS, NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS, # immediate notifications are always "new" NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW, # local notifications can be of any flag combo NT_NOTIFY_LOCAL | NT_NOTIFY_NEW, NT_NOTIFY_LOCAL | NT_NOTIFY_DELETE, NT_NOTIFY_LOCAL | NT_NOTIFY_UPDATE, NT_NOTIFY_LOCAL | NT_NOTIFY_FLAGS, NT_NOTIFY_LOCAL | NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS, ] # Generate across keys keys = ["/foo/bar", "/baz", "/boo"] val = Value.makeDouble(1) # Provide unique key indexes for each key keyindex = 5 for key in keys: for flag in flags: notifier.notifyEntry(keyindex, key, val, flag) keyindex += 1
def storage_populated(storage_empty, dispatcher, entry_notifier): storage = storage_empty entry_notifier.m_local_notifiers = False entry_notifier.m_local_notifiers = False storage.setEntryTypeValue("foo", Value.makeBoolean(True)) storage.setEntryTypeValue("foo2", Value.makeDouble(0.0)) storage.setEntryTypeValue("bar", Value.makeDouble(1.0)) storage.setEntryTypeValue("bar2", Value.makeBoolean(False)) dispatcher.reset_mock() entry_notifier.reset_mock() entry_notifier.m_local_notifiers = True return storage
def test_ProcessIncomingEntryAssignIgnore(storage_populate_one, dispatcher, entry_notifier, is_server, conn): storage = storage_populate_one value = Value.makeDouble(1.0) storage.processIncoming(Message.entryAssign("foo", 0xFFFF, 1, value, 0), conn) assert dispatcher._queueOutgoing.call_count == 0 assert entry_notifier.notifyEntry.call_count == 0
def test_SetEntryValueAssignTypeChange(storage_populate_one, dispatcher, entry_notifier): storage = storage_populate_one # update with different type results in error and no message value = Value.makeDouble(0.0) assert not storage.setEntryValue("foo", value) entry = storage.m_entries.get("foo") assert value != entry.value assert dispatcher._queueOutgoing.call_count == 0 assert entry_notifier.notifyEntry.call_count == 0
def test_SetDefaultEntryExistsDifferentType(storage_populate_one, dispatcher, entry_notifier): storage = storage_populate_one # existing entry is boolean value = Value.makeDouble(2.0) ret_val = storage.setDefaultEntryValue("foo", value) assert not ret_val # should not have updated value in table if it already existed. assert value != storage.m_entries.get("foo").value assert dispatcher._queueOutgoing.call_count == 0 assert entry_notifier.notifyEntry.call_count == 0
def storage_persistent(storage_empty, dispatcher, entry_notifier): storage = storage_empty entry_notifier.m_local_notifiers = False storage.setEntryTypeValue("boolean/true", Value.makeBoolean(True)) storage.setEntryTypeValue("boolean/false", Value.makeBoolean(False)) storage.setEntryTypeValue("double/neg", Value.makeDouble(-1.5)) storage.setEntryTypeValue("double/zero", Value.makeDouble(0.0)) storage.setEntryTypeValue("double/big", Value.makeDouble(1.3e8)) storage.setEntryTypeValue("string/empty", Value.makeString("")) storage.setEntryTypeValue("string/normal", Value.makeString("hello")) storage.setEntryTypeValue("string/special", Value.makeString("\0\3\5\n")) storage.setEntryTypeValue("string/quoted", Value.makeString('"a"')) storage.setEntryTypeValue("raw/empty", Value.makeRaw(b"")) storage.setEntryTypeValue("raw/normal", Value.makeRaw(b"hello")) storage.setEntryTypeValue("raw/special", Value.makeRaw(b"\0\3\5\n")) storage.setEntryTypeValue("booleanarr/empty", Value.makeBooleanArray([])) storage.setEntryTypeValue("booleanarr/one", Value.makeBooleanArray([True])) storage.setEntryTypeValue("booleanarr/two", Value.makeBooleanArray([True, False])) storage.setEntryTypeValue("doublearr/empty", Value.makeDoubleArray([])) storage.setEntryTypeValue("doublearr/one", Value.makeDoubleArray([0.5])) storage.setEntryTypeValue("doublearr/two", Value.makeDoubleArray([0.5, -0.25])) storage.setEntryTypeValue("stringarr/empty", Value.makeStringArray([])) storage.setEntryTypeValue("stringarr/one", Value.makeStringArray(["hello"])) storage.setEntryTypeValue("stringarr/two", Value.makeStringArray(["hello", "world\n"])) storage.setEntryTypeValue("\0\3\5\n", Value.makeBoolean(True)) storage.setEntryTypeValue("CaseSensitive/KeyName", Value.makeBoolean(True)) storage.setEntryTypeValue("=", Value.makeBoolean(True)) dispatcher.reset_mock() entry_notifier.reset_mock() entry_notifier.m_local_notifiers = True return storage
def test_PrefixNewLocal(nt_live, server_cb): nt_server, nt_client = nt_live nt_server_api = nt_server._api nt_server_api.addEntryListener("/foo", server_cb, NT_NOTIFY_NEW | NT_NOTIFY_LOCAL) # Trigger an event nt_server_api.setEntryValueById(nt_server_api.getEntryId("/foo/bar"), Value.makeDouble(1.0)) nt_server_api.setEntryValueById(nt_server_api.getEntryId("/baz"), Value.makeDouble(1.0)) assert nt_server_api.waitForEntryListenerQueue(1.0) events = server_cb.wait(1) # assert events[0].listener == handle assert events[0].local_id == nt_server_api.getEntryId("/foo/bar") assert events[0].name == "/foo/bar" assert events[0].value == Value.makeDouble(1.0) assert events[0].flags == NT_NOTIFY_NEW | NT_NOTIFY_LOCAL
def test_PollPrefixBasic(notifier): poller = notifier.createPoller() g1 = notifier.addPolled(poller, "/foo", NT_NOTIFY_NEW) g2 = notifier.addPolled(poller, "/foo", NT_NOTIFY_DELETE) g3 = notifier.addPolled(poller, "/foo", NT_NOTIFY_UPDATE) g4 = notifier.addPolled(poller, "/foo", NT_NOTIFY_FLAGS) notifier.addPolled(poller, "/bar", NT_NOTIFY_NEW) notifier.addPolled(poller, "/bar", NT_NOTIFY_DELETE) notifier.addPolled(poller, "/bar", NT_NOTIFY_UPDATE) notifier.addPolled(poller, "/bar", NT_NOTIFY_FLAGS) assert not notifier.m_local_notifiers generateNotifications(notifier) assert notifier.waitForQueue(1.0) results, timed_out = notifier.poll(poller, 0) assert not timed_out g1count = 0 g2count = 0 g3count = 0 g4count = 0 for h, result in results: print(result) assert result.name.startswith("/foo") assert result.value == Value.makeDouble(1) if h == g1: g1count += 1 assert (result.flags & NT_NOTIFY_NEW) != 0 elif h == g2: g2count += 1 assert (result.flags & NT_NOTIFY_DELETE) != 0 elif h == g3: g3count += 1 assert (result.flags & NT_NOTIFY_UPDATE) != 0 elif h == g4: g4count += 1 assert (result.flags & NT_NOTIFY_FLAGS) != 0 else: assert False, "unknown listener index" assert g1count == 2 assert g2count == 1 # NT_NOTIFY_DELETE assert g3count == 2 assert g4count == 2
def test_ProcessIncomingEntryAssign1(storage_populate_one, dispatcher, entry_notifier, is_server, conn): storage = storage_populate_one value = Value.makeDouble(1.0) storage.processIncoming(Message.entryAssign("foo", 0, 1, value, 0), conn) # EXPECT_CALL(*conn, proto_rev()).WillRepeatedly(Return(0x0300u)) if is_server: # server broadcasts new value to all *other* connections dispatcher._queueOutgoing.assert_has_calls( [call(Message.entryAssign("foo", 0, 1, value, 0), None, conn)]) else: assert dispatcher._queueOutgoing.call_count == 0 entry_notifier.notifyEntry.assert_has_calls( [call(0, "foo", value, NT_NOTIFY_UPDATE)])
def test_ProcessIncomingEntryAssign0(storage_empty, dispatcher, entry_notifier, is_server, conn): storage = storage_empty value = Value.makeDouble(1.0) entry_id = 0xFFFF if is_server else 0 storage.processIncoming(Message.entryAssign("foo", entry_id, 0, value, 0), conn) if is_server: # id assign message reply generated on the server sent to everyone dispatcher._queueOutgoing.assert_has_calls( [call(Message.entryAssign("foo", 0, 0, value, 0), None, None)]) else: assert dispatcher._queueOutgoing.call_count == 0 entry_notifier.notifyEntry.assert_has_calls( [call(0, "foo", value, NT_NOTIFY_NEW)])
def test_SetEntryTypeValueAssignTypeChange(storage_populate_one, dispatcher, entry_notifier, is_server): storage = storage_populate_one # update with different type results in assignment message value = Value.makeDouble(0.0) storage.setEntryTypeValue("foo", value) assert value == storage.m_entries.get("foo").value dispatcher._queueOutgoing.assert_has_calls([ call( Message.entryAssign("foo", 0 if is_server else 0xFFFF, 2, value, 0), None, None, ) ]) entry_notifier.notifyEntry.assert_has_calls( [call(0, "foo", value, NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL)])
def test_LoadPersistentUpdateFlags(storage_populated, dispatcher, entry_notifier, is_server): storage = storage_populated fp = StringIO('[NetworkTables Storage 3.0]\ndouble "foo2"=0.0\n') assert storage.loadPersistent(fp=fp) is None entry = storage.m_entries.get("foo2") assert Value.makeDouble(0.0) == entry.value assert NT_PERSISTENT == entry.flags assert entry.isPersistent if is_server: dispatcher._queueOutgoing.assert_has_calls( [call(Message.flagsUpdate(1, NT_PERSISTENT), None, None)]) else: assert dispatcher._queueOutgoing.call_count == 0 entry_notifier.notifyEntry.assert_has_calls( [call(1, "foo2", entry.value, NT_NOTIFY_FLAGS | NT_NOTIFY_LOCAL)])
def test_SetEntryTypeValueDifferentValue(storage_populated, dispatcher, entry_notifier, is_server): storage = storage_populated # update with same type and different value results in value update message value = Value.makeDouble(1.0) storage.setEntryTypeValue("foo2", value) assert value == storage.m_entries.get("foo2").value if is_server: # id assigned if server; seq_num incremented dispatcher._queueOutgoing.assert_has_calls( [call(Message.entryUpdate(1, 2, value), None, None)]) entry_notifier.notifyEntry.assert_has_calls( [call(1, "foo2", value, NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL)]) if not is_server: # shouldn't send an update id not assigned yet (happens on client only) assert dispatcher._queueOutgoing.call_count == 0 assert 2 == storage.m_entries.get( "foo2").seq_num # still should be incremented
def test_ProcessIncomingEntryAssignWithFlags(storage_populate_one, dispatcher, entry_notifier, is_server, conn): storage = storage_populate_one value = Value.makeDouble(1.0) storage.processIncoming(Message.entryAssign("foo", 0, 1, value, 0x2), conn) # EXPECT_CALL(*conn, proto_rev()).WillRepeatedly(Return(0x0300u)) if is_server: # server broadcasts new value/flags to all *other* connections dispatcher._queueOutgoing.assert_has_calls( [call(Message.entryAssign("foo", 0, 1, value, 0x2), None, conn)]) entry_notifier.notifyEntry.assert_has_calls( [call(0, "foo", value, NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS)]) else: # client forces flags back when an assign message is received for an # existing entry with different flags dispatcher._queueOutgoing.assert_has_calls( [call(Message.flagsUpdate(0, 0), None, None)]) entry_notifier.notifyEntry.assert_has_calls( [call(0, "foo", value, NT_NOTIFY_UPDATE)])
def test_DoubleComparison(): v1 = Value.makeDouble(0.25) v2 = Value.makeDouble(0.25) assert v1 == v2 v2 = Value.makeDouble(0.5) assert v1 != v2
def test_MixedComparison(): v2 = Value.makeBoolean(True) v3 = Value.makeDouble(0.5) assert v2 != v3 # boolean vs double
def test_loadPersistent(storage_empty, dispatcher, entry_notifier): storage = storage_empty inp = "[NetworkTables Storage 3.0]\n" inp += 'boolean "\\x00\\x03\\x05\\n"=true\n' inp += 'boolean "CaseSensitive/KeyName"=true\n' inp += 'boolean "boolean/false"=false\n' inp += 'boolean "boolean/true"=true\n' inp += 'array boolean "booleanarr/empty"=\n' inp += 'array boolean "booleanarr/one"=true\n' inp += 'array boolean "booleanarr/two"=true,false\n' inp += 'double "double/big"=1.3e+08\n' inp += 'double "double/neg"=-1.5\n' inp += 'double "double/zero"=0\n' inp += 'array double "doublearr/empty"=\n' inp += 'array double "doublearr/one"=0.5\n' inp += 'array double "doublearr/two"=0.5,-0.25\n' inp += 'raw "raw/empty"=\n' inp += 'raw "raw/normal"=aGVsbG8=\n' inp += 'raw "raw/special"=AAMFCg==\n' inp += 'string "string/empty"=""\n' inp += 'string "string/normal"="hello"\n' inp += 'string "string/special"="\\x00\\x03\\x05\\n"\n' inp += 'string "string/quoted"="\\"a\\""\n' inp += 'array string "stringarr/empty"=\n' inp += 'array string "stringarr/one"="hello"\n' inp += 'array string "stringarr/two"="hello","world\\n"\n' fp = StringIO(inp) assert storage.loadPersistent(fp=fp) is None dispatcher._queueOutgoing.assert_has_calls([call(ANY, None, None)] * 23) entry_notifier.notifyEntry.assert_has_calls( [call(ANY, ANY, ANY, NT_NOTIFY_NEW | NT_NOTIFY_LOCAL)] * 23) assert Value.makeBoolean(True) == storage.getEntryValue("boolean/true") assert Value.makeBoolean(False) == storage.getEntryValue("boolean/false") assert Value.makeDouble(-1.5) == storage.getEntryValue("double/neg") assert Value.makeDouble(0.0) == storage.getEntryValue("double/zero") assert Value.makeDouble(1.3e8) == storage.getEntryValue("double/big") assert Value.makeString("") == storage.getEntryValue("string/empty") assert Value.makeString("hello") == storage.getEntryValue("string/normal") assert Value.makeString("\0\3\5\n") == storage.getEntryValue( "string/special") assert Value.makeString('"a"') == storage.getEntryValue("string/quoted") assert Value.makeRaw(b"") == storage.getEntryValue("raw/empty") assert Value.makeRaw(b"hello") == storage.getEntryValue("raw/normal") assert Value.makeRaw(b"\0\3\5\n") == storage.getEntryValue("raw/special") assert Value.makeBooleanArray( []) == storage.getEntryValue("booleanarr/empty") assert Value.makeBooleanArray( [True]) == storage.getEntryValue("booleanarr/one") assert Value.makeBooleanArray( [True, False]) == storage.getEntryValue("booleanarr/two") assert Value.makeDoubleArray( []) == storage.getEntryValue("doublearr/empty") assert Value.makeDoubleArray([0.5 ]) == storage.getEntryValue("doublearr/one") assert Value.makeDoubleArray([0.5, -0.25 ]) == storage.getEntryValue("doublearr/two") assert Value.makeStringArray( []) == storage.getEntryValue("stringarr/empty") assert Value.makeStringArray(["hello" ]) == storage.getEntryValue("stringarr/one") assert Value.makeStringArray(["hello", "world\n" ]) == storage.getEntryValue("stringarr/two") assert Value.makeBoolean(True) == storage.getEntryValue("\0\3\5\n") assert Value.makeBoolean(True) == storage.getEntryValue( "CaseSensitive/KeyName")
def test_wire_double(v_round_trip): v_round_trip(Value.makeDouble(0.5))