buffet: Add a helper to coordinate async callbacks

We frequently end up in situations where we want to call an "init
succeeded" callback after many other callbacks indicating partial
init success come back.  AsyncEventCoordinator will manage this
process for us in a general way.

BUG=chromium:360831
TEST=Unittests

CQ-DEPEND=CL:193650

Change-Id: I968a65e88d60199d0355743a2fcee7e20156bc31
Reviewed-on: https://chromium-review.googlesource.com/194362
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Christopher Wiley <wiley@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/async_event_sequencer.cc b/buffet/async_event_sequencer.cc
new file mode 100644
index 0000000..b447c70
--- /dev/null
+++ b/buffet/async_event_sequencer.cc
@@ -0,0 +1,102 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "buffet/async_event_sequencer.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+AsyncEventSequencer::AsyncEventSequencer() { }
+AsyncEventSequencer::~AsyncEventSequencer() { }
+
+AsyncEventSequencer::Handler AsyncEventSequencer::GetHandler(
+    const std::string& descriptive_message, bool failure_is_fatal) {
+  CHECK(!started_) << "Cannot create handlers after OnAllTasksCompletedCall()";
+  int unique_registration_id = ++registration_counter_;
+  outstanding_registrations_.insert(unique_registration_id);
+  return base::Bind(&AsyncEventSequencer::HandleFinish, this,
+                    unique_registration_id, descriptive_message,
+                    failure_is_fatal);
+}
+
+AsyncEventSequencer::ExportHandler AsyncEventSequencer::GetExportHandler(
+        const std::string& interface_name, const std::string& method_name,
+        const std::string& descriptive_message, bool failure_is_fatal) {
+  auto finish_handler = GetHandler(descriptive_message, failure_is_fatal);
+  return base::Bind(&AsyncEventSequencer::HandleDBusMethodExported, this,
+                    finish_handler,
+                    interface_name,
+                    method_name);
+}
+
+void AsyncEventSequencer::OnAllTasksCompletedCall(
+    std::vector<CompletionAction> actions) {
+  CHECK(!started_) << "OnAllTasksCompletedCall called twice!";
+  started_ = true;
+  completion_actions_.assign(actions.begin(), actions.end());
+  // All of our callbacks might have been called already.
+  PossiblyRunCompletionActions();
+}
+
+void AsyncEventSequencer::HandleFinish(int registration_number,
+                                       const std::string& error_message,
+                                       bool failure_is_fatal, bool success) {
+  RetireRegistration(registration_number);
+  CheckForFailure(failure_is_fatal, success, error_message);
+  PossiblyRunCompletionActions();
+}
+
+void AsyncEventSequencer::HandleDBusMethodExported(
+    const AsyncEventSequencer::Handler& finish_handler,
+    const std::string& expected_interface_name,
+    const std::string& expected_method_name,
+    const std::string& actual_interface_name,
+    const std::string& actual_method_name, bool success) {
+  CHECK(expected_method_name == actual_method_name)
+      << "Exported DBus method '" << actual_method_name << "' "
+      << "but expected '" << expected_method_name << "'";
+  CHECK(expected_interface_name == actual_interface_name)
+      << "Exported method DBus interface '" << actual_interface_name << "' "
+      << "but expected '" << expected_interface_name << "'";
+  finish_handler.Run(success);
+}
+
+
+void AsyncEventSequencer::RetireRegistration(int registration_number) {
+  const size_t handlers_retired = outstanding_registrations_.erase(
+      registration_number);
+  CHECK(handlers_retired == 1)
+      << "Tried to retire invalid handler " << registration_number << ")";
+}
+
+void AsyncEventSequencer::CheckForFailure(bool failure_is_fatal, bool success,
+                                          const std::string& error_message) {
+  if (failure_is_fatal) {
+    CHECK(success) << error_message;
+  }
+  if (!success) {
+    LOG(ERROR) << error_message;
+    had_failures_ = true;
+  }
+}
+
+void AsyncEventSequencer::PossiblyRunCompletionActions() {
+  if (!started_ || !outstanding_registrations_.empty()) {
+    // Don't run completion actions if we have any outstanding
+    // Handlers outstanding or if any more handlers might
+    // be scheduled in the future.
+    return;
+  }
+  for (auto&& completion_action : completion_actions_) {
+    // Should this be put on the message loop or run directly?
+    completion_action.Run(!had_failures_);
+  }
+  // Discard our references to those actions.
+  completion_actions_.clear();
+}
+
+}  // namespace dbus_utils
+
+}  // namespace buffet
diff --git a/buffet/async_event_sequencer.h b/buffet/async_event_sequencer.h
new file mode 100644
index 0000000..105be6d
--- /dev/null
+++ b/buffet/async_event_sequencer.h
@@ -0,0 +1,97 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_ASYNC_EVENT_SEQUENCER_H_
+#define BUFFET_ASYNC_EVENT_SEQUENCER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/basictypes.h>
+#include <base/memory/ref_counted.h>
+
+namespace buffet {
+
+namespace dbus_utils {
+
+// A helper class for coordinating the multiple async tasks.  A consumer
+// may grab any number of callbacks via Get*Handler() and schedule a list
+// of completion actions to take.  When all handlers obtained bia Get*Handler()
+// have been called, the AsyncEventSequencer will call its CompletionActions.
+//
+// Usage:
+//
+// void Init(const base::Callback<void(bool success)> cb) {
+//   scoped_refptr<AsyncEventSequencer> sequencer(
+//       new AsyncEventSequencer());
+//   one_delegate_needing_init_.Init(sequencer->GetHandler(
+//       "my delegate failed to init", false));
+//   dbus_init_delegate_.Init(sequencer->GetExportHandler(
+//       "org.test.Interface", "ExposedMethodName",
+//       "another delegate is flaky", false));
+//   sequencer->OnAllTasksCompletedCall({cb});
+// }
+class AsyncEventSequencer : public base::RefCounted<AsyncEventSequencer> {
+ public:
+  typedef base::Callback<void(bool success)> Handler;
+  typedef base::Callback<void (const std::string& interface_name,
+                               const std::string& method_name,
+                               bool success)> ExportHandler;
+  typedef base::Callback<void(bool all_succeeded)> CompletionAction;
+
+  AsyncEventSequencer();
+  // Get a Finished handler callback.  Each callback is "unique" in the sense
+  // that subsequent calls to GetHandler() will create new handlers
+  // which will need to be called before completion actions are run.
+  Handler GetHandler(const std::string& descriptive_message,
+                     bool failure_is_fatal);
+  // Like GetHandler except with a signature tailored to
+  // ExportedObject's ExportMethod callback requirements.  Will also assert
+  // that the passed interface/method names from ExportedObject are correct.
+  ExportHandler GetExportHandler(
+      const std::string& interface_name, const std::string& method_name,
+      const std::string& descriptive_message, bool failure_is_fatal);
+  // Once all handlers obtained via GetHandler have run,
+  // we'll run each CompletionAction, then discard our references.
+  // No more handlers may be obtained after this call.
+  void OnAllTasksCompletedCall(std::vector<CompletionAction> actions);
+
+ private:
+  // We'll partially bind this function before giving it back via
+  // GetHandler.  Note that the returned callbacks have
+  // references to *this, which gives us the neat property that we'll
+  // destroy *this only when all our callbacks have been destroyed.
+  void HandleFinish(int registration_number, const std::string& error_message,
+                    bool failure_is_fatal, bool success);
+  // Similar to HandleFinish.
+  void HandleDBusMethodExported(
+      const Handler& finish_handler,
+      const std::string& expected_interface_name,
+      const std::string& expected_method_name,
+      const std::string& actual_interface_name,
+      const std::string& actual_method_name,
+      bool success);
+  void RetireRegistration(int registration_number);
+  void CheckForFailure(bool failure_is_fatal, bool success,
+                       const std::string& error_message);
+  void PossiblyRunCompletionActions();
+
+  bool started_{false};
+  int registration_counter_{0};
+  std::set<int> outstanding_registrations_;
+  std::vector<CompletionAction> completion_actions_;
+  bool had_failures_{false};
+  // Ref counted objects have private destructors.
+  ~AsyncEventSequencer();
+  friend class base::RefCounted<AsyncEventSequencer>;
+  DISALLOW_COPY_AND_ASSIGN(AsyncEventSequencer);
+};
+
+}  // namespace dbus_utils
+
+}  // namespace buffet
+
+#endif  // BUFFET_ASYNC_EVENT_SEQUENCER_H_
diff --git a/buffet/async_event_sequencer_unittest.cc b/buffet/async_event_sequencer_unittest.cc
new file mode 100644
index 0000000..d95ff56
--- /dev/null
+++ b/buffet/async_event_sequencer_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "buffet/async_event_sequencer.h"
+
+#include <base/bind_helpers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace buffet {
+
+namespace dbus_utils {
+
+namespace {
+
+const char kTestInterface[] = "org.test.if";
+const char kTestMethod1[] = "TestMethod1";
+const char kTestMethod2[] = "TestMethod2";
+
+}  // namespace
+
+class AsyncEventSequencerTest : public ::testing::Test {
+ public:
+  MOCK_METHOD1(HandleCompletion, void(bool all_succeeded));
+
+  void SetUp() {
+      aec_ = new AsyncEventSequencer();
+      cb_ = base::Bind(&AsyncEventSequencerTest::HandleCompletion,
+                       base::Unretained(this));
+  }
+
+  scoped_refptr<AsyncEventSequencer> aec_;
+  AsyncEventSequencer::CompletionAction cb_;
+};
+
+TEST_F(AsyncEventSequencerTest, WaitForCompletionActions) {
+  auto finished_handler = aec_->GetHandler("handler failed", false);
+  finished_handler.Run(true);
+  EXPECT_CALL(*this, HandleCompletion(true)).Times(1);
+  aec_->OnAllTasksCompletedCall({cb_});
+}
+
+TEST_F(AsyncEventSequencerTest, MultiInitActionsSucceed) {
+  auto finished_handler1 = aec_->GetHandler("handler failed", false);
+  auto finished_handler2 = aec_->GetHandler("handler failed", false);
+  aec_->OnAllTasksCompletedCall({cb_});
+  finished_handler1.Run(true);
+  EXPECT_CALL(*this, HandleCompletion(true)).Times(1);
+  finished_handler2.Run(true);
+}
+
+TEST_F(AsyncEventSequencerTest, SomeInitActionsFail) {
+  auto finished_handler1 = aec_->GetHandler("handler failed", false);
+  auto finished_handler2 = aec_->GetHandler("handler failed", false);
+  aec_->OnAllTasksCompletedCall({cb_});
+  finished_handler1.Run(false);
+  EXPECT_CALL(*this, HandleCompletion(false)).Times(1);
+  finished_handler2.Run(true);
+}
+
+TEST_F(AsyncEventSequencerTest, MultiDBusActionsSucceed) {
+  auto handler1 =  aec_->GetExportHandler(
+      kTestInterface, kTestMethod1, "method export failed", false);
+  auto handler2 =  aec_->GetExportHandler(
+      kTestInterface, kTestMethod2, "method export failed", false);
+  aec_->OnAllTasksCompletedCall({cb_});
+  handler1.Run(kTestInterface, kTestMethod1, true);
+  EXPECT_CALL(*this, HandleCompletion(true)).Times(1);
+  handler2.Run(kTestInterface, kTestMethod2, true);
+}
+
+TEST_F(AsyncEventSequencerTest, SomeDBusActionsFail) {
+  auto handler1 =  aec_->GetExportHandler(
+      kTestInterface, kTestMethod1, "method export failed", false);
+  auto handler2 =  aec_->GetExportHandler(
+      kTestInterface, kTestMethod2, "method export failed", false);
+  aec_->OnAllTasksCompletedCall({cb_});
+  handler1.Run(kTestInterface, kTestMethod1, true);
+  EXPECT_CALL(*this, HandleCompletion(false)).Times(1);
+  handler2.Run(kTestInterface, kTestMethod2, false);
+}
+
+TEST_F(AsyncEventSequencerTest, MixedActions) {
+  auto handler1 =  aec_->GetExportHandler(
+      kTestInterface, kTestMethod1, "method export failed", false);
+  auto handler2 = aec_->GetHandler("handler failed", false);
+  aec_->OnAllTasksCompletedCall({cb_});
+  handler1.Run(kTestInterface, kTestMethod1, true);
+  EXPECT_CALL(*this, HandleCompletion(true)).Times(1);
+  handler2.Run(true);
+}
+
+}  // namespace dbus_utils
+
+}  // namespace buffet
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp
index e4b2d2d..85c17d4 100644
--- a/buffet/buffet.gyp
+++ b/buffet/buffet.gyp
@@ -7,6 +7,7 @@
       'deps': [
         'dbus-1',
         'libchrome-<(libbase_ver)',
+        'libchrome-test-<(libbase_ver)',
         'libcurl',
       ],
     },
@@ -34,6 +35,7 @@
         'http_request.cc',
         'http_transport_curl.cc',
         'http_utils.cc',
+        'async_event_sequencer.cc',
         'manager.cc',
         'mime_utils.cc',
         'string_utils.cc',
@@ -68,6 +70,7 @@
         'buffet_testrunner.cc',
         'data_encoding_unittest.cc',
         'exported_property_set_unittest.cc',
+        'async_event_sequencer_unittest.cc',
         'mime_utils_unittest.cc',
         'string_utils_unittest.cc',
       ],