| // Copyright 2019 The Chromium 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 "components/arc/session/arc_property_util.h" |
| |
| #include <memory> |
| |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/strings/stringprintf.h" |
| #include "chromeos/constants/chromeos_switches.h" |
| #include "components/arc/test/fake_cros_config.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace arc { |
| namespace { |
| |
| constexpr char kCrosConfigPropertiesPath[] = "/arc/build-properties"; |
| |
| class ArcPropertyUtilTest : public testing::Test { |
| public: |
| ArcPropertyUtilTest() = default; |
| ~ArcPropertyUtilTest() override = default; |
| ArcPropertyUtilTest(const ArcPropertyUtilTest&) = delete; |
| ArcPropertyUtilTest& operator=(const ArcPropertyUtilTest&) = delete; |
| |
| void SetUp() override { ASSERT_TRUE(dir_.CreateUniqueTempDir()); } |
| |
| protected: |
| const base::FilePath& GetTempDir() const { return dir_.GetPath(); } |
| |
| CrosConfig* config() { |
| if (!config_) |
| config_ = std::make_unique<CrosConfig>(); |
| return config_.get(); |
| } |
| |
| private: |
| std::unique_ptr<CrosConfig> config_; |
| base::ScopedTempDir dir_; |
| }; |
| |
| TEST_F(ArcPropertyUtilTest, TestPropertyExpansions) { |
| FakeCrosConfig config; |
| config.SetString("/arc/build-properties", "brand", "alphabet"); |
| |
| std::string expanded; |
| EXPECT_TRUE(ExpandPropertyContentsForTesting( |
| "line1\n{brand}\nline3\n{brand} {brand}", &config, &expanded)); |
| EXPECT_EQ("line1\nalphabet\nline3\nalphabet alphabet\n", expanded); |
| } |
| |
| TEST_F(ArcPropertyUtilTest, TestPropertyExpansionsUnmatchedBrace) { |
| FakeCrosConfig config; |
| config.SetString("/arc/build-properties", "brand", "alphabet"); |
| |
| std::string expanded; |
| EXPECT_FALSE(ExpandPropertyContentsForTesting("line{1\nline}2\nline3", |
| &config, &expanded)); |
| } |
| |
| TEST_F(ArcPropertyUtilTest, TestPropertyExpansionsRecursive) { |
| FakeCrosConfig config; |
| config.SetString("/arc/build-properties", "brand", "alphabet"); |
| config.SetString("/arc/build-properties", "model", "{brand} soup"); |
| |
| std::string expanded; |
| EXPECT_TRUE(ExpandPropertyContentsForTesting("{model}", &config, &expanded)); |
| EXPECT_EQ("alphabet soup\n", expanded); |
| } |
| |
| TEST_F(ArcPropertyUtilTest, TestPropertyExpansionsMissingProperty) { |
| FakeCrosConfig config; |
| config.SetString("/arc/build-properties", "model", "{brand} soup"); |
| |
| std::string expanded; |
| |
| EXPECT_FALSE(ExpandPropertyContentsForTesting("{missing-property}", &config, |
| &expanded)); |
| EXPECT_FALSE(ExpandPropertyContentsForTesting("{model}", &config, &expanded)); |
| } |
| |
| // Verify that ro.product.board gets copied to ro.oem.key1 as well. |
| TEST_F(ArcPropertyUtilTest, TestPropertyExpansionBoard) { |
| FakeCrosConfig config; |
| config.SetString("/arc/build-properties", "board", "testboard"); |
| |
| std::string expanded; |
| EXPECT_TRUE(ExpandPropertyContentsForTesting("ro.product.board={board}", |
| &config, &expanded)); |
| EXPECT_EQ("ro.product.board=testboard\nro.oem.key1=testboard\n", expanded); |
| } |
| |
| // Non-fingerprint property should do simple truncation. |
| TEST_F(ArcPropertyUtilTest, TestPropertyTruncation) { |
| std::string truncated; |
| EXPECT_TRUE(TruncateAndroidPropertyForTesting( |
| "property.name=" |
| "012345678901234567890123456789012345678901234567890123456789" |
| "01234567890123456789012345678901", |
| &truncated)); |
| EXPECT_EQ( |
| "property.name=0123456789012345678901234567890123456789" |
| "012345678901234567890123456789012345678901234567890", |
| truncated); |
| } |
| |
| // Fingerprint truncation with /release-keys should do simple truncation. |
| TEST_F(ArcPropertyUtilTest, TestPropertyTruncationFingerprintRelease) { |
| std::string truncated; |
| EXPECT_TRUE(TruncateAndroidPropertyForTesting( |
| "ro.bootimage.build.fingerprint=google/toolongdevicename/" |
| "toolongdevicename_cheets:7.1.1/R65-10299.0.9999/4538390:user/" |
| "release-keys", |
| &truncated)); |
| EXPECT_EQ( |
| "ro.bootimage.build.fingerprint=google/toolongdevicename/" |
| "toolongdevicename_cheets:7.1.1/R65-10299.0.9999/4538390:user/relea", |
| truncated); |
| } |
| |
| // Fingerprint truncation with /dev-keys needs to preserve the /dev-keys. |
| TEST_F(ArcPropertyUtilTest, TestPropertyTruncationFingerprintDev) { |
| std::string truncated; |
| EXPECT_TRUE(TruncateAndroidPropertyForTesting( |
| "ro.bootimage.build.fingerprint=google/toolongdevicename/" |
| "toolongdevicename_cheets:7.1.1/R65-10299.0.9999/4538390:user/dev-keys", |
| &truncated)); |
| EXPECT_EQ( |
| "ro.bootimage.build.fingerprint=google/toolongdevicena/" |
| "toolongdevicena_cheets/R65-10299.0.9999/4538390:user/dev-keys", |
| truncated); |
| } |
| |
| // Fingerprint truncation with the wrong format should fail. |
| TEST_F(ArcPropertyUtilTest, TestPropertyTruncationBadFingerprint) { |
| std::string truncated; |
| EXPECT_FALSE(TruncateAndroidPropertyForTesting( |
| "ro.bootimage.build.fingerprint=google/toolongdevicename/" |
| "toolongdevicename_cheets:7.1.1:123456789012345678901234567890/dev-keys", |
| &truncated)); |
| } |
| |
| // Fingerprint truncation without enough room should fail. |
| TEST_F(ArcPropertyUtilTest, TestPropertyTruncationFingerprintShortDevice) { |
| std::string truncated; |
| EXPECT_FALSE(TruncateAndroidPropertyForTesting( |
| "ro.bootimage.build.fingerprint=google/dev/" |
| "dev_cheets:7.1.1/R65-10299.0.9999/453839012345678901234567890" |
| "12345678901234567890:user/dev-keys", |
| &truncated)); |
| } |
| |
| // Tests that the GetString method works as intended. |
| TEST_F(ArcPropertyUtilTest, CrosConfig_GetString) { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| command_line->AppendSwitchASCII(chromeos::switches::kArcBuildProperties, |
| "{\"k1\":\"v1\",\"k2\":\"v2\",\"k3\":3}"); |
| std::string str; |
| EXPECT_TRUE(config()->GetString(kCrosConfigPropertiesPath, "k1", &str)); |
| EXPECT_EQ("v1", str); |
| EXPECT_TRUE(config()->GetString(kCrosConfigPropertiesPath, "k2", &str)); |
| EXPECT_EQ("v2", str); |
| // The value is not a string. |
| EXPECT_FALSE(config()->GetString(kCrosConfigPropertiesPath, "k3", &str)); |
| // The property path is invalid. |
| EXPECT_FALSE(config()->GetString("/unknown/path", "k1", &str)); |
| } |
| |
| // Tests that CrosConfig can handle the case where the command line is not |
| // passed. |
| TEST_F(ArcPropertyUtilTest, CrosConfig_NoCommandline) { |
| std::string str; |
| EXPECT_FALSE(config()->GetString(kCrosConfigPropertiesPath, "k1", &str)); |
| } |
| |
| // Tests that CrosConfig can handle an empty command line. |
| TEST_F(ArcPropertyUtilTest, CrosConfig_EmptyCommandline) { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| command_line->AppendSwitchASCII(chromeos::switches::kArcBuildProperties, ""); |
| std::string str; |
| EXPECT_FALSE(config()->GetString(kCrosConfigPropertiesPath, "k1", &str)); |
| } |
| |
| // Tests that CrosConfig can handle JSON whose top-level is not a dict. |
| TEST_F(ArcPropertyUtilTest, CrosConfig_NoDict) { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| command_line->AppendSwitchASCII(chromeos::switches::kArcBuildProperties, |
| "[\"k1\"]"); |
| std::string str; |
| EXPECT_FALSE(config()->GetString(kCrosConfigPropertiesPath, "k1", &str)); |
| } |
| |
| // Tests that CrosConfig can handle an invalid JSON. |
| TEST_F(ArcPropertyUtilTest, CrosConfig_InvalidJson) { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| command_line->AppendSwitchASCII(chromeos::switches::kArcBuildProperties, |
| "{\"k1\":}"); // parse error: no value |
| std::string str; |
| EXPECT_FALSE(config()->GetString(kCrosConfigPropertiesPath, "k1", &str)); |
| } |
| |
| // Tests that ExpandPropertyFile works as intended when no property expantion |
| // is needed. |
| TEST_F(ArcPropertyUtilTest, ExpandPropertyFile_NoExpansion) { |
| constexpr const char kValidProp[] = "ro.foo=bar\nro.baz=boo"; |
| base::FilePath path; |
| ASSERT_TRUE(CreateTemporaryFileInDir(GetTempDir(), &path)); |
| base::WriteFile(path, kValidProp, strlen(kValidProp)); |
| |
| const base::FilePath dest = GetTempDir().Append("new.prop"); |
| EXPECT_TRUE(ExpandPropertyFileForTesting(path, dest, config())); |
| std::string content; |
| EXPECT_TRUE(base::ReadFileToString(dest, &content)); |
| // Note: ExpandPropertyFileForTesting() adds a trailing LF. |
| EXPECT_EQ(std::string(kValidProp) + "\n", content); |
| } |
| |
| // Tests that ExpandPropertyFile works as intended when property expantion |
| // is needed. |
| TEST_F(ArcPropertyUtilTest, ExpandPropertyFile_Expansion) { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| command_line->AppendSwitchASCII(chromeos::switches::kArcBuildProperties, |
| "{\"k1\":\"v1\",\"k2\":\"v2\"}"); |
| constexpr const char kValidProp[] = "ro.foo={k1}\nro.baz={k2}"; |
| base::FilePath path; |
| ASSERT_TRUE(CreateTemporaryFileInDir(GetTempDir(), &path)); |
| base::WriteFile(path, kValidProp, strlen(kValidProp)); |
| |
| const base::FilePath dest = GetTempDir().Append("new.prop"); |
| EXPECT_TRUE(ExpandPropertyFileForTesting(path, dest, config())); |
| std::string content; |
| EXPECT_TRUE(base::ReadFileToString(dest, &content)); |
| // Note: ExpandPropertyFileForTesting() adds a trailing LF. |
| EXPECT_EQ("ro.foo=v1\nro.baz=v2\n", content); |
| } |
| |
| // Tests that ExpandPropertyFile works as intended when nested property |
| // expantion is needed. |
| TEST_F(ArcPropertyUtilTest, ExpandPropertyFile_NestedExpansion) { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| command_line->AppendSwitchASCII(chromeos::switches::kArcBuildProperties, |
| "{\"k1\":\"{k2}\",\"k2\":\"v2\"}"); |
| constexpr const char kValidProp[] = "ro.foo={k1}\nro.baz={k2}"; |
| base::FilePath path; |
| ASSERT_TRUE(CreateTemporaryFileInDir(GetTempDir(), &path)); |
| base::WriteFile(path, kValidProp, strlen(kValidProp)); |
| |
| const base::FilePath dest = GetTempDir().Append("new.prop"); |
| EXPECT_TRUE(ExpandPropertyFileForTesting(path, dest, config())); |
| std::string content; |
| EXPECT_TRUE(base::ReadFileToString(dest, &content)); |
| // Note: ExpandPropertyFileForTesting() adds a trailing LF. |
| EXPECT_EQ("ro.foo=v2\nro.baz=v2\n", content); |
| } |
| |
| // Test that ExpandPropertyFile handles the case where a property is not found. |
| TEST_F(ArcPropertyUtilTest, ExpandPropertyFile_CannotExpand) { |
| constexpr const char kValidProp[] = |
| "ro.foo={nonexistent-property}\nro.baz=boo\n"; |
| base::FilePath path; |
| ASSERT_TRUE(CreateTemporaryFileInDir(GetTempDir(), &path)); |
| base::WriteFile(path, kValidProp, strlen(kValidProp)); |
| const base::FilePath dest = GetTempDir().Append("new.prop"); |
| EXPECT_FALSE(ExpandPropertyFileForTesting(path, dest, config())); |
| } |
| |
| // Test that ExpandPropertyFile handles the case where the input file is not |
| // found. |
| TEST_F(ArcPropertyUtilTest, ExpandPropertyFile_NoSourceFile) { |
| EXPECT_FALSE(ExpandPropertyFileForTesting(base::FilePath("/nonexistent"), |
| base::FilePath("/nonexistent2"), |
| config())); |
| } |
| |
| // Test that ExpandPropertyFile handles the case where the output file cannot |
| // be written. |
| TEST_F(ArcPropertyUtilTest, ExpandPropertyFile_CannotWrite) { |
| constexpr const char kValidProp[] = "ro.foo=bar\nro.baz=boo\n"; |
| base::FilePath path; |
| ASSERT_TRUE(CreateTemporaryFileInDir(GetTempDir(), &path)); |
| base::WriteFile(path, kValidProp, strlen(kValidProp)); |
| EXPECT_FALSE(ExpandPropertyFileForTesting( |
| path, base::FilePath("/nonexistent2"), config())); |
| } |
| |
| TEST_F(ArcPropertyUtilTest, ExpandPropertyFiles) { |
| // Both source and dest are not found. |
| EXPECT_FALSE(ExpandPropertyFiles(base::FilePath("/nonexistent1"), |
| base::FilePath("/nonexistent2"), |
| /*single_file=*/false)); |
| |
| // Both source and dest exist, but the source directory is empty. |
| base::FilePath source_dir; |
| ASSERT_TRUE(base::CreateTemporaryDirInDir(GetTempDir(), "test", &source_dir)); |
| base::FilePath dest_dir; |
| ASSERT_TRUE(base::CreateTemporaryDirInDir(GetTempDir(), "test", &dest_dir)); |
| EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_dir, false)); |
| |
| // Add default.prop to the source, but not build.prop. |
| base::FilePath default_prop = source_dir.Append("default.prop"); |
| constexpr const char kDefaultProp[] = "ro.foo=bar\n"; |
| base::WriteFile(default_prop, kDefaultProp, strlen(kDefaultProp)); |
| EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_dir, false)); |
| |
| // Add build.prop too. The call should not succeed still. |
| base::FilePath build_prop = source_dir.Append("build.prop"); |
| constexpr const char kBuildProp[] = "ro.baz=boo\n"; |
| base::WriteFile(build_prop, kBuildProp, strlen(kBuildProp)); |
| EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_dir, false)); |
| |
| // Add vendor_build.prop too. Then the call should succeed. |
| base::FilePath vendor_build_prop = source_dir.Append("vendor_build.prop"); |
| constexpr const char kVendorBuildProp[] = "ro.a=b\n"; |
| base::WriteFile(vendor_build_prop, kVendorBuildProp, |
| strlen(kVendorBuildProp)); |
| EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_dir, false)); |
| |
| // Verify all dest files are there. |
| EXPECT_TRUE(base::PathExists(dest_dir.Append("default.prop"))); |
| EXPECT_TRUE(base::PathExists(dest_dir.Append("build.prop"))); |
| EXPECT_TRUE(base::PathExists(dest_dir.Append("vendor_build.prop"))); |
| |
| // Verify their content. |
| // Note: ExpandPropertyFileForTesting() adds a trailing LF. |
| std::string content; |
| EXPECT_TRUE( |
| base::ReadFileToString(dest_dir.Append("default.prop"), &content)); |
| EXPECT_EQ(std::string(kDefaultProp) + "\n", content); |
| EXPECT_TRUE(base::ReadFileToString(dest_dir.Append("build.prop"), &content)); |
| EXPECT_EQ(std::string(kBuildProp) + "\n", content); |
| EXPECT_TRUE( |
| base::ReadFileToString(dest_dir.Append("vendor_build.prop"), &content)); |
| EXPECT_EQ(std::string(kVendorBuildProp) + "\n", content); |
| |
| // Expand it again, verify the previous result is cleared. |
| EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_dir, false)); |
| EXPECT_TRUE( |
| base::ReadFileToString(dest_dir.Append("default.prop"), &content)); |
| EXPECT_EQ(std::string(kDefaultProp) + "\n", content); |
| |
| // If default.prop does not exist in the source path, it should still process |
| // the other files, while also ensuring that default.prop is removed from the |
| // destination path. |
| base::DeleteFile(dest_dir.Append("default.prop"), /*recursive=*/false); |
| |
| EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_dir, false)); |
| |
| EXPECT_TRUE(base::ReadFileToString(dest_dir.Append("build.prop"), &content)); |
| EXPECT_EQ(std::string(kBuildProp) + "\n", content); |
| EXPECT_TRUE( |
| base::ReadFileToString(dest_dir.Append("vendor_build.prop"), &content)); |
| EXPECT_EQ(std::string(kVendorBuildProp) + "\n", content); |
| |
| // Finally, test the case where source is valid but the dest is not. |
| EXPECT_FALSE( |
| ExpandPropertyFiles(source_dir, base::FilePath("/nonexistent"), false)); |
| } |
| |
| // Do the same as the previous test, but with |single_file| == true. |
| TEST_F(ArcPropertyUtilTest, ExpandPropertyFiles_SingleFile) { |
| // Both source and dest are not found. |
| EXPECT_FALSE(ExpandPropertyFiles(base::FilePath("/nonexistent1"), |
| base::FilePath("/nonexistent2"), |
| /*single_file=*/true)); |
| |
| // Both source and dest exist, but the source directory is empty. |
| base::FilePath source_dir; |
| ASSERT_TRUE(base::CreateTemporaryDirInDir(GetTempDir(), "test", &source_dir)); |
| base::FilePath dest_prop_file; |
| ASSERT_TRUE( |
| base::CreateTemporaryDirInDir(GetTempDir(), "test", &dest_prop_file)); |
| dest_prop_file = dest_prop_file.Append("combined.prop"); |
| EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_prop_file, true)); |
| |
| // Add default.prop to the source, but not build.prop. |
| base::FilePath default_prop = source_dir.Append("default.prop"); |
| constexpr const char kDefaultProp[] = "ro.foo=bar\n"; |
| base::WriteFile(default_prop, kDefaultProp, strlen(kDefaultProp)); |
| EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_prop_file, true)); |
| |
| // Add build.prop too. The call should not succeed still. |
| base::FilePath build_prop = source_dir.Append("build.prop"); |
| constexpr const char kBuildProp[] = "ro.baz=boo\n"; |
| base::WriteFile(build_prop, kBuildProp, strlen(kBuildProp)); |
| EXPECT_FALSE(ExpandPropertyFiles(source_dir, dest_prop_file, true)); |
| |
| // Add vendor_build.prop too. Then the call should succeed. |
| base::FilePath vendor_build_prop = source_dir.Append("vendor_build.prop"); |
| constexpr const char kVendorBuildProp[] = "ro.a=b\n"; |
| base::WriteFile(vendor_build_prop, kVendorBuildProp, |
| strlen(kVendorBuildProp)); |
| EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_prop_file, true)); |
| |
| // Verify only one dest file exists. |
| EXPECT_FALSE( |
| base::PathExists(dest_prop_file.DirName().Append("default.prop"))); |
| EXPECT_FALSE(base::PathExists(dest_prop_file.DirName().Append("build.prop"))); |
| EXPECT_FALSE( |
| base::PathExists(dest_prop_file.DirName().Append("vendor_build.prop"))); |
| EXPECT_TRUE(base::PathExists(dest_prop_file)); |
| |
| // Verify the content. |
| // Note: ExpandPropertyFileForTesting() adds a trailing LF. |
| std::string content; |
| EXPECT_TRUE(base::ReadFileToString(dest_prop_file, &content)); |
| EXPECT_EQ(base::StringPrintf("%s\n%s\n%s\n", kDefaultProp, kBuildProp, |
| kVendorBuildProp), |
| content); |
| |
| // Expand it again, verify the previous result is cleared. |
| EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_prop_file, true)); |
| EXPECT_TRUE(base::ReadFileToString(dest_prop_file, &content)); |
| EXPECT_EQ(base::StringPrintf("%s\n%s\n%s\n", kDefaultProp, kBuildProp, |
| kVendorBuildProp), |
| content); |
| |
| // If default.prop does not exist in the source path, it should still process |
| // the other files. |
| base::DeleteFile(source_dir.Append("default.prop"), |
| /*recursive=*/false); |
| EXPECT_TRUE(ExpandPropertyFiles(source_dir, dest_prop_file, true)); |
| EXPECT_TRUE(base::ReadFileToString(dest_prop_file, &content)); |
| EXPECT_EQ(base::StringPrintf("%s\n%s\n", kBuildProp, kVendorBuildProp), |
| content); |
| |
| // Finally, test the case where source is valid but the dest is not. |
| EXPECT_FALSE( |
| ExpandPropertyFiles(source_dir, base::FilePath("/nonexistent"), true)); |
| } |
| |
| } // namespace |
| } // namespace arc |