Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7f924e74 authored by Myles Watson's avatar Myles Watson Committed by Automerger Merge Worker
Browse files

Merge "Add synchronized tests for ContextualCallbacks" into main am: 4ccb140b

parents b817ea80 4ccb140b
Loading
Loading
Loading
Loading
+338 −1
Original line number Diff line number Diff line
@@ -22,8 +22,12 @@
#include <base/threading/platform_thread.h>
#include <sys/syscall.h>

#include <chrono>
#include <future>
#include <string>

#include "common/bind.h"
#include "common/contextual_callback.h"
#include "gtest/gtest.h"
#include "module.h"
#include "os/handler.h"
@@ -198,12 +202,54 @@ void TestGdxModule::Stop() {
}

std::string TestGdxModule::ToString() const {
  return std::string(__func__);
  return "TestGdxModule";
}

const bluetooth::ModuleFactory TestGdxModule::Factory =
    bluetooth::ModuleFactory([]() { return new TestGdxModule(); });

void TestGdxModule::set_callback(common::ContextualCallback<void(std::string)> callback) {
  call_many_ = callback;
}

void TestGdxModule::set_once_callback(common::ContextualOnceCallback<void(std::string)> callback) {
  call_once_ = std::move(callback);
}

void TestGdxModule::call_callback_on_handler(std::string message) {
  GetHandler()->Post(common::BindOnce(
      [](common::ContextualCallback<void(std::string)> callback, std::string message) {
        callback(message);
      },
      call_many_,
      message));
}

void TestGdxModule::call_once_callback_on_handler(std::string message) {
  GetHandler()->Post(common::BindOnce(
      [](common::ContextualOnceCallback<void(std::string)> callback, std::string message) {
        callback(message);
      },
      std::move(call_once_),
      message));
}

void TestGdxModule::call_callback_on_main(std::string message) {
  post_on_bt_main([message, this]() { call_many_(message); });
}

void TestGdxModule::call_once_callback_on_main(std::string message) {
  post_on_bt_main([message, this]() { call_once_(message); });
}

void TestGdxModule::call_callback_on_jni(std::string message) {
  post_on_bt_jni([message, this]() { call_many_(message); });
}

void TestGdxModule::call_once_callback_on_jni(std::string message) {
  post_on_bt_jni([message, this]() { call_once_(message); });
}

