Handle offset change in PhasedLoop and PhasedLoopHandler

When the PhasedLoop offset_ is changed, the last_time_ must be adjusted
to have an even interval. Additionally, in the PhaseLoopHandler Call
method, schedule has to be called after fn_ because if fn_ changes
the offset then it will not be updated on the next callback since
schedule has already scheduled the next time.

Change-Id: I12110134f00ee948f986e0df9f8304fbc2b08c31
Signed-off-by: milind <milind.upadhyay@gmail.com>
diff --git a/aos/events/event_loop_param_test.cc b/aos/events/event_loop_param_test.cc
index ee77b8a..0e9ad4c 100644
--- a/aos/events/event_loop_param_test.cc
+++ b/aos/events/event_loop_param_test.cc
@@ -1549,6 +1549,103 @@
   }
 }
 
+// Tests that a phased loop responds correctly to a changing offset.
+TEST_P(AbstractEventLoopTest, PhasedLoopChangingOffsetTest) {
+  // Force a slower rate so we are guaranteed to have reports for our phased
+  // loop.
+  FLAGS_timing_report_ms = 2000;
+
+  const chrono::milliseconds kOffset = chrono::milliseconds(400);
+  const chrono::milliseconds kInterval = chrono::milliseconds(1000);
+  const int kCount = 5;
+
+  auto loop1 = MakePrimary();
+
+  // Collect up a couple of samples.
+  ::std::vector<::aos::monotonic_clock::time_point> times;
+  ::std::vector<::aos::monotonic_clock::time_point> expected_times;
+
+  PhasedLoopHandler *phased_loop;
+
+  // Run kCount iterations.
+  phased_loop = loop1->AddPhasedLoop(
+      [&phased_loop, &times, &expected_times, &loop1, this, kOffset,
+       kInterval](int count) {
+        EXPECT_EQ(count, 1);
+        times.push_back(loop1->monotonic_now());
+
+        expected_times.push_back(loop1->context().monotonic_event_time);
+
+        phased_loop->set_interval_and_offset(
+            kInterval, kOffset - chrono::milliseconds(times.size()));
+        LOG(INFO) << "new offset: "
+                  << (kOffset - chrono::milliseconds(times.size())).count();
+
+        if (times.size() == kCount) {
+          LOG(INFO) << "Exiting";
+          this->Exit();
+        }
+      },
+      kInterval, kOffset);
+  phased_loop->set_name("Test loop");
+
+  // Add a delay to make sure that delay during startup doesn't result in a
+  // "missed cycle".
+  SleepFor(chrono::seconds(2));
+
+  Run();
+  // Confirm that we got both the right number of samples, and it's odd.
+  EXPECT_EQ(times.size(), static_cast<size_t>(kCount));
+  EXPECT_EQ(times.size(), expected_times.size());
+  EXPECT_EQ((times.size() % 2), 1);
+
+  // Grab the middle sample.
+  ::aos::monotonic_clock::time_point average_time = times[times.size() / 2];
+
+  // Add up all the delays of all the times.
+  ::aos::monotonic_clock::duration sum = chrono::seconds(0);
+  for (const ::aos::monotonic_clock::time_point time : times) {
+    sum += time - average_time;
+  }
+
+  // Average and add to the middle to find the average time.
+  sum /= times.size();
+  average_time += sum;
+
+  // Compute the offset from the start of the second of the average time.  This
+  // should be pretty close to the offset.
+  const ::aos::monotonic_clock::duration remainder =
+      average_time.time_since_epoch() -
+      chrono::duration_cast<chrono::seconds>(average_time.time_since_epoch());
+
+  const chrono::milliseconds kEpsilon(100);
+  EXPECT_LT(remainder, kOffset + kEpsilon);
+  EXPECT_GT(remainder, kOffset - kEpsilon);
+
+  // Make sure that the average duration is close to 1 second.
+  EXPECT_NEAR(chrono::duration_cast<chrono::duration<double>>(times.back() -
+                                                              times.front())
+                      .count() /
+                  static_cast<double>(times.size() - 1),
+              1.0, 0.1);
+
+  // Confirm that the ideal wakeup times increment correctly.
+  for (size_t i = 1; i < expected_times.size(); ++i) {
+    LOG(INFO) << i - 1 << ": " << expected_times[i - 1] << ", " << i << ": "
+              << expected_times[i];
+    EXPECT_EQ(expected_times[i], expected_times[i - 1] + chrono::seconds(1) -
+                                     chrono::milliseconds(1));
+  }
+
+  for (size_t i = 0; i < expected_times.size(); ++i) {
+    EXPECT_EQ(expected_times[i].time_since_epoch() % chrono::seconds(1),
+              kOffset - chrono::milliseconds(i));
+  }
+
+  EXPECT_LT(expected_times[expected_times.size() / 2], average_time + kEpsilon);
+  EXPECT_GT(expected_times[expected_times.size() / 2], average_time - kEpsilon);
+}
+
 // Tests that senders count correctly in the timing report.
 TEST_P(AbstractEventLoopTest, SenderTimingReport) {
   FLAGS_timing_report_ms = 1000;