buffet: Fix issues in StateChangeQueue

Addressed additional code review issues brought after the original
code has been published.

- NotifyPropertiesUpdated() now takes timestamp and property set
  as separate parameters to simplify implementation of StateManager.
- StateChangeQueue::max_queue_size_ is made 'const'
- StateChangeQueue now maintains a time-to-event map instead of
  a vector of StateChange. This allows to keep the events sorted
  by time stamp. Also adding discrete events with the same time stamp
  coalesces the property changes.
- added a unit test for coalescing property set changes based on
  the timestamp.

BUG=None
TEST=FEATURES=test emerge-link buffet

Change-Id: I309816d1f040558620fa68a844b05251d0e4319b
Reviewed-on: https://chromium-review.googlesource.com/226300
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Reviewed-by: Anton Muhin <antonm@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/states/state_change_queue_unittest.cc b/buffet/states/state_change_queue_unittest.cc
index 8205f77..7aa6c9f 100644
--- a/buffet/states/state_change_queue_unittest.cc
+++ b/buffet/states/state_change_queue_unittest.cc
@@ -28,63 +28,111 @@
 }
 
 TEST_F(StateChangeQueueTest, UpdateOne) {
-  StateChange change;
-  change.timestamp = base::Time::Now();
-  change.property_set.emplace("prop.name", int{23});
-  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change));
+  StateChange change{
+    base::Time::Now(),
+    chromeos::VariantDictionary{{"prop.name", int{23}}}
+  };
+  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change.timestamp,
+                                              change.changed_properties));
   EXPECT_FALSE(queue_->IsEmpty());
   auto changes = queue_->GetAndClearRecordedStateChanges();
   ASSERT_EQ(1, changes.size());
   EXPECT_EQ(change.timestamp, changes.front().timestamp);
-  EXPECT_EQ(change.property_set, changes.front().property_set);
+  EXPECT_EQ(change.changed_properties, changes.front().changed_properties);
   EXPECT_TRUE(queue_->IsEmpty());
   EXPECT_TRUE(queue_->GetAndClearRecordedStateChanges().empty());
 }
 
 TEST_F(StateChangeQueueTest, UpdateMany) {
-  StateChange change1;
-  change1.timestamp = base::Time::Now();
-  change1.property_set.emplace("prop.name1", int{23});
-  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change1));
-  StateChange change2;
-  change2.timestamp = base::Time::Now();
-  change2.property_set.emplace("prop.name1", int{17});
-  change2.property_set.emplace("prop.name2", double{1.0});
-  change2.property_set.emplace("prop.name3", bool{false});
-  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change2));
+  StateChange change1{
+    base::Time::Now(),
+    chromeos::VariantDictionary{{"prop.name1", int{23}}}
+  };
+  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change1.timestamp,
+                                              change1.changed_properties));
+  StateChange change2{
+    base::Time::Now(),
+    chromeos::VariantDictionary{
+      {"prop.name1", int{17}},
+      {"prop.name2", double{1.0}},
+      {"prop.name3", bool{false}},
+    }
+  };
+  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change2.timestamp,
+                                              change2.changed_properties));
   EXPECT_FALSE(queue_->IsEmpty());
   auto changes = queue_->GetAndClearRecordedStateChanges();
   ASSERT_EQ(2, changes.size());
-  EXPECT_EQ(change1.timestamp, changes.front().timestamp);
-  EXPECT_EQ(change1.property_set, changes.front().property_set);
-  EXPECT_EQ(change2.timestamp, changes.back().timestamp);
-  EXPECT_EQ(change2.property_set, changes.back().property_set);
+  EXPECT_EQ(change1.timestamp, changes[0].timestamp);
+  EXPECT_EQ(change1.changed_properties, changes[0].changed_properties);
+  EXPECT_EQ(change2.timestamp, changes[1].timestamp);
+  EXPECT_EQ(change2.changed_properties, changes[1].changed_properties);
   EXPECT_TRUE(queue_->IsEmpty());
   EXPECT_TRUE(queue_->GetAndClearRecordedStateChanges().empty());
 }
 
