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

Commit 685b43ed authored by Chris Manton's avatar Chris Manton
Browse files

gdx: Introduce module_mainloop extension

Bug: 314003342
Test: atest bluetooth_test_gdx_unit

Change-Id: I3e43025078b232285b6a4e07f81e97c388245549
parent 9f96f2fb
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
@@ -443,6 +443,46 @@ cc_test {
    min_sdk_version: "Tiramisu",
}

cc_test {
    name: "bluetooth_test_gdx_unit",
    test_suites: ["device-tests"],
    defaults: [
        "gd_defaults",
        "mts_defaults",
    ],
    include_dirs: [
        "packages/modules/Bluetooth/system",
        "packages/modules/Bluetooth/system/include",
        "packages/modules/Bluetooth/system/stack/include",
        "packages/modules/Bluetooth/system/types",
    ],
    host_supported: true,
    srcs: [
        ":TestCommonMockFunctions",
        "module_mainloop_unittest.cc",
    ],
    generated_headers: [
        "BluetoothGeneratedBundlerSchema_h_bfbs",
        "BluetoothGeneratedDumpsysDataSchema_h",
        "BluetoothGeneratedDumpsysInternalTestData_h",
        "BluetoothGeneratedDumpsysTestData_h",
    ],
    static_libs: [
        "libbluetooth_gd",
        "libbt-btu-main-thread",
        "libbt-common",
        "libbt_shim_bridge",
        "libbt_shim_ffi",
        "libchrome",
        "libevent",
        "libflatbuffers-cpp",
    ],
    sanitize: {
        address: true,
    },
    min_sdk_version: "Tiramisu",
}

