| // 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 "src/modular/lib/modular_config/modular_config.h" |
| |
| #include <fcntl.h> |
| #include <lib/vfs/cpp/pseudo_dir.h> |
| #include <lib/vfs/cpp/pseudo_file.h> |
| |
| #include <thread> |
| |
| #include <fbl/unique_fd.h> |
| #include <rapidjson/document.h> |
| #include <src/lib/files/directory.h> |
| #include <src/lib/files/file.h> |
| #include <src/lib/files/path.h> |
| #include <src/lib/files/scoped_temp_dir.h> |
| #include <src/modular/lib/pseudo_dir/pseudo_dir_server.h> |
| #include <src/modular/lib/pseudo_dir/pseudo_dir_utils.h> |
| |
| #include "src/lib/fsl/io/fd.h" |
| #include "src/lib/fxl/strings/split_string.h" |
| #include "src/lib/fxl/strings/substitute.h" |
| #include "src/lib/testing/loop_fixture/real_loop_fixture.h" |
| #include "src/modular/lib/modular_config/modular_config_constants.h" |
| |
| // Creates the file at |path| in the directory with file descriptor |root_fd| with |
| // the contents |data|. |
| // |
| // If necessary, creates the |path| directory, including intermediate directories. |
| bool CreateFileAt(int root_fd, const std::string& path, std::string_view data) { |
| if (!files::CreateDirectoryAt(root_fd, files::GetDirectoryName(path))) { |
| return false; |
| } |
| if (!files::WriteFileAt(root_fd, path, data.data(), data.size())) { |
| return false; |
| } |
| return true; |
| } |
| |
| class ModularConfigReaderTest : public gtest::RealLoopFixture {}; |
| class ModularConfigWriterTest : public gtest::RealLoopFixture {}; |
| |
| // Test that ModularConfigReader finds and reads the startup.config file from the overridden |
| // config directory. |
| TEST_F(ModularConfigReaderTest, OverrideConfigDir) { |
| constexpr char kSessionShellForTest[] = |
| "fuchsia-pkg://example.com/ModularConfigReaderTest#meta/" |
| "ModularConfigReaderTest.cmx"; |
| |
| std::string config_contents = fxl::Substitute( |
| R"({ |
| "basemgr": { |
| "session_shells": [ |
| { |
| "url": "$0" |
| } |
| ] |
| } |
| })", |
| kSessionShellForTest); |
| |
| modular::PseudoDirServer server(modular::MakeFilePathWithContents( |
| modular::ModularConfigReader::GetOverriddenConfigPath(), config_contents)); |
| |
| modular::ModularConfigReader reader(server.OpenAt(".")); |
| auto config = reader.GetBasemgrConfig(); |
| |
| // Verify that ModularConfigReader parsed the config value we gave it. |
| EXPECT_EQ(kSessionShellForTest, config.session_shell_map().at(0).config().app_config().url()); |
| } |
| |
| // Test that ModularConfigReader finds and reads the startup.config file in the package directory. |
| TEST_F(ModularConfigReaderTest, PackagedConfigDir) { |
| constexpr char kSessionShellForTest[] = |
| "fuchsia-pkg://example.com/ModularConfigReaderTest#meta/" |
| "ModularConfigReaderTest.cmx"; |
| |
| std::string config_contents = fxl::Substitute( |
| R"({ |
| "basemgr": { |
| "session_shells": [ |
| { |
| "url": "$0" |
| } |
| ] |
| } |
| })", |
| kSessionShellForTest); |
| |
| modular::PseudoDirServer server(modular::MakeFilePathWithContents( |
| modular::ModularConfigReader::GetPackagedConfigPath(), config_contents)); |
| |
| modular::ModularConfigReader reader(server.OpenAt(".")); |
| auto config = reader.GetBasemgrConfig(); |
| |
| // Verify that ModularConfigReader parsed the config value we gave it. |
| EXPECT_EQ(kSessionShellForTest, config.session_shell_map().at(0).config().app_config().url()); |
| } |
| |
| // Test that ModularConfigReader uses default values if it fails to read the config data from file. |
| TEST_F(ModularConfigReaderTest, FailToReadConfigDir) { |
| // Create a root directory without a config file. |
| modular::PseudoDirServer server(std::make_unique<vfs::PseudoDir>()); |
| modular::ModularConfigReader reader(server.OpenAt(".")); |
| |
| auto config = reader.GetBasemgrConfig(); |
| EXPECT_EQ(modular_config::kDefaultSessionShellUrl, |
| config.session_shell_map().at(0).config().app_config().url()); |
| } |
| |
| // Test that ModularConfigReader finds and reads the AgentServiceIndex. |
| TEST_F(ModularConfigReaderTest, ProvideAgentServiceIndex) { |
| const std::string kServiceNameForTest = "fuchsia.modular.ModularConfigReaderTest"; |
| const std::string kAgentUrlForTest = |
| "fuchsia-pkg://example.com/ModularConfigReaderTest#meta/" |
| "ModularConfigReaderTest.cmx"; |
| |
| const std::string service_name_0 = kServiceNameForTest + "0"; |
| const std::string agent_url_0 = kAgentUrlForTest + "0"; |
| const std::string service_name_1 = kServiceNameForTest + "1"; |
| const std::string agent_url_1 = kAgentUrlForTest + "1"; |
| |
| std::string config_contents = fxl::Substitute( |
| R"({ |
| "sessionmgr": { |
| "agent_service_index": [ |
| { |
| "service_name": "$0", |
| "agent_url": "$1" |
| }, |
| { |
| "service_name": "$2", |
| "agent_url": "$3" |
| } |
| ] |
| } |
| })", |
| service_name_0, agent_url_0, service_name_1, agent_url_1); |
| |
| modular::PseudoDirServer server(modular::MakeFilePathWithContents( |
| files::JoinPath(modular_config::kOverriddenConfigDir, modular_config::kStartupConfigFilePath), |
| config_contents)); |
| |
| modular::ModularConfigReader reader(server.OpenAt(".")); |
| auto config = reader.GetSessionmgrConfig(); |
| |
| // Verify that ModularConfigReader parsed the config values we gave it. |
| EXPECT_EQ(service_name_0, config.agent_service_index().at(0).service_name()); |
| EXPECT_EQ(agent_url_0, config.agent_service_index().at(0).agent_url()); |
| EXPECT_EQ(service_name_1, config.agent_service_index().at(1).service_name()); |
| EXPECT_EQ(agent_url_1, config.agent_service_index().at(1).agent_url()); |
| } |
| |
| TEST_F(ModularConfigReaderTest, GetConfigAsString) { |
| std::string startup_agent = "fuchsia-pkg://fuchsia.com/startup_agent#meta/startup_agent.cmx"; |
| std::string agent_service_name = "fuchsia.modular.ModularConfigReaderTest"; |
| std::string agent_url = |
| "fuchsia-pkg://example.com/ModularConfigReaderTest#meta/" |
| "ModularConfigReaderTest.cmx"; |
| |
| std::string config_contents = fxl::Substitute( |
| R"({ |
| "sessionmgr": { |
| "startup_agents": [ |
| "$0" |
| ], |
| "agent_service_index": [ |
| { |
| "service_name": "$1", |
| "agent_url": "$2" |
| } |
| ] |
| } |
| })", |
| startup_agent, agent_service_name, agent_url); |
| |
| // Host |config_contents|, parse it into |first_reader|, and write configs into |read_config_str| |
| modular::PseudoDirServer server(modular::MakeFilePathWithContents( |
| modular::ModularConfigReader::GetConfigDataConfigPath(), config_contents)); |
| |
| modular::ModularConfigReader first_reader(server.OpenAt(".")); |
| auto basemgr_config = first_reader.GetBasemgrConfig(); |
| auto sessionmgr_config = first_reader.GetSessionmgrConfig(); |
| auto read_config_str = |
| modular::ModularConfigReader::GetConfigAsString(&basemgr_config, &sessionmgr_config); |
| |
| // Host the new config string and parse it into |second_reader| |
| modular::PseudoDirServer server_after_read(modular::MakeFilePathWithContents( |
| modular::ModularConfigReader::GetConfigDataConfigPath(), read_config_str)); |
| |
| modular::ModularConfigReader second_reader(server_after_read.OpenAt(".")); |
| |
| // Verify that the second reader has the same config values as the original |config_contents| |
| sessionmgr_config = second_reader.GetSessionmgrConfig(); |
| EXPECT_EQ(startup_agent, sessionmgr_config.startup_agents().at(0)); |
| EXPECT_EQ(agent_service_name, sessionmgr_config.agent_service_index().at(0).service_name()); |
| EXPECT_EQ(agent_url, sessionmgr_config.agent_service_index().at(0).agent_url()); |
| } |
| |
| TEST_F(ModularConfigReaderTest, GetConfigAsStringDoesntChangeValues) { |
| std::string startup_agent = "fuchsia-pkg://fuchsia.com/startup_agent#meta/startup_agent.cmx"; |
| std::string session_agent = "fuchsia-pkg://fuchsia.com/session_agent#meta/session_agent.cmx"; |
| std::string agent_service_name = "fuchsia.modular.ModularConfigReaderTest"; |
| std::string agent_url = |
| "fuchsia-pkg://example.com/ModularConfigReaderTest#meta/" |
| "ModularConfigReaderTest.cmx"; |
| std::string session_shell_url = "fuchsia-pkg://fuchsia.com/session_shell#meta/session_shell.cmx"; |
| std::string story_shell_url = "fuchsia-pkg://fuchsia.com/story_shell#meta/story_shell.cmx"; |
| |
| fuchsia::modular::session::BasemgrConfig basemgr_config; |
| basemgr_config.set_enable_cobalt(false); |
| basemgr_config.set_use_session_shell_for_story_shell_factory(true); |
| fuchsia::modular::session::SessionShellConfig session_shell_config; |
| session_shell_config.mutable_app_config()->set_url(session_shell_url); |
| fuchsia::modular::session::SessionShellMapEntry session_shell_map_entry; |
| session_shell_map_entry.set_name(session_shell_url); |
| session_shell_map_entry.set_config(std::move(session_shell_config)); |
| basemgr_config.mutable_session_shell_map()->push_back(std::move(session_shell_map_entry)); |
| fuchsia::modular::session::StoryShellConfig story_shell_config; |
| story_shell_config.mutable_app_config()->set_url(story_shell_url); |
| story_shell_config.mutable_app_config()->mutable_args()->push_back("arg1"); |
| basemgr_config.set_story_shell(std::move(story_shell_config)); |
| |
| fuchsia::modular::session::SessionmgrConfig sessionmgr_config; |
| sessionmgr_config.set_enable_cobalt(false); |
| sessionmgr_config.mutable_startup_agents()->push_back(startup_agent); |
| sessionmgr_config.mutable_session_agents()->push_back(session_agent); |
| sessionmgr_config.set_story_shell_url(story_shell_url); |
| fuchsia::modular::session::AppConfig component_arg; |
| component_arg.set_url(agent_url); |
| component_arg.mutable_args()->push_back("arg2"); |
| sessionmgr_config.mutable_component_args()->push_back(std::move(component_arg)); |
| fuchsia::modular::session::AgentServiceIndexEntry agent_entry; |
| agent_entry.set_service_name(agent_service_name); |
| agent_entry.set_agent_url(agent_url); |
| sessionmgr_config.mutable_agent_service_index()->push_back(std::move(agent_entry)); |
| sessionmgr_config.mutable_restart_session_on_agent_crash()->push_back(agent_url); |
| sessionmgr_config.set_disable_agent_restart_on_crash(true); |
| |
| modular::ModularConfigReader::GetConfigAsString(&basemgr_config, &sessionmgr_config); |
| |
| // Check that none of the configs were modified as part of GetConfigAsString |
| EXPECT_FALSE(basemgr_config.enable_cobalt()); |
| EXPECT_TRUE(basemgr_config.use_session_shell_for_story_shell_factory()); |
| ASSERT_EQ(1u, basemgr_config.session_shell_map().size()); |
| EXPECT_EQ(session_shell_url, basemgr_config.session_shell_map().at(0).name()); |
| EXPECT_EQ(session_shell_url, |
| basemgr_config.session_shell_map().at(0).config().app_config().url()); |
| EXPECT_EQ(story_shell_url, basemgr_config.story_shell().app_config().url()); |
| ASSERT_EQ(1u, basemgr_config.story_shell().app_config().args().size()); |
| EXPECT_EQ("arg1", basemgr_config.story_shell().app_config().args().at(0)); |
| |
| EXPECT_FALSE(sessionmgr_config.enable_cobalt()); |
| EXPECT_EQ(startup_agent, sessionmgr_config.startup_agents().at(0)); |
| EXPECT_EQ(session_agent, sessionmgr_config.session_agents().at(0)); |
| EXPECT_EQ(story_shell_url, sessionmgr_config.story_shell_url()); |
| EXPECT_EQ(agent_url, sessionmgr_config.component_args().at(0).url()); |
| ASSERT_EQ(1u, sessionmgr_config.component_args().at(0).args().size()); |
| EXPECT_EQ("arg2", sessionmgr_config.component_args().at(0).args().at(0)); |
| EXPECT_EQ(agent_service_name, sessionmgr_config.agent_service_index().at(0).service_name()); |
| EXPECT_EQ(agent_url, sessionmgr_config.agent_service_index().at(0).agent_url()); |
| EXPECT_EQ(agent_url, sessionmgr_config.restart_session_on_agent_crash().at(0)); |
| EXPECT_TRUE(sessionmgr_config.disable_agent_restart_on_crash()); |
| } |
| |
| // Test that ModularConfigReader accepts JSON documents that contain comments |
| TEST_F(ModularConfigReaderTest, ParseComments) { |
| std::string config_contents = R"({ |
| "basemgr": { |
| /* This is |
| * a comment */ |
| "session_shells": [ |
| { |
| // This is another comment |
| "url": "fuchsia-pkg://example.com/test#meta/test.cmx" |
| } |
| ] |
| } |
| })"; |
| |
| modular::PseudoDirServer server(modular::MakeFilePathWithContents( |
| files::JoinPath(modular_config::kOverriddenConfigDir, modular_config::kStartupConfigFilePath), |
| config_contents)); |
| |
| modular::ModularConfigReader reader(server.OpenAt(".")); |
| auto config = reader.GetBasemgrConfig(); |
| |
| // Verify that ModularConfigReader parsed the config. |
| EXPECT_EQ(1u, config.session_shell_map().size()); |
| } |
| |
| // Tests that ParseConfig successfully parses a valid Modular config JSON string with |
| // some non-default values set. |
| TEST_F(ModularConfigReaderTest, ParseConfigOk) { |
| static constexpr auto kConfigJson = R"({ |
| "basemgr": { |
| "enable_cobalt": false |
| }, |
| "sessionmgr": { |
| "enable_cobalt": false |
| } |
| })"; |
| |
| auto config_result = modular::ParseConfig(kConfigJson); |
| ASSERT_TRUE(config_result.is_ok()); |
| |
| auto config = config_result.take_value(); |
| ASSERT_TRUE(config.has_basemgr_config()); |
| EXPECT_FALSE(config.basemgr_config().enable_cobalt()); |
| ASSERT_TRUE(config.has_sessionmgr_config()); |
| EXPECT_FALSE(config.sessionmgr_config().enable_cobalt()); |
| } |
| |
| // Tests that ParseConfig return an error when passed a string that doesn't contain valid JSON. |
| TEST_F(ModularConfigReaderTest, ParseConfigInvalidJson) { |
| static constexpr auto kConfigJson = R"(this is not valid JSON)"; |
| |
| auto config_result = modular::ParseConfig(kConfigJson); |
| EXPECT_TRUE(config_result.is_error()); |
| } |
| |
| // Tests that ParseConfig return an error when passed a JSON string that doesn't match |
| // the Modular config schema. |
| TEST_F(ModularConfigReaderTest, ParseConfigInvalidSchema) { |
| static constexpr auto kConfigJson = R"({ |
| "basemgr": { |
| "session_shells": { |
| "this is valid JSON but not a valid modular config" |
| } |
| } |
| })"; |
| |
| auto config_result = modular::ParseConfig(kConfigJson); |
| EXPECT_TRUE(config_result.is_error()); |
| } |
| |
| // Tests that DefaultConfig returns a ModularConfig with some default values. |
| TEST_F(ModularConfigReaderTest, DefaultConfig) { |
| auto config = modular::DefaultConfig(); |
| |
| ASSERT_TRUE(config.has_basemgr_config()); |
| EXPECT_TRUE(config.basemgr_config().enable_cobalt()); |
| ASSERT_EQ(1u, config.basemgr_config().session_shell_map().size()); |
| EXPECT_EQ(modular_config::kDefaultSessionShellUrl, |
| config.basemgr_config().session_shell_map().at(0).config().app_config().url()); |
| |
| ASSERT_TRUE(config.has_sessionmgr_config()); |
| EXPECT_TRUE(config.sessionmgr_config().enable_cobalt()); |
| } |
| |
| // Tests that ConfigToJsonString returns a JSON string containing a serialized ModularConfig. |
| TEST_F(ModularConfigReaderTest, ConfigToJsonString) { |
| static constexpr auto kExpectedJson = R"({ |
| "basemgr": { |
| "enable_cobalt": true, |
| "use_session_shell_for_story_shell_factory": false, |
| "session_shells": [ |
| { |
| "url": "fuchsia-pkg://fuchsia.com/dev_session_shell#meta/dev_session_shell.cmx", |
| "args": [] |
| } |
| ], |
| "story_shell_url": "fuchsia-pkg://fuchsia.com/dev_story_shell#meta/dev_story_shell.cmx" |
| }, |
| "sessionmgr": { |
| "enable_cobalt": true, |
| "startup_agents": [], |
| "session_agents": [], |
| "component_args": [], |
| "agent_service_index": [], |
| "restart_session_on_agent_crash": [], |
| "disable_agent_restart_on_crash": false |
| } |
| })"; |
| rapidjson::Document expected_json_doc; |
| expected_json_doc.Parse(kExpectedJson); |
| ASSERT_FALSE(expected_json_doc.HasParseError()); |
| |
| fuchsia::modular::session::ModularConfig config; |
| auto config_json = modular::ConfigToJsonString(config); |
| |
| rapidjson::Document config_json_doc; |
| config_json_doc.Parse(config_json); |
| EXPECT_FALSE(config_json_doc.HasParseError()); |
| |
| EXPECT_EQ(expected_json_doc, config_json_doc) |
| << "Expected: " << kExpectedJson << "\nActual: " << config_json; |
| } |
| |
| // Tests that ModularConfigReader reads from the persistent configuration when it exists and |
| // persistent config_override is allowed. |
| TEST_F(ModularConfigReaderTest, ReadPersistentConfig) { |
| constexpr char kSessionShellForTest[] = |
| "fuchsia-pkg://example.com/ModularConfigReaderTest#meta/" |
| "ModularConfigReaderTest.cmx"; |
| |
| std::string persistent_config_contents = fxl::Substitute( |
| R"({ |
| "basemgr": { |
| "session_shells": [ |
| { |
| "url": "$0" |
| } |
| ] |
| } |
| })", |
| kSessionShellForTest); |
| |
| // Create a directory that simulates the component namespace with the layout: |
| // |
| // / |
| // ├── cache |
| // │ └── startup.config |
| // └── config |
| // └── data |
| // ├── startup.config |
| // └── allow_persistent_config_override |
| files::ScopedTempDir root_dir; |
| fbl::unique_fd root_fd(open(root_dir.path().c_str(), O_RDONLY)); |
| |
| // The persistent /cache/startup.config file contains |persistent_config_contents|. |
| ASSERT_TRUE(CreateFileAt(root_fd.get(), modular::ModularConfigReader::GetPersistentConfigPath(), |
| persistent_config_contents)); |
| |
| // The /config/data/startup.config file contains an empty config. |
| ASSERT_TRUE( |
| CreateFileAt(root_fd.get(), modular::ModularConfigReader::GetConfigDataConfigPath(), "{}")); |
| |
| // Allow persistent config_override. |
| ASSERT_TRUE(CreateFileAt(root_fd.get(), |
| modular::ModularConfigReader::GetAllowPersistentConfigOverridePath(), |
| "(file contents are ignored)")); |
| |
| modular::ModularConfigReader reader(std::move(root_fd)); |
| |
| // Verify that ModularConfigReader read from the persistent config in /cache. |
| auto basemgr_config = reader.GetBasemgrConfig(); |
| EXPECT_EQ(kSessionShellForTest, |
| basemgr_config.session_shell_map().at(0).config().app_config().url()); |
| } |
| |
| // Tests that ModularConfigReader reads from the regular config_override when persistence is |
| // not allowed even if a persistent config exists. |
| TEST_F(ModularConfigReaderTest, ReadPersistentConfigNotAllowed) { |
| constexpr char kSessionShellForTest[] = |
| "fuchsia-pkg://example.com/ModularConfigReaderTest#meta/" |
| "ModularConfigReaderTest.cmx"; |
| |
| std::string config_contents = fxl::Substitute( |
| R"({ |
| "basemgr": { |
| "session_shells": [ |
| { |
| "url": "$0" |
| } |
| ] |
| } |
| })", |
| kSessionShellForTest); |
| |
| // Create a directory that simulates the component namespace with the layout: |
| // |
| // / |
| // ├── cache |
| // │ └── startup.config |
| // ├── config |
| // │ └── data |
| // └── config_override |
| // └── data |
| // └── startup.config |
| files::ScopedTempDir root_dir; |
| fbl::unique_fd root_fd(open(root_dir.path().c_str(), O_RDONLY)); |
| |
| // The persistent /cache/startup.config file exists but its contents won't be read. |
| ASSERT_TRUE(CreateFileAt(root_fd.get(), modular::ModularConfigReader::GetPersistentConfigPath(), |
| "(file contents are ignored)")); |
| |
| // The /config_override/data/startup.config file contains |config_contents|. |
| ASSERT_TRUE(CreateFileAt(root_fd.get(), modular::ModularConfigReader::GetOverriddenConfigPath(), |
| config_contents)); |
| |
| // Create the directory where the allow_persistent_config_override marker file |
| // should be, but not the file itself. |
| ASSERT_TRUE(files::CreateDirectoryAt( |
| root_fd.get(), files::GetDirectoryName( |
| modular::ModularConfigReader::GetAllowPersistentConfigOverridePath()))); |
| |
| modular::ModularConfigReader reader(std::move(root_fd)); |
| |
| // Verify that ModularConfigReader read from /config_override. |
| auto basemgr_config = reader.GetBasemgrConfig(); |
| EXPECT_EQ(kSessionShellForTest, |
| basemgr_config.session_shell_map().at(0).config().app_config().url()); |
| } |
| |
| // Tests that ModularConfigReader.ReadAndMaybePersistConfig stores configuration from |
| // config_override to the persistent config dir when allowed. |
| TEST_F(ModularConfigReaderTest, ReadAndMaybePersistConfig) { |
| // Create a directory that simulates the component namespace with the layout: |
| // |
| // / |
| // ├── cache |
| // ├── config |
| // │ └── data |
| // │ └── allow_persistent_config_override |
| // └── config_override |
| // └── data |
| // └── startup.config |
| files::ScopedTempDir root_dir; |
| fbl::unique_fd root_fd(open(root_dir.path().c_str(), O_RDONLY)); |
| |
| auto persistent_config_dir = |
| files::GetDirectoryName(modular::ModularConfigReader::GetPersistentConfigPath()); |
| |
| // Create the directory where the persistent config should be, but not the file itself. |
| ASSERT_TRUE(files::CreateDirectoryAt(root_fd.get(), persistent_config_dir)); |
| |
| // The /config_override/data/startup.config file contains an empty config. |
| ASSERT_TRUE( |
| CreateFileAt(root_fd.get(), modular::ModularConfigReader::GetOverriddenConfigPath(), "{}")); |
| |
| // Allow persistent config_override. |
| ASSERT_TRUE(CreateFileAt(root_fd.get(), |
| modular::ModularConfigReader::GetAllowPersistentConfigOverridePath(), |
| "(file contents are ignored)")); |
| |
| modular::ModularConfigReader reader(root_fd.duplicate()); |
| |
| fbl::unique_fd write_fd( |
| openat(root_fd.get(), persistent_config_dir.c_str(), O_RDONLY | O_DIRECTORY)); |
| ASSERT_TRUE(write_fd.is_valid()); |
| modular::ModularConfigWriter writer(std::move(write_fd)); |
| |
| auto config_result = reader.ReadAndMaybePersistConfig(&writer); |
| ASSERT_TRUE(config_result.is_ok()); |
| |
| // Verify that ReadAndMaybePersistConfig persisted the configuration. |
| EXPECT_TRUE(reader.PersistentConfigExists()); |
| EXPECT_TRUE( |
| files::IsFileAt(root_fd.get(), modular::ModularConfigReader::GetPersistentConfigPath())); |
| } |
| |
| // Tests that ModularConfigReader.ReadAndMaybePersistConfig does not store configuration from |
| // config_override to the persistent config dir when not allowed. |
| TEST_F(ModularConfigReaderTest, ReadAndMaybePersistConfigNotAllowed) { |
| // Create a directory that simulates the component namespace with the layout: |
| // |
| // / |
| // ├── cache |
| // ├── config |
| // │ └── data |
| // └── config_override |
| // └── data |
| // └── startup.config |
| files::ScopedTempDir root_dir; |
| fbl::unique_fd root_fd(open(root_dir.path().c_str(), O_RDONLY)); |
| |
| auto persistent_config_dir = |
| files::GetDirectoryName(modular::ModularConfigReader::GetPersistentConfigPath()); |
| |
| // Create the directory where the persistent config should be, but not the file itself. |
| ASSERT_TRUE(files::CreateDirectoryAt(root_fd.get(), persistent_config_dir)); |
| |
| // The /config_override/data/startup.config file contains an empty config. |
| ASSERT_TRUE( |
| CreateFileAt(root_fd.get(), modular::ModularConfigReader::GetOverriddenConfigPath(), "{}")); |
| |
| // Create the directory where the allow_persistent_config_override marker file |
| // should be, but not the file itself. |
| ASSERT_TRUE(files::CreateDirectoryAt( |
| root_fd.get(), files::GetDirectoryName( |
| modular::ModularConfigReader::GetAllowPersistentConfigOverridePath()))); |
| |
| modular::ModularConfigReader reader(root_fd.duplicate()); |
| |
| fbl::unique_fd write_fd( |
| openat(root_fd.get(), persistent_config_dir.c_str(), O_RDONLY | O_DIRECTORY)); |
| ASSERT_TRUE(write_fd.is_valid()); |
| modular::ModularConfigWriter writer(std::move(write_fd)); |
| |
| auto config_result = reader.ReadAndMaybePersistConfig(&writer); |
| ASSERT_TRUE(config_result.is_ok()); |
| |
| // Verify that ReadAndMaybePersistConfig has not persisted the configuration. |
| EXPECT_FALSE(reader.PersistentConfigExists()); |
| EXPECT_FALSE( |
| files::IsFileAt(root_fd.get(), modular::ModularConfigReader::GetPersistentConfigPath())); |
| } |
| |
| // Tests that ModularConfigReader.ReadAndMaybePersistConfig overwrites existing |
| // persistent configuration. |
| TEST_F(ModularConfigReaderTest, ReadAndMaybePersistConfigOverwritesExisting) { |
| constexpr char kSessionShellForTest[] = |
| "fuchsia-pkg://example.com/ModularConfigReaderTest#meta/" |
| "ModularConfigReaderTest.cmx"; |
| |
| std::string config_contents = fxl::Substitute( |
| R"({ |
| "basemgr": { |
| "session_shells": [ |
| { |
| "url": "$0" |
| } |
| ] |
| } |
| })", |
| kSessionShellForTest); |
| |
| // Create a directory that simulates the component namespace with the layout: |
| // |
| // / |
| // ├── cache |
| // │ └── startup.config |
| // ├── config |
| // │ └── data |
| // │ └── allow_persistent_config_override |
| // └── config_override |
| // └── data |
| // └── startup.config |
| files::ScopedTempDir root_dir; |
| fbl::unique_fd root_fd(open(root_dir.path().c_str(), O_RDONLY)); |
| |
| auto persistent_config_dir = |
| files::GetDirectoryName(modular::ModularConfigReader::GetPersistentConfigPath()); |
| |
| // The persistent /cache/startup.config file contains an empty config. |
| ASSERT_TRUE( |
| CreateFileAt(root_fd.get(), modular::ModularConfigReader::GetPersistentConfigPath(), "{}")); |
| |
| // The /config_override/data/startup.config file contains a config with |kSessionShellForTest|. |
| ASSERT_TRUE(CreateFileAt(root_fd.get(), modular::ModularConfigReader::GetOverriddenConfigPath(), |
| config_contents)); |
| |
| // Allow persistent config_override. |
| ASSERT_TRUE(CreateFileAt(root_fd.get(), |
| modular::ModularConfigReader::GetAllowPersistentConfigOverridePath(), |
| "(file contents are ignored)")); |
| |
| modular::ModularConfigReader reader(root_fd.duplicate()); |
| |
| // The persistent config exists before ReadAndMaybePersistConfig is called. |
| ASSERT_TRUE(reader.PersistentConfigExists()); |
| ASSERT_TRUE( |
| files::IsFileAt(root_fd.get(), modular::ModularConfigReader::GetPersistentConfigPath())); |
| |
| fbl::unique_fd write_fd( |
| openat(root_fd.get(), persistent_config_dir.c_str(), O_RDONLY | O_DIRECTORY)); |
| ASSERT_TRUE(write_fd.is_valid()); |
| modular::ModularConfigWriter writer(std::move(write_fd)); |
| |
| auto config_result = reader.ReadAndMaybePersistConfig(&writer); |
| ASSERT_TRUE(config_result.is_ok()); |
| |
| // Read the config with a new reader. |
| modular::ModularConfigReader reader2(root_fd.duplicate()); |
| |
| ASSERT_TRUE(reader2.PersistentConfigExists()); |
| |
| // Verify that ModularConfigReader read from overwritten config. |
| auto basemgr_config = reader2.GetBasemgrConfig(); |
| EXPECT_EQ(kSessionShellForTest, |
| basemgr_config.session_shell_map().at(0).config().app_config().url()); |
| } |
| |
| // Tests that ModularConfigWriter.Delete deletes the persistent configuration file. |
| TEST_F(ModularConfigWriterTest, Delete) { |
| // Create a directory that simulates the component namespace with the layout: |
| // |
| // / |
| // └── cache |
| // └── startup.config |
| files::ScopedTempDir root_dir; |
| fbl::unique_fd root_fd(open(root_dir.path().c_str(), O_RDONLY)); |
| |
| auto persistent_config_dir = |
| files::GetDirectoryName(modular::ModularConfigReader::GetPersistentConfigPath()); |
| |
| // The persistent /cache/startup.config file contains an empty config. |
| ASSERT_TRUE( |
| CreateFileAt(root_fd.get(), modular::ModularConfigReader::GetPersistentConfigPath(), "{}")); |
| |
| modular::ModularConfigReader reader(root_fd.duplicate()); |
| |
| fbl::unique_fd write_fd( |
| openat(root_fd.get(), persistent_config_dir.c_str(), O_RDONLY | O_DIRECTORY)); |
| ASSERT_TRUE(write_fd.is_valid()); |
| modular::ModularConfigWriter writer(std::move(write_fd)); |
| |
| // The persistent config exists before Delete is called. |
| ASSERT_TRUE(reader.PersistentConfigExists()); |
| ASSERT_TRUE( |
| files::IsFileAt(root_fd.get(), modular::ModularConfigReader::GetPersistentConfigPath())); |
| |
| writer.Delete(); |
| |
| // The config should have been deleted. |
| EXPECT_FALSE(reader.PersistentConfigExists()); |
| EXPECT_FALSE( |
| files::IsFileAt(root_fd.get(), modular::ModularConfigReader::GetPersistentConfigPath())); |
| } |