| // Copyright 2019 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/modular_test_harness/cpp/test_harness_impl.h" |
| |
| #include <fuchsia/modular/session/cpp/fidl.h> |
| #include <fuchsia/modular/testing/cpp/fidl.h> |
| #include <lib/sys/cpp/testing/test_with_environment.h> |
| #include <lib/vfs/cpp/pseudo_dir.h> |
| #include <peridot/lib/modular_config/modular_config.h> |
| #include <peridot/lib/modular_config/modular_config_constants.h> |
| #include <peridot/lib/util/pseudo_dir_server.h> |
| #include <src/lib/files/file.h> |
| #include <src/lib/files/path.h> |
| #include <src/lib/fxl/strings/split_string.h> |
| #include <src/lib/fxl/strings/substitute.h> |
| |
| #include <thread> |
| |
| #include "gtest/gtest.h" |
| |
| constexpr char kFakeBaseShellUrl[] = |
| "fuchsia-pkg://example.com/FAKE_BASE_SHELL_PKG/fake_base_shell.cmx"; |
| constexpr char kFakeSessionShellUrl[] = |
| "fuchsia-pkg://example.com/FAKE_SESSION_SHELL_PKG/fake_session_shell.cmx"; |
| constexpr char kFakeStoryShellUrl[] = |
| "fuchsia-pkg://example.com/FAKE_STORY_SHELL_PKG/fake_story_shell.cmx"; |
| constexpr char kFakeModuleUrl[] = |
| "fuchsia-pkg://example.com/FAKE_MODULE_PKG/fake_module.cmx"; |
| |
| namespace modular { |
| namespace testing { |
| namespace { |
| std::string GenerateFakeUrl() { |
| uint32_t random_number = 0; |
| zx_cprng_draw(&random_number, sizeof random_number); |
| constexpr char kFakeUrlSubstitutePattern[] = |
| "fuchsia-pkg://example.com/GENERATED_URL_$0#meta/GENERATED_URL_$0.cmx"; |
| return fxl::Substitute(kFakeUrlSubstitutePattern, |
| std::to_string(random_number)); |
| } |
| }; // namespace |
| |
| class TestHarnessImplTest : public sys::testing::TestWithEnvironment { |
| public: |
| TestHarnessImplTest() |
| : harness_impl_(real_env(), harness_.NewRequest(), |
| [this] { did_exit_ = true; }) {} |
| |
| fuchsia::modular::testing::TestHarnessPtr& test_harness() { |
| return harness_; |
| }; |
| |
| bool did_exit() { return did_exit_; } |
| |
| std::unique_ptr<vfs::PseudoDir> MakeBasemgrConfigDir( |
| fuchsia::modular::testing::TestHarnessSpec spec) { |
| return TestHarnessImpl::MakeBasemgrConfigDir(std::move(spec)); |
| } |
| |
| private: |
| bool did_exit_ = false; |
| fuchsia::modular::testing::TestHarnessPtr harness_; |
| ::modular::testing::TestHarnessImpl harness_impl_; |
| }; |
| |
| namespace { |
| |
| // Closing the TestHarness connection will cause TestHarnessImpl to notify that |
| // it's not usable. |
| TEST_F(TestHarnessImplTest, ExitCallback) { |
| test_harness().Unbind(); |
| RunLoopUntil([&] { return did_exit(); }); |
| } |
| |
| // Check that the config that TestHarnessImpl generates is readable by |
| // ModuleConfigReader. |
| TEST_F(TestHarnessImplTest, MakeBasemgrConfigDir) { |
| constexpr char kSessionShellForTest[] = |
| "fuchsia-pkg://example.com/TestHarnessImplTest#meta/" |
| "TestHarnessImplTest.cmx"; |
| |
| fuchsia::modular::testing::TestHarnessSpec spec; |
| fuchsia::modular::session::SessionShellMapEntry session_shell_entry; |
| session_shell_entry.mutable_config()->mutable_app_config()->set_url( |
| kSessionShellForTest); |
| |
| spec.mutable_basemgr_config()->mutable_session_shell_map()->push_back( |
| std::move(session_shell_entry)); |
| |
| // Construct "/config_override/data" dirs, and add MakeBasemgrConfigDir() to |
| // "data" dir. |
| auto namespace_dir = std::make_unique<vfs::PseudoDir>(); |
| { |
| auto dir_split = |
| fxl::SplitString(modular_config::kOverriddenConfigDir, "/", |
| fxl::kTrimWhitespace, fxl::kSplitWantNonEmpty); |
| ASSERT_EQ(2u, dir_split.size()); |
| |
| auto second_dir = std::make_unique<vfs::PseudoDir>(); |
| second_dir->AddEntry(dir_split[1].ToString(), |
| MakeBasemgrConfigDir(std::move(spec))); |
| namespace_dir->AddEntry(dir_split[0].ToString(), std::move(second_dir)); |
| } |
| |
| modular::PseudoDirServer server(std::move(namespace_dir)); |
| modular::ModularConfigReader config_reader(server.OpenAt(".")); |
| EXPECT_EQ(kSessionShellForTest, config_reader.GetBasemgrConfig() |
| .session_shell_map() |
| .at(0) |
| .config() |
| .app_config() |
| .url()); |
| } |
| |
| // Test that additional injected services are made available, spin up the |
| // associated component when requested. This test exercises overriding a default |
| // injected service. |
| TEST_F(TestHarnessImplTest, DefaultInjectedServices) { |
| fuchsia::modular::testing::TestHarnessSpec spec; |
| |
| auto generated_accountmgr_url = GenerateFakeUrl(); |
| |
| spec.mutable_env_services_to_inject()->push_back( |
| fuchsia::modular::testing::InjectedService{ |
| // Override the default injected AccountManager. |
| .name = fuchsia::auth::account::AccountManager::Name_, |
| .url = generated_accountmgr_url}); |
| |
| // Intercept the component URL which supplies AccountManager. |
| { |
| fuchsia::modular::testing::InterceptSpec intercept_spec; |
| intercept_spec.set_component_url(generated_accountmgr_url); |
| spec.mutable_components_to_intercept()->push_back( |
| std::move(intercept_spec)); |
| } |
| |
| bool intercepted_accountmgr = false; |
| test_harness().events().OnNewComponent = |
| [&](fuchsia::sys::StartupInfo startup_info, |
| fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent> |
| component) { |
| if (startup_info.launch_info.url == generated_accountmgr_url) { |
| intercepted_accountmgr = true; |
| } else { |
| ASSERT_FALSE("Started for no reason."); |
| } |
| }; |
| |
| test_harness()->Run(std::move(spec)); |
| |
| fuchsia::auth::account::AccountManagerPtr accountmgr; |
| test_harness()->ConnectToEnvironmentService( |
| fuchsia::auth::account::AccountManager::Name_, |
| accountmgr.NewRequest().TakeChannel()); |
| |
| RunLoopUntil([&] { return intercepted_accountmgr; }); |
| } |
| |
| // Test that additional injected services are made available, spin up the |
| // associated component when requested. This test exercises injecting a custom |
| // service. |
| TEST_F(TestHarnessImplTest, CustomInjectedServices) { |
| fuchsia::modular::testing::TestHarnessSpec spec; |
| |
| auto generated_componentctx_url = GenerateFakeUrl(); |
| |
| spec.mutable_env_services_to_inject()->push_back( |
| fuchsia::modular::testing::InjectedService{ |
| // Provide a custom injected service. |
| .name = fuchsia::modular::ComponentContext::Name_, |
| .url = generated_componentctx_url}); |
| |
| // Intercept the component URL which supplies ComponentContext. |
| { |
| fuchsia::modular::testing::InterceptSpec intercept_spec; |
| intercept_spec.set_component_url(generated_componentctx_url); |
| spec.mutable_components_to_intercept()->push_back( |
| std::move(intercept_spec)); |
| } |
| |
| bool intercepted_componentctx = false; |
| test_harness().events().OnNewComponent = |
| [&](fuchsia::sys::StartupInfo startup_info, |
| fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent> |
| component) { |
| if (startup_info.launch_info.url == generated_componentctx_url) { |
| intercepted_componentctx = true; |
| } else { |
| ASSERT_FALSE("Started for no reason."); |
| } |
| }; |
| |
| test_harness()->Run(std::move(spec)); |
| |
| fuchsia::modular::ComponentContextPtr componentctx; |
| test_harness()->ConnectToEnvironmentService( |
| fuchsia::modular::ComponentContext::Name_, |
| componentctx.NewRequest().TakeChannel()); |
| |
| RunLoopUntil([&] { return intercepted_componentctx; }); |
| } |
| |
| TEST_F(TestHarnessImplTest, InterceptBaseShell) { |
| // Setup base shell interception. |
| fuchsia::modular::testing::InterceptSpec shell_intercept_spec; |
| shell_intercept_spec.set_component_url(kFakeBaseShellUrl); |
| |
| fuchsia::modular::testing::TestHarnessSpec spec; |
| spec.mutable_basemgr_config() |
| ->mutable_base_shell() |
| ->mutable_app_config() |
| ->set_url(kFakeBaseShellUrl); |
| spec.mutable_components_to_intercept()->push_back( |
| std::move(shell_intercept_spec)); |
| |
| // Listen for base shell interception. |
| bool intercepted = false; |
| |
| test_harness().events().OnNewComponent = |
| [&](fuchsia::sys::StartupInfo startup_info, |
| fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent> |
| component) { |
| ASSERT_EQ(kFakeBaseShellUrl, startup_info.launch_info.url); |
| intercepted = true; |
| }; |
| |
| test_harness()->Run(std::move(spec)); |
| |
| RunLoopUntil([&] { return intercepted; }); |
| }; |
| |
| TEST_F(TestHarnessImplTest, InterceptSessionShell) { |
| fuchsia::modular::testing::TestHarnessSpec spec; |
| |
| // 1. Setup session shell interception. |
| fuchsia::modular::testing::InterceptSpec shell_intercept_spec; |
| shell_intercept_spec.set_component_url(kFakeSessionShellUrl); |
| { |
| fuchsia::modular::session::SessionShellMapEntry entry; |
| entry.mutable_config()->mutable_app_config()->set_url(kFakeSessionShellUrl); |
| |
| spec.mutable_basemgr_config()->mutable_session_shell_map()->push_back( |
| std::move(entry)); |
| } |
| spec.mutable_components_to_intercept()->push_back( |
| std::move(shell_intercept_spec)); |
| |
| // 2. Listen for session shell interception. |
| bool intercepted = false; |
| test_harness().events().OnNewComponent = |
| [&intercepted]( |
| fuchsia::sys::StartupInfo startup_info, |
| fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent> |
| component) { |
| if (startup_info.launch_info.url == kFakeSessionShellUrl) |
| intercepted = true; |
| }; |
| |
| test_harness()->Run(std::move(spec)); |
| |
| RunLoopUntil([&] { return intercepted; }); |
| }; |
| |
| TEST_F(TestHarnessImplTest, InterceptStoryShellAndModule) { |
| // Setup story shell interception. |
| fuchsia::modular::testing::InterceptSpec shell_intercept_spec; |
| shell_intercept_spec.set_component_url(kFakeStoryShellUrl); |
| |
| fuchsia::modular::testing::TestHarnessSpec spec; |
| spec.mutable_basemgr_config() |
| ->mutable_story_shell() |
| ->mutable_app_config() |
| ->set_url(shell_intercept_spec.component_url()); |
| spec.mutable_components_to_intercept()->push_back( |
| std::move(shell_intercept_spec)); |
| |
| // Setup kFakeModuleUrl interception. |
| { |
| fuchsia::modular::testing::InterceptSpec intercept_spec; |
| intercept_spec.set_component_url(kFakeModuleUrl); |
| spec.mutable_components_to_intercept()->push_back( |
| std::move(intercept_spec)); |
| } |
| |
| // Listen for story shell interception. |
| bool story_shell_intercepted = false; |
| // Listen for module interception. |
| bool fake_module_intercepted = false; |
| |
| test_harness().events().OnNewComponent = |
| [&](fuchsia::sys::StartupInfo startup_info, |
| fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent> |
| component) { |
| if (startup_info.launch_info.url == kFakeModuleUrl) { |
| fake_module_intercepted = true; |
| } else if (startup_info.launch_info.url == kFakeStoryShellUrl) { |
| story_shell_intercepted = true; |
| } |
| }; |
| test_harness()->Run(std::move(spec)); |
| |
| // Create a new story -- this should auto-start the story (because of |
| // test_session_shell's behaviour), and launch a new story shell. |
| fuchsia::modular::PuppetMasterPtr puppet_master; |
| fuchsia::modular::StoryPuppetMasterPtr story_master; |
| |
| fuchsia::modular::testing::TestHarnessService svc; |
| svc.set_puppet_master(puppet_master.NewRequest()); |
| test_harness()->GetService(std::move(svc)); |
| |
| puppet_master->ControlStory("my_story", story_master.NewRequest()); |
| |
| using fuchsia::modular::AddMod; |
| using fuchsia::modular::StoryCommand; |
| |
| std::vector<StoryCommand> cmds; |
| StoryCommand cmd; |
| AddMod add_mod; |
| add_mod.mod_name = {"mod_name"}; |
| add_mod.intent.handler = kFakeModuleUrl; |
| add_mod.surface_relation = fuchsia::modular::SurfaceRelation{}; |
| cmd.set_add_mod(std::move(add_mod)); |
| cmds.push_back(std::move(cmd)); |
| |
| story_master->Enqueue(std::move(cmds)); |
| story_master->Execute([](fuchsia::modular::ExecuteResult result) {}); |
| |
| ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&] { return story_shell_intercepted; }, |
| zx::sec(10))); |
| ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&] { return fake_module_intercepted; }, |
| zx::sec(10))); |
| }; |
| |
| } // namespace |
| } // namespace testing |
| } // namespace modular |