Implement independent instances.

Previously, most of the classes were implemented as singletons so only one
instance was possible.

This change adds an instance handle-based API.  In Java, this API is located
in a different package than the old API (edu.wpi.first.networktables).

Backwards compatibility with ITable and the old NetworkTable API is largely
maintained, but a handful of classes have moved to the new package in Java
(ConnectionInfo and PersistentException), and the old JNI has been completed
replaced.

Also:
- Move SetTeam implementation to Dispatcher.
- Consistently pass time through Java and C++ Value API.
- Rename nt_Value.h to NetworkTableValue.h for consistency with Java.
- Improve documentation
- Make C++ and Java APIs more consistent
- Document RPC functions and support RPC in Java.
- Add polling features for entry and connection listeners and use them to
  move callback threads to Java level.
- Remove thread start and stop hooks (as polling is available).
- Make Notifiers, RpcServer, Dispatcher, and Storage mockable.
- Set NOTIFY_NEW on immediate entry notifications.
- Make GetTable("/") and GetTable("") equivalent.
- Generate local notification for flags update when loading persistent file.

And many unit test updates/changes:
- Use InitGoogleMock instead of InitGoogleTest in test main.
- Move test printers to TestPrinter.h/cpp.
- Provide printers for StringRef, EntryNotifier, and Handle.
- StorageTest: Check notifications.
- Add entry notifier unit tests.
- Storage: Add test for incoming entry assignment.
- Update connection listener tests.
- Add entry listener unit tests.

Fixes #11, #140, #189, #190, #192, #193, #221
This commit is contained in:
Peter Johnson
2017-04-23 10:26:17 -07:00
parent 8125a179fb
commit 5ab20bb27c
118 changed files with 15638 additions and 4640 deletions

View File