+TEST_F(StateChangeQueueTest, GroupByTimestamp) {
+  base::Time timestamp = base::Time::Now();
+  base::TimeDelta time_delta = base::TimeDelta::FromMinutes(1);
+
+  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
+      timestamp,
+      chromeos::VariantDictionary{{"prop.name1", int{1}}}));
+
+  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
+      timestamp,
+      chromeos::VariantDictionary{{"prop.name2", int{2}}}));
+
+  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
+      timestamp,
+      chromeos::VariantDictionary{{"prop.name1", int{3}}}));
+
+  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
+      timestamp + time_delta,
+      chromeos::VariantDictionary{{"prop.name1", int{4}}}));
+
+  auto changes = queue_->GetAndClearRecordedStateChanges();
+  ASSERT_EQ(2, changes.size());
+
+  chromeos::VariantDictionary expected1{
+    {"prop.name1", int{3}},
+    {"prop.name2", int{2}},
+  };
+  chromeos::VariantDictionary expected2{
+    {"prop.name1", int{4}},
+  };
+  EXPECT_EQ(timestamp, changes[0].timestamp);
+  EXPECT_EQ(expected1, changes[0].changed_properties);
+  EXPECT_EQ(timestamp + time_delta, changes[1].timestamp);
+  EXPECT_EQ(expected2, changes[1].changed_properties);
+}
+
 TEST_F(StateChangeQueueTest, MaxQueueSize) {
   queue_.reset(new StateChangeQueue(2));
   base::Time start_time = base::Time::Now();
-  base::TimeDelta time_delta = base::TimeDelta::FromMinutes(1);
+  base::TimeDelta time_delta1 = base::TimeDelta::FromMinutes(1);
+  base::TimeDelta time_delta2 = base::TimeDelta::FromMinutes(3);
 
-  StateChange change;
-  change.timestamp = start_time;
-  change.property_set.emplace("prop.name1", int{1});
-  change.property_set.emplace("prop.name2", int{2});
-  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change));
+  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
+      start_time,
+      chromeos::VariantDictionary{
+        {"prop.name1", int{1}},
+        {"prop.name2", int{2}},
+      }));
 
-  change.timestamp += time_delta;
-  change.property_set.clear();
-  change.property_set.emplace("prop.name1", int{3});
-  change.property_set.emplace("prop.name3", int{4});
-  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change));
+  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
+      start_time + time_delta1,
+      chromeos::VariantDictionary{
+        {"prop.name1", int{3}},
+        {"prop.name3", int{4}},
+      }));
 
-  change.timestamp += time_delta;
-  change.property_set.clear();
-  change.property_set.emplace("prop.name10", int{10});
-  change.property_set.emplace("prop.name11", int{11});
-  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change));
+  ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
+      start_time + time_delta2,
+      chromeos::VariantDictionary{
+        {"prop.name10", int{10}},
+        {"prop.name11", int{11}},
+      }));
 
   auto changes = queue_->GetAndClearRecordedStateChanges();
   ASSERT_EQ(2, changes.size());
@@ -94,15 +142,15 @@
     {"prop.name2", int{2}},
     {"prop.name3", int{4}},
   };
-  EXPECT_EQ(start_time + time_delta, changes.front().timestamp);
-  EXPECT_EQ(expected1, changes.front().property_set);
+  EXPECT_EQ(start_time + time_delta1, changes[0].timestamp);
+  EXPECT_EQ(expected1, changes[0].changed_properties);
 
   chromeos::VariantDictionary expected2{
     {"prop.name10", int{10}},
     {"prop.name11", int{11}},
   };
-  EXPECT_EQ(start_time + 2 * time_delta, changes.back().timestamp);
-  EXPECT_EQ(expected2, changes.back().property_set);
+  EXPECT_EQ(start_time + time_delta2, changes[1].timestamp);
+  EXPECT_EQ(expected2, changes[1].changed_properties);
 }
 
 }  // namespace buffet