cc_test {
    name: "bluetooth_packet_parser_test",
    test_suites: ["general-tests"],
+59 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <base/callback.h>
#include <base/functional/bind.h>
#include <base/location.h>

#include "stack/include/main_thread.h"

namespace bluetooth {

class ModuleMainloop {
 public:
  ModuleMainloop() noexcept = default;
  virtual ~ModuleMainloop() = default;
  ModuleMainloop(const ModuleMainloop& mod) = delete;

 protected:
  // Threadsafe post onto mainloop a function with copyable arguments
  template <typename Functor, typename... Args>
  void PostFunctionOnMain(Functor&& functor, Args&&... args) const {
    do_in_main_thread(
        FROM_HERE, base::BindOnce(std::forward<Functor>(functor), std::forward<Args>(args)...));
  }

  // Threadsafe post onto mainloop a method and contex with copyable arguments
  template <typename T, typename Functor, typename... Args>
  void PostMethodOnMain(std::shared_ptr<T> ref, Functor&& functor, Args... args) const {
    do_in_main_thread(
        FROM_HERE,
        base::BindOnce(
            [](std::weak_ptr<T> ref, Functor&& functor, Args&&... args) {
              if (std::shared_ptr<T> spt = ref.lock()) {
                base::BindOnce(std::forward<Functor>(functor), spt, std::forward<Args>(args)...)
                    .Run();
              }
            },
            std::weak_ptr<T>(ref),
            std::forward<Functor>(functor),
            std::forward<Args>(args)...));
  }
};

}  // namespace bluetooth
+247 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "module_mainloop_unittest.h"

#include <base/callback.h>
#include <base/functional/bind.h>
#include <base/location.h>
#include <base/threading/platform_thread.h>
#include <sys/syscall.h>

#include <string>

#include "gtest/gtest.h"
#include "module.h"
#include "os/handler.h"
#include "os/log.h"
#include "os/thread.h"
#include "stack/include/main_thread.h"

using namespace bluetooth;

namespace {
constexpr int sync_timeout_in_ms = 3000;

std::promise<pid_t> external_function_promise;
std::promise<pid_t> private_impl_promise;
std::promise<pid_t> protected_method_promise;

}  // namespace

// Global function with C linkage
void external_function(int /* a */, double /* b */, char /* c */) {
  external_function_promise.set_value(base::PlatformThread::CurrentId());
}

// Module private implementation that is inaccessible externally
struct TestModule::PrivateImpl : public ModuleMainloop {
  const int kMaxTestModuleRecurseDepth = 10;

  void privateCallableMethod(int /* a */, double /* b */, char /* c */) {
    private_impl_promise.set_value(base::PlatformThread::CurrentId());
  }

  void repostMethodTest(int /* a */, double /* b */, char /* c */) {
    private_impl_promise.set_value(base::PlatformThread::CurrentId());
  }

  void privateCallableRepostMethod(
      std::shared_ptr<TestModule::PrivateImpl> ptr, int a, double b, char c) {
    PostMethodOnMain(ptr, &PrivateImpl::repostMethodTest, a, b, c);
  }

  void privateCallableRecursiveMethod(
      std::shared_ptr<TestModule::PrivateImpl> ptr, int depth, double b, char c) {
    if (depth > kMaxTestModuleRecurseDepth) {
      private_impl_promise.set_value(base::PlatformThread::CurrentId());
      return;
    }
    PostMethodOnMain(ptr, &PrivateImpl::privateCallableRecursiveMethod, ptr, depth + 1, b, c);
  }
};

// Protected module method executed on handler
void TestModule::call_on_handler_protected_method(int handler_tid, int a, int b, int c) {
  protected_method_promise = std::promise<pid_t>();
  auto future = protected_method_promise.get_future();
  CallOn(this, &TestModule::protected_method, a, b, c);
  ASSERT_EQ(future.wait_for(std::chrono::seconds(3)), std::future_status::ready);
  ASSERT_EQ(future.get(), handler_tid);
}

// Global external function executed on main loop
void TestModule::call_on_main_external_function(int mainloop_tid, int a, double b, char c) {
  external_function_promise = std::promise<pid_t>();
  auto future = external_function_promise.get_future();
  PostFunctionOnMain(&external_function, a, b, c);
  ASSERT_EQ(future.wait_for(std::chrono::seconds(3)), std::future_status::ready);
  ASSERT_EQ(future.get(), mainloop_tid);
}

// Private implementation method executed on main loop
void TestModule::call_on_main(int mainloop_tid, int a, int b, int c) {
  private_impl_promise = std::promise<pid_t>();
  auto future = private_impl_promise.get_future();
  PostMethodOnMain(pimpl_, &TestModule::PrivateImpl::privateCallableMethod, a, b, c);
  ASSERT_EQ(future.wait_for(std::chrono::seconds(3)), std::future_status::ready);
  ASSERT_EQ(future.get(), mainloop_tid);
}

// Private implementation method executed on main loop and reposted
void TestModule::call_on_main_repost(int mainloop_tid, int a, int b, int c) {
  private_impl_promise = std::promise<pid_t>();
  auto future = private_impl_promise.get_future();
  PostMethodOnMain(pimpl_, &TestModule::PrivateImpl::privateCallableRepostMethod, pimpl_, a, b, c);
  ASSERT_EQ(future.wait_for(std::chrono::seconds(3)), std::future_status::ready);
  ASSERT_EQ(future.get(), mainloop_tid);
}

// Private implementation method executed on main loop recursively
void TestModule::call_on_main_recurse(int mainloop_tid, int depth, int b, int c) {
  private_impl_promise = std::promise<pid_t>();
  auto future = private_impl_promise.get_future();
  PostMethodOnMain(
      pimpl_, &TestModule::PrivateImpl::privateCallableRecursiveMethod, pimpl_, depth, b, c);
  ASSERT_EQ(future.wait_for(std::chrono::seconds(3)), std::future_status::ready);
  ASSERT_EQ(future.get(), mainloop_tid);
}

void TestModule::protected_method(int /* a */, int /* b */, int /* c */) {
  protected_method_promise.set_value(base::PlatformThread::CurrentId());
}

bool TestModule::IsStarted() const {
  return pimpl_ != nullptr;
}

void TestModule::Start() {
  ASSERT_FALSE(IsStarted());
  pimpl_ = std::make_shared<TestModule::PrivateImpl>();
}

void TestModule::Stop() {
  ASSERT_TRUE(IsStarted());
  pimpl_.reset();
}

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

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

//
// Module GDx Testing Below
//
class ModuleGdxTest : public ::testing::Test {
 protected:
  void SetUp() override {
    test_framework_tid_ = base::PlatformThread::CurrentId();
    module_ = new TestModule();
    main_thread_start_up();
    mainloop_tid_ = get_mainloop_tid();
  }

  void TearDown() override {
    sync_main_handler();
    main_thread_shut_down();
    delete module_;
  }

  void sync_main_handler() {
    std::promise promise = std::promise<void>();
    std::future future = promise.get_future();
    post_on_bt_main([&promise]() { promise.set_value(); });
    future.wait_for(std::chrono::milliseconds(sync_timeout_in_ms));
  };

  static pid_t get_mainloop_tid() {
    std::promise<pid_t> pid_promise = std::promise<pid_t>();
    auto future = pid_promise.get_future();
    post_on_bt_main([&pid_promise]() { pid_promise.set_value(base::PlatformThread::CurrentId()); });
    return future.get();
  }

  pid_t test_framework_tid_{-1};
  pid_t mainloop_tid_{-1};
  TestModuleRegistry module_registry_;
  TestModule* module_;
};

class ModuleGdxWithStackTest : public ModuleGdxTest {
 protected:
  void SetUp() override {
    ModuleGdxTest::SetUp();
    module_registry_.InjectTestModule(&TestModule::Factory, module_ /* pass ownership */);
    module_ = nullptr;  // ownership is passed
    handler_tid_ = get_handler_tid(module_registry_.GetTestModuleHandler(&TestModule::Factory));
  }

  static pid_t get_handler_tid(os::Handler* handler) {
    std::promise<pid_t> handler_tid_promise = std::promise<pid_t>();
    std::future<pid_t> future = handler_tid_promise.get_future();
    handler->Post(common::BindOnce(
        [](std::promise<pid_t> promise) { promise.set_value(base::PlatformThread::CurrentId()); },
        std::move(handler_tid_promise)));
    return future.get();
  }

  void TearDown() override {
    module_registry_.StopAll();
    ModuleGdxTest::TearDown();
  }

  TestModule* Mod() {
    return module_registry_.GetModuleUnderTest<TestModule>();
  }

  pid_t handler_tid_{-1};
};

TEST_F(ModuleGdxTest, nop) {}

TEST_F(ModuleGdxTest, lifecycle) {
  ::bluetooth::os::Thread* thread =
      new bluetooth::os::Thread("Name", bluetooth::os::Thread::Priority::REAL_TIME);
  ASSERT_FALSE(module_registry_.IsStarted<TestModule>());
  module_registry_.Start<TestModule>(thread);
  ASSERT_TRUE(module_registry_.IsStarted<TestModule>());
  module_registry_.StopAll();
  ASSERT_FALSE(module_registry_.IsStarted<TestModule>());
  delete thread;
}

TEST_F(ModuleGdxWithStackTest, call_on_handler_protected_method) {
  Mod()->call_on_handler_protected_method(handler_tid_, 1, 2, 3);
}

TEST_F(ModuleGdxWithStackTest, test_call_on_main) {
  Mod()->call_on_main(mainloop_tid_, 1, 2, 3);
}

TEST_F(ModuleGdxWithStackTest, test_call_external_function) {
  Mod()->call_on_main_external_function(mainloop_tid_, 1, 2.3, 'c');
}

TEST_F(ModuleGdxWithStackTest, test_call_on_main_repost) {
  Mod()->call_on_main_repost(mainloop_tid_, 1, 2, 3);
}

TEST_F(ModuleGdxWithStackTest, test_call_on_main_recurse) {
  Mod()->call_on_main_recurse(mainloop_tid_, 1, 2, 3);
}
+57 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <base/callback.h>
#include <base/functional/bind.h>
#include <base/location.h>

#include <string>

#include "module.h"
#include "module_mainloop.h"

using namespace bluetooth;

void external_function(int /* a */, double /* b */, char /* c */);

class TestModule : public Module, public ModuleMainloop {
 public:
  void call_on_handler_protected_method(pid_t tid, int a, int b, int c);
  void call_on_main_external_function(pid_t tid, int a, double b, char c);
  void call_on_main(pid_t tid, int a, int b, int c);
  void call_on_main_repost(pid_t tid, int a, int b, int c);
  void call_on_main_recurse(pid_t tid, int a, int b, int c);

  static const bluetooth::ModuleFactory Factory;

 protected:
  void protected_method(int a, int b, int c);
  void call_on_main_internal(int a, int b, int c);
  bool IsStarted() const;

  void ListDependencies(bluetooth::ModuleList* /* list */) const override {}
  void Start() override;
  void Stop() override;
  std::string ToString() const override;

 private:
  struct PrivateImpl;
  std::shared_ptr<TestModule::PrivateImpl> pimpl_;

  bool started_ = false;

  friend bluetooth::ModuleRegistry;
};