@@ -0,0 +1,316 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2017. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "EntryNotifier.h"
#include "gtest/gtest.h"
#include "support/Logger.h"
#include "TestPrinters.h"
#include "ValueMatcher.h"
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::IsNull;
using ::testing::Return;
namespace nt {
class EntryNotifierTest : public ::testing::Test {
public:
EntryNotifierTest() : notifier(1, logger) { notifier.Start(); }
void GenerateNotifications();
protected:
wpi::Logger logger;
EntryNotifier notifier;
};
void EntryNotifierTest::GenerateNotifications() {
// All flags combos that can be generated by Storage
static const unsigned int 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
static const char* keys[] = {"/foo/bar", "/baz", "/boo"};
auto val = Value::MakeDouble(1);
// Provide unique key indexes for each key
unsigned int keyindex = 5;
for (auto key : keys) {
for (auto flag : flags) {
notifier.NotifyEntry(keyindex, key, val, flag);
}
++keyindex;
}
}
TEST_F(EntryNotifierTest, PollEntryMultiple) {
auto poller1 = notifier.CreatePoller();
auto poller2 = notifier.CreatePoller();
auto poller3 = notifier.CreatePoller();
auto h1 = notifier.AddPolled(poller1, 6, NT_NOTIFY_NEW);
auto h2 = notifier.AddPolled(poller2, 6, NT_NOTIFY_NEW);
auto h3 = notifier.AddPolled(poller3, 6, NT_NOTIFY_UPDATE);
ASSERT_FALSE(notifier.local_notifiers());
GenerateNotifications();
ASSERT_TRUE(notifier.WaitForQueue(1.0));
bool timed_out = false;
auto results1 = notifier.Poll(poller1, 0, &timed_out);
ASSERT_FALSE(timed_out);
auto results2 = notifier.Poll(poller2, 0, &timed_out);
ASSERT_FALSE(timed_out);
auto results3 = notifier.Poll(poller3, 0, &timed_out);
ASSERT_FALSE(timed_out);
ASSERT_EQ(results1.size(), 2u);
for (const auto& result : results1) {
SCOPED_TRACE(::testing::PrintToString(result));
EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h1);
}
ASSERT_EQ(results2.size(), 2u);
for (const auto& result : results2) {
SCOPED_TRACE(::testing::PrintToString(result));
EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h2);
}
ASSERT_EQ(results3.size(), 2u);
for (const auto& result : results3) {
SCOPED_TRACE(::testing::PrintToString(result));
EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h3);
}
}
TEST_F(EntryNotifierTest, PollEntryBasic) {
auto poller = notifier.CreatePoller();
auto g1 = notifier.AddPolled(poller, 6, NT_NOTIFY_NEW);
auto g2 = notifier.AddPolled(poller, 6, NT_NOTIFY_DELETE);
auto g3 = notifier.AddPolled(poller, 6, NT_NOTIFY_UPDATE);
auto g4 = notifier.AddPolled(poller, 6, NT_NOTIFY_FLAGS);
ASSERT_FALSE(notifier.local_notifiers());
GenerateNotifications();
ASSERT_TRUE(notifier.WaitForQueue(1.0));
bool timed_out = false;
auto results = notifier.Poll(poller, 0, &timed_out);
ASSERT_FALSE(timed_out);
int g1count = 0;
int g2count = 0;
int g3count = 0;
int g4count = 0;
for (const auto& result : results) {
SCOPED_TRACE(::testing::PrintToString(result));
EXPECT_EQ(result.name, "/baz");
EXPECT_THAT(result.value, ValueEq(Value::MakeDouble(1)));
EXPECT_EQ(Handle{result.entry}.GetType(), Handle::kEntry);
EXPECT_EQ(Handle{result.entry}.GetInst(), 1);
EXPECT_EQ(Handle{result.entry}.GetIndex(), 6);
EXPECT_EQ(Handle{result.listener}.GetType(), Handle::kEntryListener);
EXPECT_EQ(Handle{result.listener}.GetInst(), 1);
if (Handle{result.listener}.GetIndex() == (int)g1) {
++g1count;
EXPECT_TRUE((result.flags & NT_NOTIFY_NEW) != 0);
} else if (Handle{result.listener}.GetIndex() == (int)g2) {
++g2count;
EXPECT_TRUE((result.flags & NT_NOTIFY_DELETE) != 0);
} else if (Handle{result.listener}.GetIndex() == (int)g3) {
++g3count;
EXPECT_TRUE((result.flags & NT_NOTIFY_UPDATE) != 0);
} else if (Handle{result.listener}.GetIndex() == (int)g4) {
++g4count;
EXPECT_TRUE((result.flags & NT_NOTIFY_FLAGS) != 0);
} else {
ADD_FAILURE() << "unknown listener index";
}
}
EXPECT_EQ(g1count, 2);
EXPECT_EQ(g2count, 1); // NT_NOTIFY_DELETE
EXPECT_EQ(g3count, 2);
EXPECT_EQ(g4count, 2);
}
TEST_F(EntryNotifierTest, PollEntryImmediate) {
auto poller = notifier.CreatePoller();
notifier.AddPolled(poller, 6, NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE);
notifier.AddPolled(poller, 6, NT_NOTIFY_NEW);
ASSERT_FALSE(notifier.local_notifiers());
GenerateNotifications();
ASSERT_TRUE(notifier.WaitForQueue(1.0));
bool timed_out = false;
auto results = notifier.Poll(poller, 0, &timed_out);
ASSERT_FALSE(timed_out);
SCOPED_TRACE(::testing::PrintToString(results));
ASSERT_EQ(results.size(), 4u);
}
TEST_F(EntryNotifierTest, PollEntryLocal) {
auto poller = notifier.CreatePoller();
notifier.AddPolled(poller, 6, NT_NOTIFY_NEW | NT_NOTIFY_LOCAL);
notifier.AddPolled(poller, 6, NT_NOTIFY_NEW);
ASSERT_TRUE(notifier.local_notifiers());
GenerateNotifications();
ASSERT_TRUE(notifier.WaitForQueue(1.0));
bool timed_out = false;
auto results = notifier.Poll(poller, 0, &timed_out);
ASSERT_FALSE(timed_out);
SCOPED_TRACE(::testing::PrintToString(results));
ASSERT_EQ(results.size(), 6u);
}
TEST_F(EntryNotifierTest, PollPrefixMultiple) {
auto poller1 = notifier.CreatePoller();
auto poller2 = notifier.CreatePoller();
auto poller3 = notifier.CreatePoller();
auto h1 = notifier.AddPolled(poller1, "/foo", NT_NOTIFY_NEW);
auto h2 = notifier.AddPolled(poller2, "/foo", NT_NOTIFY_NEW);
auto h3 = notifier.AddPolled(poller3, "/foo", NT_NOTIFY_UPDATE);
ASSERT_FALSE(notifier.local_notifiers());
GenerateNotifications();
ASSERT_TRUE(notifier.WaitForQueue(1.0));
bool timed_out = false;
auto results1 = notifier.Poll(poller1, 0, &timed_out);
ASSERT_FALSE(timed_out);
auto results2 = notifier.Poll(poller2, 0, &timed_out);
ASSERT_FALSE(timed_out);
auto results3 = notifier.Poll(poller3, 0, &timed_out);
ASSERT_FALSE(timed_out);
ASSERT_EQ(results1.size(), 2u);
for (const auto& result : results1) {
SCOPED_TRACE(::testing::PrintToString(result));
EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h1);
}
ASSERT_EQ(results2.size(), 2u);
for (const auto& result : results2) {
SCOPED_TRACE(::testing::PrintToString(result));
EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h2);
}
ASSERT_EQ(results3.size(), 2u);
for (const auto& result : results3) {
SCOPED_TRACE(::testing::PrintToString(result));
EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h3);
}
}
TEST_F(EntryNotifierTest, PollPrefixBasic) {
auto poller = notifier.CreatePoller();
auto g1 = notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW);
auto g2 = notifier.AddPolled(poller, "/foo", NT_NOTIFY_DELETE);
auto g3 = notifier.AddPolled(poller, "/foo", NT_NOTIFY_UPDATE);
auto 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_FALSE(notifier.local_notifiers());
GenerateNotifications();
ASSERT_TRUE(notifier.WaitForQueue(1.0));
bool timed_out = false;
auto results = notifier.Poll(poller, 0, &timed_out);
ASSERT_FALSE(timed_out);
int g1count = 0;
int g2count = 0;
int g3count = 0;
int g4count = 0;
for (const auto& result : results) {
SCOPED_TRACE(::testing::PrintToString(result));
EXPECT_TRUE(StringRef(result.name).startswith("/foo"));
EXPECT_THAT(result.value, ValueEq(Value::MakeDouble(1)));
EXPECT_EQ(Handle{result.entry}.GetType(), Handle::kEntry);
EXPECT_EQ(Handle{result.entry}.GetInst(), 1);
EXPECT_EQ(Handle{result.entry}.GetIndex(), 5);
EXPECT_EQ(Handle{result.listener}.GetType(), Handle::kEntryListener);
EXPECT_EQ(Handle{result.listener}.GetInst(), 1);
if (Handle{result.listener}.GetIndex() == (int)g1) {
++g1count;
EXPECT_TRUE((result.flags & NT_NOTIFY_NEW) != 0);
} else if (Handle{result.listener}.GetIndex() == (int)g2) {
++g2count;
EXPECT_TRUE((result.flags & NT_NOTIFY_DELETE) != 0);
} else if (Handle{result.listener}.GetIndex() == (int)g3) {
++g3count;
EXPECT_TRUE((result.flags & NT_NOTIFY_UPDATE) != 0);
} else if (Handle{result.listener}.GetIndex() == (int)g4) {
++g4count;
EXPECT_TRUE((result.flags & NT_NOTIFY_FLAGS) != 0);
} else {
ADD_FAILURE() << "unknown listener index";
}
}
EXPECT_EQ(g1count, 2);
EXPECT_EQ(g2count, 1); // NT_NOTIFY_DELETE
EXPECT_EQ(g3count, 2);
EXPECT_EQ(g4count, 2);
}
TEST_F(EntryNotifierTest, PollPrefixImmediate) {
auto poller = notifier.CreatePoller();
notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE);
notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW);
ASSERT_FALSE(notifier.local_notifiers());
GenerateNotifications();
ASSERT_TRUE(notifier.WaitForQueue(1.0));
bool timed_out = false;
auto results = notifier.Poll(poller, 0, &timed_out);
ASSERT_FALSE(timed_out);
SCOPED_TRACE(::testing::PrintToString(results));
ASSERT_EQ(results.size(), 4u);
}
TEST_F(EntryNotifierTest, PollPrefixLocal) {
auto poller = notifier.CreatePoller();
notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW | NT_NOTIFY_LOCAL);
notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW);
ASSERT_TRUE(notifier.local_notifiers());
GenerateNotifications();
ASSERT_TRUE(notifier.WaitForQueue(1.0));
bool timed_out = false;
auto results = notifier.Poll(poller, 0, &timed_out);
ASSERT_FALSE(timed_out);
SCOPED_TRACE(::testing::PrintToString(results));
ASSERT_EQ(results.size(), 6u);
}
} // namespace nt