//
// Module GDx Testing Below
//
@@ -339,3 +385,294 @@ TEST_F(ModuleGdxWithStackTest, test_call_on_jni_repost) {
TEST_F(ModuleGdxWithStackTest, test_call_on_jni_recurse) {
  Mod()->call_on_jni_recurse(jniloop_tid_, 1, 2, 3);
}

class ModuleGdxWithInstrumentedCallback : public ModuleGdxWithStackTest {
 protected:
  void SetUp() override {
    ModuleGdxWithStackTest::SetUp();
  }

  void TearDown() override {
    ModuleGdxWithStackTest::TearDown();
  }

  // A helper class to promise/future for synchronization
  class Promises {
   public:
    std::promise<std::string> result;
    std::future<std::string> result_future = result.get_future();
    std::promise<void> blocking;
    std::future<void> blocking_future = blocking.get_future();
    std::promise<void> unblock;
    std::future<void> unblock_future = unblock.get_future();
  };

  class InstrumentedCallback {
   public:
    Promises promises;
    common::ContextualCallback<void(std::string)> callback;
  };

  class InstrumentedOnceCallback {
   public:
    Promises promises;
    common::ContextualOnceCallback<void(std::string)> callback;
  };

  std::unique_ptr<InstrumentedCallback> GetNewCallbackOnMain() {
    auto to_return = std::make_unique<InstrumentedCallback>();
    to_return->callback = get_main()->Bind(
        [](std::promise<std::string>* promise_ptr,
           std::promise<void>* blocking,
           std::future<void>* unblock,
           std::string result) {
          // Tell the test that this callback is running (and blocking)
          blocking->set_value();
          // Block until the test is ready to continue
          ASSERT_EQ(std::future_status::ready, unblock->wait_for(std::chrono::seconds(1)));
          // Send the result back to the test
          promise_ptr->set_value(result);
          log::info("set_value {}", result);
        },
        &to_return->promises.result,
        &to_return->promises.blocking,
        &to_return->promises.unblock_future);

    return to_return;
  }

  std::unique_ptr<InstrumentedOnceCallback> GetNewOnceCallbackOnMain() {
    auto to_return = std::make_unique<InstrumentedOnceCallback>();
    to_return->callback = get_main()->BindOnce(
        [](std::promise<std::string>* promise_ptr,
           std::promise<void>* blocking,
           std::future<void>* unblock,
           std::string result) {
          blocking->set_value();
          ASSERT_EQ(std::future_status::ready, unblock->wait_for(std::chrono::seconds(1)));
          promise_ptr->set_value(result);
          log::info("set_value {}", result);
        },
        &to_return->promises.result,
        &to_return->promises.blocking,
        &to_return->promises.unblock_future);

    return to_return;
  }
};

TEST_F(ModuleGdxWithInstrumentedCallback, test_call_callback_on_handler) {
  auto instrumented = GetNewCallbackOnMain();
  Mod()->set_callback(instrumented->callback);

  // Enqueue the callback and wait for it to block on main thread
  std::string result = "This was called on the handler";
  Mod()->call_callback_on_handler(result);
  ASSERT_EQ(
      std::future_status::ready,
      instrumented->promises.blocking_future.wait_for(std::chrono::seconds(1)));
  log::info("Waiting");

  // Enqueue something else on the main thread and verify that it hasn't run
  static auto second_task_promise = std::promise<void>();
  auto final_future = second_task_promise.get_future();
  do_in_main_thread(
      FROM_HERE,
      common::BindOnce(
          [](std::promise<void> promise) {
            promise.set_value();
            log::info("Finally");
          },
          std::move(second_task_promise)));
  ASSERT_EQ(std::future_status::timeout, final_future.wait_for(std::chrono::milliseconds(1)));

  // Let the callback start
  instrumented->promises.unblock.set_value();
  ASSERT_EQ(
      std::future_status::ready,
      instrumented->promises.result_future.wait_for(std::chrono::seconds(1)));
  ASSERT_EQ(result, instrumented->promises.result_future.get());

  // Let the second task finish
  ASSERT_EQ(std::future_status::ready, final_future.wait_for(std::chrono::seconds(1)));
}

TEST_F(ModuleGdxWithInstrumentedCallback, test_call_once_callback_on_handler) {
  auto instrumented = GetNewOnceCallbackOnMain();
  Mod()->set_once_callback(std::move(instrumented->callback));

  // Enqueue the callback and wait for it to block on main thread
  std::string result = "This was called on the handler";
  Mod()->call_once_callback_on_handler(result);
  ASSERT_EQ(
      std::future_status::ready,
      instrumented->promises.blocking_future.wait_for(std::chrono::seconds(1)));
  log::info("Waiting");

  // Enqueue something else on the main thread and verify that it hasn't run
  static auto second_task_promise = std::promise<void>();
  auto final_future = second_task_promise.get_future();
  do_in_main_thread(
      FROM_HERE,
      common::BindOnce(
          [](std::promise<void> promise) {
            promise.set_value();
            log::info("Finally");
          },
          std::move(second_task_promise)));
  ASSERT_EQ(std::future_status::timeout, final_future.wait_for(std::chrono::milliseconds(1)));

  // Let the callback start
  instrumented->promises.unblock.set_value();
  ASSERT_EQ(
      std::future_status::ready,
      instrumented->promises.result_future.wait_for(std::chrono::seconds(1)));
  ASSERT_EQ(result, instrumented->promises.result_future.get());

  // Let the second task finish
  ASSERT_EQ(std::future_status::ready, final_future.wait_for(std::chrono::seconds(1)));
}

TEST_F(ModuleGdxWithInstrumentedCallback, test_call_callback_on_main) {
  auto instrumented = GetNewCallbackOnMain();
  Mod()->set_callback(instrumented->callback);

  // Enqueue the callback and wait for it to block on main thread
  std::string result = "This was called on the main";
  Mod()->call_callback_on_main(result);
  ASSERT_EQ(
      std::future_status::ready,
      instrumented->promises.blocking_future.wait_for(std::chrono::seconds(1)));
  log::info("Waiting");

  // Enqueue something else on the main thread and verify that it hasn't run
  static auto second_task_promise = std::promise<void>();
  auto final_future = second_task_promise.get_future();
  do_in_main_thread(
      FROM_HERE,
      common::BindOnce(
          [](std::promise<void> promise) {
            promise.set_value();
            log::info("Finally");
          },
          std::move(second_task_promise)));
  ASSERT_EQ(std::future_status::timeout, final_future.wait_for(std::chrono::milliseconds(1)));

  // Let the callback start
  instrumented->promises.unblock.set_value();
  ASSERT_EQ(
      std::future_status::ready,
      instrumented->promises.result_future.wait_for(std::chrono::seconds(1)));
  ASSERT_EQ(result, instrumented->promises.result_future.get());

  // Let the second task finish
  ASSERT_EQ(std::future_status::ready, final_future.wait_for(std::chrono::seconds(1)));
}

TEST_F(ModuleGdxWithInstrumentedCallback, test_call_once_callback_on_main) {
  auto instrumented = GetNewOnceCallbackOnMain();
  Mod()->set_once_callback(std::move(instrumented->callback));

  // Enqueue the callback and wait for it to block on main thread
  std::string result = "This was called on the main";
  Mod()->call_once_callback_on_main(result);
  ASSERT_EQ(
      std::future_status::ready,
      instrumented->promises.blocking_future.wait_for(std::chrono::seconds(1)));
  log::info("Waiting");

  // Enqueue something else on the main thread and verify that it hasn't run
  static auto second_task_promise = std::promise<void>();
  auto final_future = second_task_promise.get_future();
  do_in_main_thread(
      FROM_HERE,
      common::BindOnce(
          [](std::promise<void> promise) {
            promise.set_value();
            log::info("Finally");
          },
          std::move(second_task_promise)));
  ASSERT_EQ(std::future_status::timeout, final_future.wait_for(std::chrono::milliseconds(1)));

  // Let the callback start
  instrumented->promises.unblock.set_value();
  ASSERT_EQ(
      std::future_status::ready,
      instrumented->promises.result_future.wait_for(std::chrono::seconds(1)));
  ASSERT_EQ(result, instrumented->promises.result_future.get());

  // Let the second task finish
  ASSERT_EQ(std::future_status::ready, final_future.wait_for(std::chrono::seconds(1)));
}

TEST_F(ModuleGdxWithInstrumentedCallback, test_call_callback_on_jni) {
  auto instrumented = GetNewCallbackOnMain();
  Mod()->set_callback(instrumented->callback);

  // Enqueue the callback and wait for it to block on main thread
  std::string result = "This was called on the jni";
  Mod()->call_callback_on_jni(result);
  ASSERT_EQ(
      std::future_status::ready,
      instrumented->promises.blocking_future.wait_for(std::chrono::seconds(1)));
  log::info("Waiting");

  // Enqueue something else on the main thread and verify that it hasn't run
  static auto second_task_promise = std::promise<void>();
  auto final_future = second_task_promise.get_future();
  do_in_main_thread(
      FROM_HERE,
      common::BindOnce(
          [](std::promise<void> promise) {
            promise.set_value();
            log::info("Finally");
          },
          std::move(second_task_promise)));
  ASSERT_EQ(std::future_status::timeout, final_future.wait_for(std::chrono::milliseconds(1)));

  // Let the callback start
  instrumented->promises.unblock.set_value();
  ASSERT_EQ(
      std::future_status::ready,
      instrumented->promises.result_future.wait_for(std::chrono::seconds(1)));
  ASSERT_EQ(result, instrumented->promises.result_future.get());

  // Let the second task finish
  ASSERT_EQ(std::future_status::ready, final_future.wait_for(std::chrono::seconds(1)));
}

TEST_F(ModuleGdxWithInstrumentedCallback, test_call_once_callback_on_jni) {
  auto instrumented = GetNewOnceCallbackOnMain();
  Mod()->set_once_callback(std::move(instrumented->callback));

  // Enqueue the callback and wait for it to block on main thread
  std::string result = "This was called on the jni";
  Mod()->call_once_callback_on_jni(result);
  ASSERT_EQ(
      std::future_status::ready,
      instrumented->promises.blocking_future.wait_for(std::chrono::seconds(1)));
  log::info("Waiting");

  // Enqueue something else on the main thread and verify that it hasn't run
  static auto second_task_promise = std::promise<void>();
  auto final_future = second_task_promise.get_future();
  do_in_main_thread(
      FROM_HERE,
      common::BindOnce(
          [](std::promise<void> promise) {
            promise.set_value();
            log::info("Finally");
          },
          std::move(second_task_promise)));
  ASSERT_EQ(std::future_status::timeout, final_future.wait_for(std::chrono::milliseconds(1)));

  // Let the callback start
  instrumented->promises.unblock.set_value();
  ASSERT_EQ(
      std::future_status::ready,
      instrumented->promises.result_future.wait_for(std::chrono::seconds(1)));
  ASSERT_EQ(result, instrumented->promises.result_future.get());

  // Let the second task finish
  ASSERT_EQ(std::future_status::ready, final_future.wait_for(std::chrono::seconds(1)));
}
+14 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@

#include <string>

#include "common/contextual_callback.h"
#include "module.h"
#include "module_jniloop.h"
#include "module_mainloop.h"
@@ -41,6 +42,16 @@ class TestGdxModule : public Module, public ModuleMainloop, public ModuleJniloop
  void call_on_jni_repost(pid_t tid, int a, int b, int c);
  void call_on_jni_recurse(pid_t tid, int a, int b, int c);

  void set_callback(common::ContextualCallback<void(std::string)> one_message_callback);
  void set_once_callback(common::ContextualOnceCallback<void(std::string)> one_message_callback);

  void call_callback_on_handler(std::string message);
  void call_once_callback_on_handler(std::string message);
  void call_callback_on_jni(std::string message);
  void call_once_callback_on_jni(std::string message);
  void call_callback_on_main(std::string message);
  void call_once_callback_on_main(std::string message);

  static const bluetooth::ModuleFactory Factory;

 protected:
@@ -58,6 +69,9 @@ class TestGdxModule : public Module, public ModuleMainloop, public ModuleJniloop
  struct PrivateImpl;
  std::shared_ptr<TestGdxModule::PrivateImpl> pimpl_;

  common::ContextualOnceCallback<void(std::string)> call_once_;
  common::ContextualCallback<void(std::string)> call_many_;

  bool started_ = false;

  friend bluetooth::ModuleRegistry;