| // Copyright 2023 The Fuchsia 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 "lib/ld/testing/mock-loader-service.h" |
| |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/dispatcher.h> |
| #include <lib/elfldltl/testing/get-test-data.h> |
| #include <lib/fit/defer.h> |
| #include <zircon/dlfcn.h> |
| |
| #include <filesystem> |
| |
| #include <gmock/gmock.h> |
| |
| namespace ld::testing { |
| namespace { |
| |
| using ::testing::Return; |
| |
| } // namespace |
| |
| // A mock server implementation serving the fuchsia.ldsvc.Loader protocol. When |
| // it receives a request, it will invoke the associated MOCK_METHOD. If the |
| // test caller did not call Expect* for the request before it is made, then |
| // the MockServer will fail the test. |
| class MockLoaderService::MockServer : public fidl::WireServer<fuchsia_ldsvc::Loader> { |
| public: |
| MockServer() = default; |
| |
| ~MockServer() { |
| if (backing_loop_) { |
| backing_loop_->Shutdown(); |
| } |
| } |
| |
| void Init(fidl::ServerEnd<fuchsia_ldsvc::Loader> server) { |
| backing_loop_.emplace(&kAsyncLoopConfigNoAttachToCurrentThread); |
| zx_status_t status = backing_loop_->StartThread("MockLoaderService"); |
| ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status); |
| fidl::BindServer(backing_loop_->dispatcher(), std::move(server), this); |
| } |
| |
| // Note the mocked methods take std::string, though the Expect* wrappers take |
| // std::string_view. The std::string_view passed to the wrappers is not |
| // guaranteed to point to memory that's valid after the call, so they copy |
| // into a fresh std::string for EXPECT_CALL; those stay live in the mock. |
| |
| MOCK_METHOD(zx::result<zx::vmo>, MockLoadObject, (std::string)); |
| |
| MOCK_METHOD(zx::result<>, MockConfig, (std::string)); |
| |
| private: |
| // The fidl::WireServer<fuchsia_ldsvc::Loader> implementation, each FIDL |
| // method will call into its associated gMock method when invoked. |
| |
| // Done is not used in ld tests. |
| void Done(DoneCompleter::Sync& completer) override { ADD_FAILURE() << "unexpected Done call"; } |
| |
| void LoadObject(LoadObjectRequestView request, LoadObjectCompleter::Sync& completer) override { |
| auto result = MockLoadObject(std::string(request->object_name.get())); |
| completer.Reply(result.status_value(), std::move(result).value_or(zx::vmo())); |
| } |
| |
| void Config(ConfigRequestView request, ConfigCompleter::Sync& completer) override { |
| auto result = MockConfig(std::string(request->config.get())); |
| completer.Reply(result.status_value()); |
| } |
| |
| // Clone is not used in ld tests. |
| void Clone(CloneRequestView request, CloneCompleter::Sync& completer) override { |
| ADD_FAILURE() << "unexpected Clone call"; |
| } |
| |
| std::optional<async::Loop> backing_loop_; |
| }; |
| |
| MockLoaderService::MockLoaderService() = default; |
| |
| MockLoaderService::~MockLoaderService() = default; |
| |
| void MockLoaderService::Init() { |
| mock_server_ = std::make_unique<::testing::StrictMock<MockServer>>(); |
| auto endpoints = fidl::Endpoints<fuchsia_ldsvc::Loader>::Create(); |
| ASSERT_NO_FATAL_FAILURE(mock_server_->Init(std::move(endpoints.server))); |
| mock_client_ = std::move(endpoints.client); |
| } |
| |
| void MockLoaderService::ExpectLoadObject(std::string_view name, |
| zx::result<zx::vmo> expected_result) { |
| EXPECT_CALL(*mock_server_, MockLoadObject(std::string{name})) |
| .WillOnce(Return(std::move(expected_result))); |
| } |
| |
| void MockLoaderService::ExpectConfig(std::string_view name, zx::result<> expected_result) { |
| EXPECT_CALL(*mock_server_, MockConfig(std::string{name})).WillOnce(Return(expected_result)); |
| } |
| |
| void MockLoaderServiceForTest::Needed(std::initializer_list<std::string_view> names) { |
| for (std::string_view name : names) { |
| ExpectDependency(name); |
| } |
| } |
| |
| void MockLoaderServiceForTest::Needed( |
| std::initializer_list<std::pair<std::string_view, bool>> name_found_pairs) { |
| for (auto [name, found] : name_found_pairs) { |
| if (found) { |
| ExpectDependency(name); |
| } else { |
| ExpectMissing(name); |
| } |
| } |
| } |
| |
| void MockLoaderServiceForTest::ExpectLoadObject(std::string_view name, |
| zx::result<zx::vmo> expected_result) { |
| ASSERT_NO_FATAL_FAILURE(ReadyMock()); |
| mock_loader_->ExpectLoadObject(name, std::move(expected_result)); |
| } |
| |
| void MockLoaderServiceForTest::ExpectLoadObject(std::string_view name, zx::vmo vmo) { |
| ASSERT_TRUE(vmo); |
| ExpectLoadObject(name, zx::ok(std::move(vmo))); |
| } |
| |
| void MockLoaderServiceForTest::ExpectDependency(std::string_view name) { |
| ExpectLoadObject(name, GetDepVmo(name)); |
| } |
| |
| void MockLoaderServiceForTest::ExpectRootModule(std::string_view name) { |
| ExpectLoadObject(name, GetRootModuleVmo(name)); |
| } |
| |
| void MockLoaderServiceForTest::ExpectMissing(std::string_view name) { |
| ExpectLoadObject(name, zx::error{ZX_ERR_NOT_FOUND}); |
| } |
| |
| void MockLoaderServiceForTest::ExpectConfig(std::string_view config) { |
| ASSERT_NO_FATAL_FAILURE(ReadyMock()); |
| mock_loader_->ExpectConfig(config, zx::ok()); |
| } |
| |
| zx::channel MockLoaderServiceForTest::TakeLdsvc() { |
| zx::channel ldsvc; |
| if (mock_loader_) { |
| ldsvc = mock_loader_->client().TakeChannel(); |
| } |
| return ldsvc; |
| } |
| |
| zx::unowned_channel MockLoaderServiceForTest::BorrowLdsvc() { |
| zx::unowned_channel ldsvc; |
| if (mock_loader_) { |
| ldsvc = mock_loader_->client().channel().borrow(); |
| } |
| return ldsvc; |
| } |
| |
| void MockLoaderServiceForTest::CallWithLoaderInstalled(fit::function<void()> func) { |
| // Install the mock loader as the system loader. |
| auto mock_ldsvc = BorrowLdsvc(); |
| ASSERT_TRUE(mock_ldsvc->is_valid()); |
| |
| // Restore the loader to the previous system loader at the close of function scope. |
| auto restore_system_ldsvc = |
| fit::defer([system_ldsvc = zx::channel{dl_set_loader_service(mock_ldsvc->get())}]() mutable { |
| dl_set_loader_service(system_ldsvc.release()); |
| }); |
| |
| // Now that the mock loader service is installed, call the function. |
| func(); |
| } |
| |
| zx::vmo MockLoaderServiceForTest::GetDepVmo(std::string_view name) { |
| // TODO(https://fxbug.dev/335737373): use a more direct means to look up the file. |
| const std::string path = std::filesystem::path("test") / "lib" / LD_TEST_LIBPREFIX / name; |
| return elfldltl::testing::GetTestLibVmo(path); |
| } |
| |
| zx::vmo MockLoaderServiceForTest::GetRootModuleVmo(std::string_view name) { |
| const std::string path = std::filesystem::path("test") / "lib" / name; |
| return elfldltl::testing::GetTestLibVmo(path); |
| } |
| |
| void MockLoaderServiceForTest::ReadyMock() { |
| if (!mock_loader_) { |
| mock_loader_ = std::make_unique<MockLoaderService>(); |
| ASSERT_NO_FATAL_FAILURE(mock_loader_->Init()); |
| } |
| } |
| |
| } // namespace ld::testing |