Add support for state updates on ComponentManager
Implemented state support functionality from StateManager. Now the
ComponentManager object can replace StateManager, StatePackage,
CommandDictionary and CommandManager classes.
Most implementation was borrowed/adapted from existing StateManager
and StatePackage classes. And the interfaces were made compatible
with those two classes so the new ComponentManager can be used as
a drop-in replacement of the old classes.
BUG: 25841719
Change-Id: Ib981de1cf44c47a95594c56d78ac211812b3fa44
Reviewed-on: https://weave-review.googlesource.com/1780
Reviewed-by: Vitaly Buka <vitalybuka@google.com>
diff --git a/src/component_manager_unittest.cc b/src/component_manager_unittest.cc
index ba6bc34..83f9933 100644
--- a/src/component_manager_unittest.cc
+++ b/src/component_manager_unittest.cc
@@ -30,6 +30,71 @@
return false;
}
+// Creates sample trait/component trees:
+// {
+// "traits": {
+// "t1": {},
+// "t2": {},
+// "t3": {},
+// "t4": {},
+// "t5": {},
+// "t6": {},
+// },
+// "components": {
+// "comp1": {
+// "traits": [ "t1" ],
+// "components": {
+// "comp2": [
+// { "traits": [ "t2" ] },
+// {
+// "traits": [ "t3" ],
+// "components": {
+// "comp3": {
+// "traits": [ "t4" ],
+// "components": {
+// "comp4": {
+// "traits": [ "t5", "t6" ]
+// }
+// }
+// }
+// }
+// }
+// ],
+// }
+// }
+// }
+// }
+void CreateTestComponentTree(ComponentManager* manager) {
+ const char kTraits[] = R"({"t1":{},"t2":{},"t3":{},"t4":{},"t5":{},"t6":{}})";
+ auto json = CreateDictionaryValue(kTraits);
+ ASSERT_TRUE(manager->LoadTraits(*json, nullptr));
+ EXPECT_TRUE(manager->AddComponent("", "comp1", {"t1"}, nullptr));
+ EXPECT_TRUE(manager->AddComponentArrayItem("comp1", "comp2", {"t2"},
+ nullptr));
+ EXPECT_TRUE(manager->AddComponentArrayItem("comp1", "comp2", {"t3"},
+ nullptr));
+ EXPECT_TRUE(manager->AddComponent("comp1.comp2[1]", "comp3", {"t4"},
+ nullptr));
+ EXPECT_TRUE(manager->AddComponent("comp1.comp2[1].comp3", "comp4",
+ {"t5", "t6"}, nullptr));
+}
+
+// Test clock class to record predefined time intervals.
+// Implementation from base/test/simple_test_clock.{h|cc}
+class SimpleTestClock : public base::Clock {
+ public:
+ base::Time Now() override { return now_; }
+
+ // Advances the clock by |delta|.
+ void Advance(base::TimeDelta delta) { now_ += delta; }
+
+ // Sets the clock to the given time.
+ void SetNow(base::Time now) { now_ = now; }
+
+ private:
+ base::Time now_;
+};
+
} // anonymous namespace
TEST(ComponentManager, Empty) {
@@ -176,6 +241,63 @@
EXPECT_FALSE(manager.LoadTraits(*json, nullptr));
}
+TEST(ComponentManager, AddTraitDefChangedCallback) {
+ ComponentManager manager;
+ int count = 0;
+ int count2 = 0;
+ manager.AddTraitDefChangedCallback(base::Bind([&count]() { count++; }));
+ manager.AddTraitDefChangedCallback(base::Bind([&count2]() { count2++; }));
+ EXPECT_EQ(1, count);
+ EXPECT_EQ(1, count2);
+ // New definitions.
+ const char kTraits1[] = R"({
+ "trait1": {
+ "state": {
+ "property1": {"type": "boolean"}
+ }
+ },
+ "trait2": {
+ "state": {
+ "property2": {"type": "string"}
+ }
+ }
+ })";
+ auto json = CreateDictionaryValue(kTraits1);
+ EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
+ EXPECT_EQ(2, count);
+ // Duplicate definition, shouldn't call the callback.
+ const char kTraits2[] = R"({
+ "trait1": {
+ "state": {
+ "property1": {"type": "boolean"}
+ }
+ }
+ })";
+ json = CreateDictionaryValue(kTraits2);
+ EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
+ EXPECT_EQ(2, count);
+ // New definition, should call the callback now.
+ const char kTraits3[] = R"({
+ "trait3": {
+ "state": {
+ "property3": {"type": "string"}
+ }
+ }
+ })";
+ json = CreateDictionaryValue(kTraits3);
+ EXPECT_TRUE(manager.LoadTraits(*json, nullptr));
+ EXPECT_EQ(3, count);
+ // Wrong definition, shouldn't call the callback.
+ const char kTraits4[] = R"({
+ "trait4": "foo"
+ })";
+ json = CreateDictionaryValue(kTraits4);
+ EXPECT_FALSE(manager.LoadTraits(*json, nullptr));
+ EXPECT_EQ(3, count);
+ // Make sure both callbacks were called the same number of times.
+ EXPECT_EQ(count2, count);
+}
+
TEST(ComponentManager, LoadTraitsNotAnObject) {
ComponentManager manager;
const char kTraits1[] = R"({"trait1": 0})";
@@ -426,18 +548,32 @@
EXPECT_FALSE(manager.AddComponent("comp1", "comp2", {}, nullptr));
}
+TEST(ComponentManager, AddComponentTreeChangedCallback) {
+ ComponentManager manager;
+ int count = 0;
+ int count2 = 0;
+ manager.AddComponentTreeChangedCallback(base::Bind([&count]() { count++; }));
+ manager.AddComponentTreeChangedCallback(
+ base::Bind([&count2]() { count2++; }));
+ EXPECT_EQ(1, count);
+ EXPECT_EQ(1, count2);
+ EXPECT_TRUE(manager.AddComponent("", "comp1", {}, nullptr));
+ EXPECT_EQ(2, count);
+ EXPECT_TRUE(manager.AddComponent("comp1", "comp2", {}, nullptr));
+ EXPECT_EQ(3, count);
+ EXPECT_TRUE(manager.AddComponent("comp1.comp2", "comp4", {}, nullptr));
+ EXPECT_EQ(4, count);
+ EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp3", {}, nullptr));
+ EXPECT_EQ(5, count);
+ EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp3", {}, nullptr));
+ EXPECT_EQ(6, count);
+ // Make sure both callbacks were called the same number of times.
+ EXPECT_EQ(count2, count);
+}
+
TEST(ComponentManager, FindComponent) {
ComponentManager manager;
- const char kTraits[] = R"({"t1":{}, "t2":{}, "t3":{}, "t4":{}, "t5":{}})";
- auto json = CreateDictionaryValue(kTraits);
- ASSERT_TRUE(manager.LoadTraits(*json, nullptr));
-
- EXPECT_TRUE(manager.AddComponent("", "comp1", {"t1"}, nullptr));
- EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp2", {"t2"}, nullptr));
- EXPECT_TRUE(manager.AddComponentArrayItem("comp1", "comp2", {"t3"}, nullptr));
- EXPECT_TRUE(manager.AddComponent("comp1.comp2[1]", "comp3", {"t4"}, nullptr));
- EXPECT_TRUE(manager.AddComponent("comp1.comp2[1].comp3", "comp4", {"t5"},
- nullptr));
+ CreateTestComponentTree(&manager);
const base::DictionaryValue* comp = manager.FindComponent("comp1", nullptr);
ASSERT_NE(nullptr, comp);
@@ -606,4 +742,347 @@
last_tags.clear();
}
+TEST(ComponentManager, SetStateProperties) {
+ ComponentManager manager;
+ CreateTestComponentTree(&manager);
+
+ const char kState1[] = R"({"t1": {"p1": 0, "p2": "foo"}})";
+ auto state1 = CreateDictionaryValue(kState1);
+ ASSERT_TRUE(manager.SetStateProperties("comp1", *state1, nullptr));
+ const char kExpected1[] = R"({
+ "comp1": {
+ "traits": [ "t1" ],
+ "state": {"t1": {"p1": 0, "p2": "foo"}},
+ "components": {
+ "comp2": [
+ {
+ "traits": [ "t2" ]
+ },
+ {
+ "traits": [ "t3" ],
+ "components": {
+ "comp3": {
+ "traits": [ "t4" ],
+ "components": {
+ "comp4": {
+ "traits": [ "t5", "t6" ]
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ })";
+ EXPECT_JSON_EQ(kExpected1, manager.GetComponents());
+
+ const char kState2[] = R"({"t1": {"p1": {"bar": "baz"}}})";
+ auto state2 = CreateDictionaryValue(kState2);
+ ASSERT_TRUE(manager.SetStateProperties("comp1", *state2, nullptr));
+
+ const char kExpected2[] = R"({
+ "comp1": {
+ "traits": [ "t1" ],
+ "state": {"t1": {"p1": {"bar": "baz"}, "p2": "foo"}},
+ "components": {
+ "comp2": [
+ {
+ "traits": [ "t2" ]
+ },
+ {
+ "traits": [ "t3" ],
+ "components": {
+ "comp3": {
+ "traits": [ "t4" ],
+ "components": {
+ "comp4": {
+ "traits": [ "t5", "t6" ]
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ })";
+ EXPECT_JSON_EQ(kExpected2, manager.GetComponents());
+
+ const char kState3[] = R"({"t5": {"p1": 1}})";
+ auto state3 = CreateDictionaryValue(kState3);
+ ASSERT_TRUE(manager.SetStateProperties("comp1.comp2[1].comp3.comp4", *state3,
+ nullptr));
+
+ const char kExpected3[] = R"({
+ "comp1": {
+ "traits": [ "t1" ],
+ "state": {"t1": {"p1": {"bar": "baz"}, "p2": "foo"}},
+ "components": {
+ "comp2": [
+ {
+ "traits": [ "t2" ]
+ },
+ {
+ "traits": [ "t3" ],
+ "components": {
+ "comp3": {
+ "traits": [ "t4" ],
+ "components": {
+ "comp4": {
+ "traits": [ "t5", "t6" ],
+ "state": { "t5": { "p1": 1 } }
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ })";
+ EXPECT_JSON_EQ(kExpected3, manager.GetComponents());
+}
+
+TEST(ComponentManager, SetStatePropertiesFromJson) {
+ ComponentManager manager;
+ CreateTestComponentTree(&manager);
+
+ ASSERT_TRUE(manager.SetStatePropertiesFromJson(
+ "comp1.comp2[1].comp3.comp4", R"({"t5": {"p1": 3}, "t6": {"p2": 5}})",
+ nullptr));
+
+ const char kExpected[] = R"({
+ "comp1": {
+ "traits": [ "t1" ],
+ "components": {
+ "comp2": [
+ {
+ "traits": [ "t2" ]
+ },
+ {
+ "traits": [ "t3" ],
+ "components": {
+ "comp3": {
+ "traits": [ "t4" ],
+ "components": {
+ "comp4": {
+ "traits": [ "t5", "t6" ],
+ "state": {
+ "t5": { "p1": 3 },
+ "t6": { "p2": 5 }
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ })";
+ EXPECT_JSON_EQ(kExpected, manager.GetComponents());
+}
+
+TEST(ComponentManager, SetGetStateProperty) {
+ ComponentManager manager;
+ const char kTraits[] = R"({
+ "trait1": {
+ "state": {
+ "prop1": { "type": "string" },
+ "prop2": { "type": "integer" }
+ }
+ },
+ "trait2": {
+ "state": {
+ "prop3": { "type": "string" },
+ "prop4": { "type": "string" }
+ }
+ }
+ })";
+ auto traits = CreateDictionaryValue(kTraits);
+ ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
+ ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1", "trait2"}, nullptr));
+
+ base::StringValue p1("foo");
+ ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop1", p1, nullptr));
+
+ const char kExpected1[] = R"({
+ "comp1": {
+ "traits": [ "trait1", "trait2" ],
+ "state": {
+ "trait1": { "prop1": "foo" }
+ }
+ }
+ })";
+ EXPECT_JSON_EQ(kExpected1, manager.GetComponents());
+
+ base::FundamentalValue p2(2);
+ ASSERT_TRUE(manager.SetStateProperty("comp1", "trait2.prop3", p2, nullptr));
+
+ const char kExpected2[] = R"({
+ "comp1": {
+ "traits": [ "trait1", "trait2" ],
+ "state": {
+ "trait1": { "prop1": "foo" },
+ "trait2": { "prop3": 2 }
+ }
+ }
+ })";
+ EXPECT_JSON_EQ(kExpected2, manager.GetComponents());
+ // Just the package name without property:
+ EXPECT_FALSE(manager.SetStateProperty("comp1", "trait2", p2, nullptr));
+
+ const base::Value* value = manager.GetStateProperty("comp1", "trait1.prop1",
+ nullptr);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(p1.Equals(value));
+ value = manager.GetStateProperty("comp1", "trait2.prop3", nullptr);
+ ASSERT_NE(nullptr, value);
+ EXPECT_TRUE(p2.Equals(value));
+
+ // Non-existing property:
+ EXPECT_EQ(nullptr, manager.GetStateProperty("comp1", "trait2.p", nullptr));
+ // Non-existing component
+ EXPECT_EQ(nullptr, manager.GetStateProperty("comp2", "trait.prop", nullptr));
+ // Just the package name without property:
+ EXPECT_EQ(nullptr, manager.GetStateProperty("comp1", "trait2", nullptr));
+}
+
+TEST(ComponentManager, AddStateChangedCallback) {
+ SimpleTestClock clock;
+ ComponentManager manager{&clock};
+ const char kTraits[] = R"({
+ "trait1": {
+ "state": {
+ "prop1": { "type": "string" },
+ "prop2": { "type": "string" }
+ }
+ }
+ })";
+ auto traits = CreateDictionaryValue(kTraits);
+ ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
+ ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1"}, nullptr));
+
+ int count = 0;
+ int count2 = 0;
+ manager.AddStateChangedCallback(base::Bind([&count]() { count++; }));
+ manager.AddStateChangedCallback(base::Bind([&count2]() { count2++; }));
+ EXPECT_EQ(1, count);
+ EXPECT_EQ(1, count2);
+ EXPECT_EQ(0, manager.GetLastStateChangeId());
+
+ base::StringValue p1("foo");
+ ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop1", p1, nullptr));
+ EXPECT_EQ(2, count);
+ EXPECT_EQ(2, count2);
+ EXPECT_EQ(1, manager.GetLastStateChangeId());
+
+ ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop2", p1, nullptr));
+ EXPECT_EQ(3, count);
+ EXPECT_EQ(3, count2);
+ EXPECT_EQ(2, manager.GetLastStateChangeId());
+
+ // Fail - no component.
+ ASSERT_FALSE(manager.SetStateProperty("comp2", "trait1.prop2", p1, nullptr));
+ EXPECT_EQ(3, count);
+ EXPECT_EQ(3, count2);
+ EXPECT_EQ(2, manager.GetLastStateChangeId());
+}
+
+TEST(ComponentManager, ComponentStateUpdates) {
+ SimpleTestClock clock;
+ ComponentManager manager{&clock};
+ const char kTraits[] = R"({
+ "trait1": {
+ "state": {
+ "prop1": { "type": "string" },
+ "prop2": { "type": "string" }
+ }
+ },
+ "trait2": {
+ "state": {
+ "prop3": { "type": "string" },
+ "prop4": { "type": "string" }
+ }
+ }
+ })";
+ auto traits = CreateDictionaryValue(kTraits);
+ ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
+ ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1", "trait2"}, nullptr));
+ ASSERT_TRUE(manager.AddComponent("", "comp2", {"trait1", "trait2"}, nullptr));
+
+ std::vector<ComponentManager::UpdateID> updates1;
+ auto callback1 = [&updates1](ComponentManager::UpdateID id) {
+ updates1.push_back(id);
+ };
+ // State change queue is empty, callback should be called immediately.
+ auto token1 = manager.AddServerStateUpdatedCallback(base::Bind(callback1));
+ ASSERT_EQ(1u, updates1.size());
+ EXPECT_EQ(manager.GetLastStateChangeId(), updates1.front());
+ updates1.clear();
+
+ base::StringValue foo("foo");
+ base::Time time1 = base::Time::Now();
+ clock.SetNow(time1);
+ // These three updates should be grouped into two separate state change queue
+ // items, since they all happen at the same time, but for two different
+ // components.
+ ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop1", foo, nullptr));
+ ASSERT_TRUE(manager.SetStateProperty("comp2", "trait2.prop3", foo, nullptr));
+ ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop2", foo, nullptr));
+
+ std::vector<ComponentManager::UpdateID> updates2;
+ auto callback2 = [&updates2](ComponentManager::UpdateID id) {
+ updates2.push_back(id);
+ };
+ // State change queue is not empty, so callback will be called later.
+ auto token2 = manager.AddServerStateUpdatedCallback(base::Bind(callback2));
+ EXPECT_TRUE(updates2.empty());
+
+ base::StringValue bar("bar");
+ base::Time time2 = time1 + base::TimeDelta::FromSeconds(1);
+ clock.SetNow(time2);
+ // Two more update events (as above) but at |time2|.
+ ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop1", bar, nullptr));
+ ASSERT_TRUE(manager.SetStateProperty("comp2", "trait2.prop3", bar, nullptr));
+ ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop2", bar, nullptr));
+
+ auto snapshot = manager.GetAndClearRecordedStateChanges();
+ EXPECT_EQ(manager.GetLastStateChangeId(), snapshot.update_id);
+ ASSERT_EQ(4u, snapshot.state_changes.size());
+
+ EXPECT_EQ("comp1", snapshot.state_changes[0].component);
+ EXPECT_EQ(time1, snapshot.state_changes[0].timestamp);
+ EXPECT_JSON_EQ(R"({"trait1":{"prop1":"foo","prop2":"foo"}})",
+ *snapshot.state_changes[0].changed_properties);
+
+ EXPECT_EQ("comp2", snapshot.state_changes[1].component);
+ EXPECT_EQ(time1, snapshot.state_changes[1].timestamp);
+ EXPECT_JSON_EQ(R"({"trait2":{"prop3":"foo"}})",
+ *snapshot.state_changes[1].changed_properties);
+
+ EXPECT_EQ("comp1", snapshot.state_changes[2].component);
+ EXPECT_EQ(time2, snapshot.state_changes[2].timestamp);
+ EXPECT_JSON_EQ(R"({"trait1":{"prop1":"bar","prop2":"bar"}})",
+ *snapshot.state_changes[2].changed_properties);
+
+ EXPECT_EQ("comp2", snapshot.state_changes[3].component);
+ EXPECT_EQ(time2, snapshot.state_changes[3].timestamp);
+ EXPECT_JSON_EQ(R"({"trait2":{"prop3":"bar"}})",
+ *snapshot.state_changes[3].changed_properties);
+
+ // Make sure previous GetAndClearRecordedStateChanges() clears the queue.
+ auto snapshot2 = manager.GetAndClearRecordedStateChanges();
+ EXPECT_EQ(manager.GetLastStateChangeId(), snapshot2.update_id);
+ EXPECT_TRUE(snapshot2.state_changes.empty());
+
+ // Now indicate that we have update the changes on the server.
+ manager.NotifyStateUpdatedOnServer(snapshot.update_id);
+ ASSERT_EQ(1u, updates1.size());
+ EXPECT_EQ(snapshot.update_id, updates1.front());
+ ASSERT_EQ(1u, updates2.size());
+ EXPECT_EQ(snapshot.update_id, updates2.front());
+}
+
} // namespace weave