diff --git a/test/unit/StorageTest.cpp b/test/unit/StorageTest.cpp index 28f8dd9..0e1b8fd 100644 --- a/test/unit/StorageTest.cpp +++ b/test/unit/StorageTest.cpp @@ -7,7 +7,10 @@ #include "Storage.h" +#include + #include "gtest/gtest.h" +#include "gmock/gmock.h" namespace nt { @@ -37,6 +40,55 @@ class StorageTestPopulated : public StorageTest { } }; +class StorageTestPersistent : public StorageTest { + public: + StorageTestPersistent() { + 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(StringRef(""))); + storage.SetEntryTypeValue("string/normal", + Value::MakeString(StringRef("hello"))); + storage.SetEntryTypeValue("string/special", + Value::MakeString(StringRef("\0\3\5\n", 4))); + storage.SetEntryTypeValue("raw/empty", Value::MakeRaw(StringRef(""))); + storage.SetEntryTypeValue("raw/normal", Value::MakeRaw(StringRef("hello"))); + storage.SetEntryTypeValue("raw/special", + Value::MakeRaw(StringRef("\0\3\5\n", 4))); + storage.SetEntryTypeValue("booleanarr/empty", + Value::MakeBooleanArray(std::vector{})); + storage.SetEntryTypeValue("booleanarr/one", + Value::MakeBooleanArray(std::vector{1})); + storage.SetEntryTypeValue("booleanarr/two", + Value::MakeBooleanArray(std::vector{1, 0})); + storage.SetEntryTypeValue("doublearr/empty", + Value::MakeDoubleArray(std::vector{})); + storage.SetEntryTypeValue("doublearr/one", + Value::MakeDoubleArray(std::vector{0.5})); + storage.SetEntryTypeValue( + "doublearr/two", + Value::MakeDoubleArray(std::vector{0.5, -0.25})); + storage.SetEntryTypeValue( + "stringarr/empty", Value::MakeStringArray(std::vector{})); + storage.SetEntryTypeValue( + "stringarr/one", + Value::MakeStringArray(std::vector{"hello"})); + storage.SetEntryTypeValue( + "stringarr/two", + Value::MakeStringArray(std::vector{"hello", "world\n"})); + storage.SetEntryTypeValue(StringRef("\0\3\5\n", 4), + Value::MakeBoolean(true)); + while (!updates().empty()) updates().pop(); + } +}; + +class MockLoadWarn { + public: + MOCK_METHOD2(Warn, void(std::size_t line, const char* msg)); +}; + TEST_F(StorageTest, Construct) { ASSERT_TRUE(entries().empty()); ASSERT_TRUE(updates().empty()); @@ -309,4 +361,268 @@ TEST_F(StorageTestPopulated, GetEntryInfoPrefixTypes) { EXPECT_EQ(NT_BOOLEAN, info[0].type); } +TEST_F(StorageTestPersistent, SavePersistentEmpty) { + std::ostringstream oss; + storage.SavePersistent(oss); + ASSERT_EQ("[NetworkTables Storage 3.0]\n", oss.str()); +} + +TEST_F(StorageTestPersistent, SavePersistent) { + for (auto& i : entries()) i.getValue()->set_flags(NT_PERSISTENT); + std::ostringstream oss; + storage.SavePersistent(oss); + std::string out = oss.str(); + //fputs(out.c_str(), stderr); + llvm::StringRef line, rem = out; + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("[NetworkTables Storage 3.0]", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("boolean \"\\x00\\x03\\x05\\n\"=true", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("boolean \"boolean/false\"=false", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("boolean \"boolean/true\"=true", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("array boolean \"booleanarr/empty\"=", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("array boolean \"booleanarr/one\"=true", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("array boolean \"booleanarr/two\"=true,false", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("double \"double/big\"=1.3e+08", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("double \"double/neg\"=-1.5", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("double \"double/zero\"=0", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("array double \"doublearr/empty\"=", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("array double \"doublearr/one\"=0.5", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("array double \"doublearr/two\"=0.5,-0.25", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("raw \"raw/empty\"=", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("raw \"raw/normal\"=aGVsbG8=", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("raw \"raw/special\"=AAMFCg==", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("string \"string/empty\"=\"\"", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("string \"string/normal\"=\"hello\"", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("string \"string/special\"=\"\\x00\\x03\\x05\\n\"", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("array string \"stringarr/empty\"=", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("array string \"stringarr/one\"=\"hello\"", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("array string \"stringarr/two\"=\"hello\",\"world\\n\"", line); + std::tie(line, rem) = rem.split('\n'); + ASSERT_EQ("", line); +} + +TEST_F(StorageTest, LoadPersistentBadHeader) { + MockLoadWarn warn; + auto warn_func = + [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + + std::istringstream iss(""); + EXPECT_CALL(warn, Warn(1, "header line mismatch, ignoring rest of file")); + EXPECT_FALSE(storage.LoadPersistent(iss, warn_func)); + + std::istringstream iss2("[NetworkTables"); + EXPECT_CALL(warn, Warn(1, "header line mismatch, ignoring rest of file")); + EXPECT_FALSE(storage.LoadPersistent(iss2, warn_func)); + ASSERT_TRUE(entries().empty()); + ASSERT_TRUE(updates().empty()); +} + +TEST_F(StorageTest, LoadPersistentCommentHeader) { + MockLoadWarn warn; + auto warn_func = + [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + + std::istringstream iss( + "\n; comment\n# comment\n[NetworkTables Storage 3.0]\n"); + EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); + ASSERT_TRUE(entries().empty()); + ASSERT_TRUE(updates().empty()); +} + +TEST_F(StorageTest, LoadPersistentEmptyName) { + MockLoadWarn warn; + auto warn_func = + [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + + std::istringstream iss( + "[NetworkTables Storage 3.0]\nboolean \"\"=true\n"); + EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); + ASSERT_TRUE(entries().empty()); + ASSERT_TRUE(updates().empty()); +} + +TEST_F(StorageTest, LoadPersistentAssign) { + MockLoadWarn warn; + auto warn_func = + [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + + std::istringstream iss( + "[NetworkTables Storage 3.0]\nboolean \"foo\"=true\n"); + EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); + auto entry = storage.GetEntry("foo"); + EXPECT_EQ(*Value::MakeBoolean(true), *entry->value()); + EXPECT_EQ(NT_PERSISTENT, entry->flags()); + ASSERT_EQ(1u, updates().size()); + auto update = updates().pop(); + EXPECT_EQ(entry, update.entry); + EXPECT_EQ(Storage::Update::kAssign, update.kind); +} + +TEST_F(StorageTestPopulateOne, LoadPersistentUpdateFlags) { + MockLoadWarn warn; + auto warn_func = + [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + + std::istringstream iss( + "[NetworkTables Storage 3.0]\nboolean \"foo\"=true\n"); + EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); + auto entry = storage.GetEntry("foo"); + EXPECT_EQ(*Value::MakeBoolean(true), *entry->value()); + EXPECT_EQ(NT_PERSISTENT, entry->flags()); + ASSERT_EQ(1u, updates().size()); + auto update = updates().pop(); + EXPECT_EQ(entry, update.entry); + EXPECT_EQ(Storage::Update::kFlagsUpdate, update.kind); +} + +TEST_F(StorageTestPopulateOne, LoadPersistentUpdateValue) { + MockLoadWarn warn; + auto warn_func = + [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + + storage.GetEntry("foo")->set_flags(NT_PERSISTENT); + while (!updates().empty()) updates().pop(); + + std::istringstream iss( + "[NetworkTables Storage 3.0]\nboolean \"foo\"=false\n"); + EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); + auto entry = storage.GetEntry("foo"); + EXPECT_EQ(*Value::MakeBoolean(false), *entry->value()); + EXPECT_EQ(NT_PERSISTENT, entry->flags()); + ASSERT_EQ(1u, updates().size()); + auto update = updates().pop(); + EXPECT_EQ(entry, update.entry); + EXPECT_EQ(Storage::Update::kValueUpdate, update.kind); +} + +TEST_F(StorageTestPopulateOne, LoadPersistentUpdateValueFlags) { + MockLoadWarn warn; + auto warn_func = + [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + + std::istringstream iss( + "[NetworkTables Storage 3.0]\nboolean \"foo\"=false\n"); + EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); + auto entry = storage.GetEntry("foo"); + EXPECT_EQ(*Value::MakeBoolean(false), *entry->value()); + EXPECT_EQ(NT_PERSISTENT, entry->flags()); + ASSERT_EQ(2u, updates().size()); + auto update = updates().pop(); + EXPECT_EQ(entry, update.entry); + EXPECT_EQ(Storage::Update::kValueUpdate, update.kind); + update = updates().pop(); + EXPECT_EQ(entry, update.entry); + EXPECT_EQ(Storage::Update::kFlagsUpdate, update.kind); +} + +TEST_F(StorageTest, LoadPersistent) { + MockLoadWarn warn; + auto warn_func = + [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + + std::string in = "[NetworkTables Storage 3.0]\n"; + in += "boolean \"\\x00\\x03\\x05\\n\"=true\n"; + in += "boolean \"boolean/false\"=false\n"; + in += "boolean \"boolean/true\"=true\n"; + in += "array boolean \"booleanarr/empty\"=\n"; + in += "array boolean \"booleanarr/one\"=true\n"; + in += "array boolean \"booleanarr/two\"=true,false\n"; + in += "double \"double/big\"=1.3e+08\n"; + in += "double \"double/neg\"=-1.5\n"; + in += "double \"double/zero\"=0\n"; + in += "array double \"doublearr/empty\"=\n"; + in += "array double \"doublearr/one\"=0.5\n"; + in += "array double \"doublearr/two\"=0.5,-0.25\n"; + in += "raw \"raw/empty\"=\n"; + in += "raw \"raw/normal\"=aGVsbG8=\n"; + in += "raw \"raw/special\"=AAMFCg==\n"; + in += "string \"string/empty\"=\"\"\n"; + in += "string \"string/normal\"=\"hello\"\n"; + in += "string \"string/special\"=\"\\x00\\x03\\x05\\n\"\n"; + in += "array string \"stringarr/empty\"=\n"; + in += "array string \"stringarr/one\"=\"hello\"\n"; + in += "array string \"stringarr/two\"=\"hello\",\"world\\n\"\n"; + + std::istringstream iss(in); + EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); + ASSERT_EQ(21u, entries().size()); + ASSERT_EQ(21u, updates().size()); + + EXPECT_EQ(*Value::MakeBoolean(true), *storage.GetEntryValue("boolean/true")); + EXPECT_EQ(*Value::MakeBoolean(false), + *storage.GetEntryValue("boolean/false")); + EXPECT_EQ(*Value::MakeDouble(-1.5), *storage.GetEntryValue("double/neg")); + EXPECT_EQ(*Value::MakeDouble(0.0), *storage.GetEntryValue("double/zero")); + EXPECT_EQ(*Value::MakeDouble(1.3e8), *storage.GetEntryValue("double/big")); + EXPECT_EQ(*Value::MakeString(StringRef("")), + *storage.GetEntryValue("string/empty")); + EXPECT_EQ(*Value::MakeString(StringRef("hello")), + *storage.GetEntryValue("string/normal")); + EXPECT_EQ(*Value::MakeString(StringRef("\0\3\5\n", 4)), + *storage.GetEntryValue("string/special")); + EXPECT_EQ(*Value::MakeRaw(StringRef("")), + *storage.GetEntryValue("raw/empty")); + EXPECT_EQ(*Value::MakeRaw(StringRef("hello")), + *storage.GetEntryValue("raw/normal")); + EXPECT_EQ(*Value::MakeRaw(StringRef("\0\3\5\n", 4)), + *storage.GetEntryValue("raw/special")); + EXPECT_EQ(*Value::MakeBooleanArray(std::vector{}), + *storage.GetEntryValue("booleanarr/empty")); + EXPECT_EQ(*Value::MakeBooleanArray(std::vector{1}), + *storage.GetEntryValue("booleanarr/one")); + EXPECT_EQ(*Value::MakeBooleanArray(std::vector{1, 0}), + *storage.GetEntryValue("booleanarr/two")); + EXPECT_EQ(*Value::MakeDoubleArray(std::vector{}), + *storage.GetEntryValue("doublearr/empty")); + EXPECT_EQ(*Value::MakeDoubleArray(std::vector{0.5}), + *storage.GetEntryValue("doublearr/one")); + EXPECT_EQ(*Value::MakeDoubleArray(std::vector{0.5, -0.25}), + *storage.GetEntryValue("doublearr/two")); + EXPECT_EQ(*Value::MakeStringArray(std::vector{}), + *storage.GetEntryValue("stringarr/empty")); + EXPECT_EQ(*Value::MakeStringArray(std::vector{"hello"}), + *storage.GetEntryValue("stringarr/one")); + EXPECT_EQ( + *Value::MakeStringArray(std::vector{"hello", "world\n"}), + *storage.GetEntryValue("stringarr/two")); + EXPECT_EQ(*Value::MakeBoolean(true), + *storage.GetEntryValue(StringRef("\0\3\5\n", 4))); +} + +TEST_F(StorageTest, LoadPersistentWarn) { + MockLoadWarn warn; + auto warn_func = + [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + + std::istringstream iss( + "[NetworkTables Storage 3.0]\nboolean \"foo\"=foo\n"); + EXPECT_CALL(warn, + Warn(2, "unrecognized boolean value, not 'true' or 'false'")); + EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); + + ASSERT_TRUE(entries().empty()); + ASSERT_TRUE(updates().empty()); +} + } // namespace nt