shill: Add cellular hooks for suspend and termination

When ChromeOS suspends or shill terminates, a cellular device should
disconnect from the cellular service.  This CL adds methods to do that
and connects them to the manager's hook table.  The manager runs these
actions when there is a terminate signal.  The shill Daemon has been
modified to signal the manager on a terminate signal.  TODO: Run these
actions on a suspend signal.

This CL modifies the HookTable API to remove the polling action.
Instead, actions are expected to call HookTable::ActionComplete()
to signal the completion of an action.

BUG=chromium-os:22408
TEST=new unittests; ran all existing unittests.  Manual testing
includes executing 'stop shill' and looking at log messages to make
sure the termination actions occurred.

Change-Id: I5bbf0832e76a5c818724fbca4c436beb3e5d182b
Reviewed-on: https://gerrit.chromium.org/gerrit/24261
Reviewed-by: Jason Glasgow <jglasgow@chromium.org>
Tested-by: Gary Morain <gmorain@chromium.org>
Commit-Ready: Gary Morain <gmorain@chromium.org>
diff --git a/hook_table_unittest.cc b/hook_table_unittest.cc
index bb3ff14..74fc621 100644
--- a/hook_table_unittest.cc
+++ b/hook_table_unittest.cc
@@ -4,18 +4,22 @@
 
 #include "shill/hook_table.h"
 
+#include <string>
+
 #include <base/bind.h>
 #include <base/callback.h>
+#include <base/message_loop.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include "shill/error.h"
-#include "shill/mock_event_dispatcher.h"
+#include "shill/event_dispatcher.h"
 
 using base::Callback;
 using base::Closure;
 using base::Bind;
 using base::Unretained;
+using std::string;
 using ::testing::_;
 using ::testing::InSequence;
 using ::testing::Return;
@@ -25,18 +29,21 @@
 
 class HookTableTest : public testing::Test {
  public:
+  static const char kName[];
   MOCK_METHOD0(StartAction, void());
-  MOCK_METHOD0(PollAction, bool());
+  MOCK_METHOD0(StartAction2, void());
   MOCK_METHOD1(DoneAction, void(const Error &));
 
  protected:
   HookTableTest()
       : hook_table_(&event_dispatcher_) {}
 
-  MockEventDispatcher event_dispatcher_;
+  EventDispatcher event_dispatcher_;
   HookTable hook_table_;
 };
 
+const char HookTableTest::kName[] = "test";
+
 MATCHER(IsSuccess, "") {
   return arg.IsSuccess();
 }
@@ -45,116 +52,186 @@
   return arg.IsFailure();
 }
 
-TEST_F(HookTableTest, ActionCompletesImmediately) {
+TEST_F(HookTableTest, ActionCompletes) {
   EXPECT_CALL(*this, StartAction());
-  EXPECT_CALL(*this, PollAction()).WillOnce(Return(true));
   EXPECT_CALL(*this, DoneAction(IsSuccess()));
-  EXPECT_CALL(event_dispatcher_, PostDelayedTask(_, _)).Times(0);
   Closure start_cb = Bind(&HookTableTest::StartAction, Unretained(this));
-  Callback<bool()> poll_cb = Bind(&HookTableTest::PollAction, Unretained(this));
   Callback<void(const Error &)> done_cb = Bind(&HookTableTest::DoneAction,
                                                Unretained(this));
-  hook_table_.Add("test", start_cb, poll_cb);
+  hook_table_.Add(kName, start_cb);
   hook_table_.Run(0, done_cb);
+  hook_table_.ActionComplete(kName);
+
+  // Ensure that the timeout callback got cancelled.  If it did not get
+  // cancelled, done_cb will be run twice and make this test fail.
+  event_dispatcher_.DispatchPendingEvents();
 }
 
-TEST_F(HookTableTest, DelayedAction) {
-  Closure pending_cb;
-  const int kTimeout = 10;
-  const int64 kExpectedDelay = kTimeout * 1000 / HookTable::kPollIterations;
-  EXPECT_CALL(*this, StartAction());
-  {
-    InSequence s;
-    EXPECT_CALL(*this, PollAction()).WillOnce(Return(false));
-    EXPECT_CALL(event_dispatcher_, PostDelayedTask(_, kExpectedDelay))
-        .WillOnce(SaveArg<0>(&pending_cb));
-    EXPECT_CALL(*this, PollAction()).WillOnce(Return(true));
-    EXPECT_CALL(*this, DoneAction(IsSuccess()));
-  }
+ACTION_P2(CompleteAction, hook_table, name) {
+  hook_table->ActionComplete(name);
+}
+
+TEST_F(HookTableTest, ActionCompletesInline) {
+  // StartAction completes immediately before HookTable::Run() returns.
+  EXPECT_CALL(*this, StartAction())
+      .WillOnce(CompleteAction(&hook_table_, kName));
+  EXPECT_CALL(*this, DoneAction(IsSuccess()));
   Closure start_cb = Bind(&HookTableTest::StartAction, Unretained(this));
-  Callback<bool()> poll_cb = Bind(&HookTableTest::PollAction, Unretained(this));
   Callback<void(const Error &)> done_cb = Bind(&HookTableTest::DoneAction,
                                                Unretained(this));
+  hook_table_.Add(kName, start_cb);
+  hook_table_.Run(0, done_cb);
 
-  hook_table_.Add("test", start_cb, poll_cb);
-  hook_table_.Run(kTimeout, done_cb);
-  ASSERT_FALSE(pending_cb.is_null());
-  pending_cb.Run();
+  // Ensure that the timeout callback got cancelled.  If it did not get
+  // cancelled, done_cb will be run twice and make this test fail.
+  event_dispatcher_.DispatchPendingEvents();
 }
 
 TEST_F(HookTableTest, ActionTimesOut) {
-  Closure pending_cb;
-  const int kTimeout = 10;
-  const int64 kExpectedDelay = kTimeout * 1000 / HookTable::kPollIterations;
+  const int kTimeout = 1;
   EXPECT_CALL(*this, StartAction());
-  EXPECT_CALL(*this, PollAction()).WillRepeatedly(Return(false));
-  EXPECT_CALL(event_dispatcher_, PostDelayedTask(_, kExpectedDelay))
-      .Times(HookTable::kPollIterations)
-      .WillRepeatedly(SaveArg<0>(&pending_cb));
   EXPECT_CALL(*this, DoneAction(IsFailure()));
 
   Closure start_cb = Bind(&HookTableTest::StartAction, Unretained(this));
-  Callback<bool()> poll_cb = Bind(&HookTableTest::PollAction, Unretained(this));
   Callback<void(const Error &)> done_cb = Bind(&HookTableTest::DoneAction,
                                                Unretained(this));
 
-  hook_table_.Add("test", start_cb, poll_cb);
+  hook_table_.Add(kName, start_cb);
   hook_table_.Run(kTimeout, done_cb);
-  for (int i = 0; i < HookTable::kPollIterations; ++i) {
-    ASSERT_FALSE(pending_cb.is_null());
-    pending_cb.Run();
-  }
+
+  // Cause the event dispatcher to exit after kTimeout + 1 ms.
+  event_dispatcher_.PostDelayedTask(MessageLoop::QuitClosure(),
+                                    kTimeout * + 1);
+  event_dispatcher_.DispatchForever();
 }
 
 TEST_F(HookTableTest, MultipleActionsAllSucceed) {
+  const string kName1 = "test1";
+  const string kName2 = "test2";
+  const string kName3 = "test3";
   Closure pending_cb;
   const int kTimeout = 10;
-  EXPECT_CALL(*this, StartAction()).Times(3);
-  EXPECT_CALL(*this, PollAction())
-      .Times(3)
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(event_dispatcher_, PostDelayedTask(_, _)).Times(0);
+  EXPECT_CALL(*this, StartAction()).Times(2);
+
+  // StartAction2 completes immediately before HookTable::Run() returns.
+  EXPECT_CALL(*this, StartAction2())
+      .WillOnce(CompleteAction(&hook_table_, kName1));
   EXPECT_CALL(*this, DoneAction(IsSuccess()));
 
   Closure start_cb = Bind(&HookTableTest::StartAction, Unretained(this));
-  Callback<bool()> poll_cb = Bind(&HookTableTest::PollAction, Unretained(this));
+  Closure start2_cb = Bind(&HookTableTest::StartAction2, Unretained(this));
   Callback<void(const Error &)> done_cb = Bind(&HookTableTest::DoneAction,
                                                Unretained(this));
 
-  hook_table_.Add("test1", start_cb, poll_cb);
-  hook_table_.Add("test2", start_cb, poll_cb);
-  hook_table_.Add("test3", start_cb, poll_cb);
+  hook_table_.Add(kName1, start2_cb);
+  hook_table_.Add(kName2, start_cb);
+  hook_table_.Add(kName3, start_cb);
   hook_table_.Run(kTimeout, done_cb);
+  hook_table_.ActionComplete(kName2);
+  hook_table_.ActionComplete(kName3);
 }
 
 TEST_F(HookTableTest, MultipleActionsAndOneTimesOut) {
+  const string kName1 = "test1";
+  const string kName2 = "test2";
+  const string kName3 = "test3";
   Closure pending_cb;
-  const int kTimeout = 10;
-  const int64 kExpectedDelay = kTimeout * 1000 / HookTable::kPollIterations;
+  const int kTimeout = 1;
   EXPECT_CALL(*this, StartAction()).Times(3);
-  EXPECT_CALL(*this, PollAction())
-      .Times((HookTable::kPollIterations + 1) * 3)
-      .WillOnce(Return(true))
-      .WillOnce(Return(true))
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(event_dispatcher_, PostDelayedTask(_, kExpectedDelay))
-      .Times(HookTable::kPollIterations)
-      .WillRepeatedly(SaveArg<0>(&pending_cb));
   EXPECT_CALL(*this, DoneAction(IsFailure()));
 
   Closure start_cb = Bind(&HookTableTest::StartAction, Unretained(this));
-  Callback<bool()> poll_cb = Bind(&HookTableTest::PollAction, Unretained(this));
   Callback<void(const Error &)> done_cb = Bind(&HookTableTest::DoneAction,
                                                Unretained(this));
 
-  hook_table_.Add("test1", start_cb, poll_cb);
-  hook_table_.Add("test2", start_cb, poll_cb);
-  hook_table_.Add("test3", start_cb, poll_cb);
+  hook_table_.Add(kName1, start_cb);
+  hook_table_.Add(kName2, start_cb);
+  hook_table_.Add(kName3, start_cb);
   hook_table_.Run(kTimeout, done_cb);
-  for (int i = 0; i < HookTable::kPollIterations; ++i) {
-    ASSERT_FALSE(pending_cb.is_null());
-    pending_cb.Run();
+  hook_table_.ActionComplete(kName1);
+  hook_table_.ActionComplete(kName3);
+  // Cause the event dispatcher to exit after kTimeout + 1 ms.
+  event_dispatcher_.PostDelayedTask(MessageLoop::QuitClosure(),
+                                    kTimeout + 1);
+  event_dispatcher_.DispatchForever();
+}
+
+TEST_F(HookTableTest, AddActionsWithSameName) {
+  EXPECT_CALL(*this, StartAction()).Times(0);
+  EXPECT_CALL(*this, StartAction2());
+  EXPECT_CALL(*this, DoneAction(IsSuccess()));
+  Closure start_cb = Bind(&HookTableTest::StartAction, Unretained(this));
+  Closure start2_cb = Bind(&HookTableTest::StartAction2, Unretained(this));
+  Callback<void(const Error &)> done_cb = Bind(&HookTableTest::DoneAction,
+                                               Unretained(this));
+  hook_table_.Add(kName, start_cb);
+
+  // Adding an action with the same name kName.  New callbacks should replace
+  // old ones.
+  hook_table_.Add(kName, start2_cb);
+  hook_table_.Run(0, done_cb);
+  hook_table_.ActionComplete(kName);
+
+  // Ensure that the timeout callback got cancelled.  If it did not get
+  // cancelled, done_cb will be run twice and make this test fail.
+  event_dispatcher_.DispatchPendingEvents();
+}
+
+TEST_F(HookTableTest, RemoveAction) {
+  EXPECT_CALL(*this, StartAction()).Times(0);
+  EXPECT_CALL(*this, DoneAction(IsSuccess()));
+  Closure start_cb = Bind(&HookTableTest::StartAction, Unretained(this));
+  Callback<void(const Error &)> done_cb = Bind(&HookTableTest::DoneAction,
+                                               Unretained(this));
+  hook_table_.Add(kName, start_cb);
+  hook_table_.Remove(kName);
+  hook_table_.Run(0, done_cb);
+}
+
+TEST_F(HookTableTest, ActionCompleteFollowedByRemove) {
+  EXPECT_CALL(*this, StartAction()).Times(0);
+  Closure start_cb = Bind(&HookTableTest::StartAction, Unretained(this));
+  hook_table_.Add(kName, start_cb);
+  hook_table_.ActionComplete(kName);
+  hook_table_.Remove(kName);
+}
+
+class SomeClass : public base::RefCounted<SomeClass> {
+ public:
+  SomeClass() {}
+  void StartAction() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SomeClass);
+};
+
+// This test verifies that a class that removes itself from a hook table upon
+// destruction does not crash if the hook table is destroyed first.
+TEST_F(HookTableTest, RefcountedObject) {
+  scoped_ptr<HookTable> ht(new HookTable(&event_dispatcher_));
+  {
+    scoped_refptr<SomeClass> ref_counted_object = new SomeClass();
+    Closure start_cb = Bind(&SomeClass::StartAction, ref_counted_object);
+    ht->Add(kName, start_cb);
   }
 }
 
+TEST_F(HookTableTest, ActionAddedBeforePreviousActionCompletes) {
+  EXPECT_CALL(*this, StartAction());
+  EXPECT_CALL(*this, StartAction2()).Times(0);
+  EXPECT_CALL(*this, DoneAction(IsSuccess()));
+  Closure start_cb = Bind(&HookTableTest::StartAction, Unretained(this));
+  Closure start2_cb = Bind(&HookTableTest::StartAction2, Unretained(this));
+  Callback<void(const Error &)> done_cb = Bind(&HookTableTest::DoneAction,
+                                               Unretained(this));
+  hook_table_.Add(kName, start_cb);
+  hook_table_.Run(0, done_cb);
+
+  // An action with the same name is added before the previous actions complete.
+  // It should not be run.
+  hook_table_.Add(kName, start2_cb);
+  hook_table_.ActionComplete(kName);
+}
+
+
 }  // namespace shill