| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/component_updater/android/component_loader_policy.h" |
| |
| #include <fcntl.h> |
| #include <jni.h> |
| #include <stdint.h> |
| |
| #include <iterator> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/android/jni_android.h" |
| #include "base/android/jni_array.h" |
| #include "base/android/jni_string.h" |
| #include "base/android/scoped_java_ref.h" |
| #include "base/containers/flat_map.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_file.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/hash/hash.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/task_environment.h" |
| #include "base/values.h" |
| #include "base/version.h" |
| #include "components/component_updater/android/components_info_holder.h" |
| #include "components/component_updater/component_updater_service.h" |
| #include "components/crash/core/common/crash_key.h" |
| #include "components/metrics/component_metrics_provider.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace component_updater { |
| |
| namespace { |
| |
| constexpr char kComponentId[] = "llkgjffcdpffmhiakmfcdcblohccpfmo"; |
| // This hash corresponds to kComponentId. |
| constexpr uint8_t kSha256Hash[] = { |
| 0xbb, 0xa6, 0x95, 0x52, 0x3f, 0x55, 0xc7, 0x80, 0xac, 0x52, 0x32, |
| 0x1b, 0xe7, 0x22, 0xf5, 0xce, 0x6a, 0xfd, 0x9c, 0x9e, 0xa9, 0x2a, |
| 0x0b, 0x50, 0x60, 0x2b, 0x7f, 0x6c, 0x64, 0x80, 0x09, 0x04}; |
| |
| constexpr char kMockComponentHistogramName[] = |
| "ComponentUpdater.AndroidComponentLoader.LoadStatus.MockComponent"; |
| |
| constexpr char kCohortId[] = "1:1vi9"; |
| |
| void GetPkHash(std::vector<uint8_t>* hash) { |
| hash->assign(std::begin(kSha256Hash), std::end(kSha256Hash)); |
| } |
| |
| std::vector<int> OpenFileFds(const base::FilePath& base, |
| const std::vector<std::string>& files) { |
| std::vector<int> fds; |
| for (const std::string& file : files) { |
| base::FilePath path = base.AppendASCII(file); |
| fds.push_back(open(path.MaybeAsASCII().c_str(), O_RDONLY)); |
| } |
| return fds; |
| } |
| |
| using OnLoadedTestCallBack = |
| base::OnceCallback<void(const base::Version&, |
| base::flat_map<std::string, base::ScopedFD>&, |
| base::Value::Dict)>; |
| using OnFailedTestCallBack = base::OnceCallback<void(ComponentLoadResult)>; |
| |
| class MockLoaderPolicy : public ComponentLoaderPolicy { |
| public: |
| explicit MockLoaderPolicy(OnLoadedTestCallBack on_loaded, |
| OnFailedTestCallBack on_failed) |
| : on_loaded_(std::move(on_loaded)), on_failed_(std::move(on_failed)) {} |
| |
| MockLoaderPolicy() |
| : on_loaded_(base::DoNothing()), on_failed_(base::DoNothing()) {} |
| |
| MockLoaderPolicy(const MockLoaderPolicy&) = delete; |
| MockLoaderPolicy& operator=(const MockLoaderPolicy&) = delete; |
| |
| void ComponentLoaded(const base::Version& version, |
| base::flat_map<std::string, base::ScopedFD>& fd_map, |
| base::Value::Dict manifest) override { |
| std::move(on_loaded_).Run(version, fd_map, std::move(manifest)); |
| } |
| |
| void ComponentLoadFailed(ComponentLoadResult error) override { |
| std::move(on_failed_).Run(error); |
| } |
| |
| void GetHash(std::vector<uint8_t>* hash) const override { GetPkHash(hash); } |
| |
| std::string GetMetricsSuffix() const override { return "MockComponent"; } |
| |
| private: |
| OnLoadedTestCallBack on_loaded_; |
| OnFailedTestCallBack on_failed_; |
| }; |
| |
| void VerifyComponentLoaded(base::OnceClosure on_done, |
| const base::Version& version, |
| base::flat_map<std::string, base::ScopedFD>& fd_map, |
| base::Value::Dict manifest) { |
| EXPECT_EQ(version.GetString(), "123.456.789"); |
| EXPECT_EQ(fd_map.size(), 2u); |
| EXPECT_NE(fd_map.find("file1.txt"), fd_map.end()); |
| EXPECT_NE(fd_map.find("file2.txt"), fd_map.end()); |
| |
| std::move(on_done).Run(); |
| } |
| |
| } // namespace |
| |
| class AndroidComponentLoaderPolicyTest : public testing::Test { |
| public: |
| AndroidComponentLoaderPolicyTest() { |
| crash_reporter::ResetCrashKeysForTesting(); |
| crash_reporter::InitializeCrashKeysForTesting(); |
| } |
| ~AndroidComponentLoaderPolicyTest() override { |
| crash_reporter::ResetCrashKeysForTesting(); |
| } |
| |
| AndroidComponentLoaderPolicyTest(const AndroidComponentLoaderPolicyTest&) = |
| delete; |
| AndroidComponentLoaderPolicyTest& operator=( |
| const AndroidComponentLoaderPolicyTest&) = delete; |
| |
| void SetUp() override { |
| env_ = base::android::AttachCurrentThread(); |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| } |
| |
| protected: |
| void WriteFile(const std::string& file_name, const std::string& content) { |
| ASSERT_TRUE( |
| base::WriteFile(temp_dir_.GetPath().AppendASCII(file_name), content)); |
| files_.push_back(file_name); |
| } |
| |
| std::vector<int> GetFileFds() const { |
| return OpenFileFds(temp_dir_.GetPath(), files_); |
| } |
| |
| raw_ptr<JNIEnv> env_ = nullptr; |
| std::vector<std::string> files_; |
| base::HistogramTester histogram_tester_; |
| |
| private: |
| base::ScopedTempDir temp_dir_; |
| }; |
| |
| TEST_F(AndroidComponentLoaderPolicyTest, TestValidManifest) { |
| base::test::TaskEnvironment task_environment; |
| |
| WriteFile("file1.txt", "1"); |
| WriteFile("file2.txt", "2"); |
| WriteFile(kManifestFileName, |
| "{\n\"manifest_version\": 2,\n\"version\": \"123.456.789\"\n}"); |
| WriteFile(kMetadataFileName, |
| "{\"cohortId\":\"" + std::string(kCohortId) + "\"}"); |
| |
| base::RunLoop run_loop; |
| auto* android_policy = |
| new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>( |
| base::BindOnce(&VerifyComponentLoaded, run_loop.QuitClosure()), |
| base::BindOnce([](ComponentLoadResult) { FAIL(); }))); |
| |
| android_policy->ComponentLoaded( |
| env_, base::android::ToJavaArrayOfStrings(env_, files_), |
| base::android::ToJavaIntArray(env_, GetFileFds())); |
| run_loop.Run(); |
| |
| histogram_tester_.ExpectBucketCount(kMockComponentHistogramName, |
| ComponentLoadResult::kComponentLoaded, 1); |
| histogram_tester_.ExpectTotalCount(kMockComponentHistogramName, 1); |
| EXPECT_EQ("ORIGIN_TRIALS-123.456.789", |
| crash_reporter::GetCrashKeyValue(kComponentsCrashKeyName)); |
| EXPECT_EQ("ORIGIN_TRIALS-" + |
| base::NumberToString( |
| metrics::ComponentMetricsProvider::HashCohortId(kCohortId)), |
| crash_reporter::GetCrashKeyValue(kCohortHashCrashKeyName)); |
| |
| std::vector<ComponentInfo> components = |
| ComponentsInfoHolder::GetInstance()->GetComponents(); |
| EXPECT_EQ(components.size(), 1u); |
| EXPECT_EQ(components[0].id, kComponentId); |
| EXPECT_EQ(components[0].cohort_id, kCohortId); |
| } |
| |
| TEST_F(AndroidComponentLoaderPolicyTest, TestMissingManifest) { |
| base::test::TaskEnvironment task_environment; |
| |
| WriteFile("file.txt", "test"); |
| |
| base::RunLoop run_loop; |
| auto* android_policy = |
| new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>( |
| base::BindOnce([](const base::Version& version, |
| base::flat_map<std::string, base::ScopedFD>& fd_map, |
| base::Value::Dict manifest) { FAIL(); }), |
| base::BindLambdaForTesting([&](ComponentLoadResult error) { |
| ASSERT_EQ(error, ComponentLoadResult::kMissingManifest); |
| run_loop.Quit(); |
| }))); |
| |
| android_policy->ComponentLoaded( |
| env_, base::android::ToJavaArrayOfStrings(env_, files_), |
| base::android::ToJavaIntArray(env_, GetFileFds())); |
| run_loop.Run(); |
| |
| histogram_tester_.ExpectBucketCount(kMockComponentHistogramName, |
| ComponentLoadResult::kMissingManifest, 1); |
| histogram_tester_.ExpectTotalCount(kMockComponentHistogramName, 1); |
| } |
| |
| TEST_F(AndroidComponentLoaderPolicyTest, TestInvalidVersion) { |
| base::test::TaskEnvironment task_environment; |
| |
| WriteFile("file.txt", "test"); |
| WriteFile(kManifestFileName, |
| "{\n\"manifest_version\": 2,\n\"version\": \"\"\n}"); |
| |
| base::RunLoop run_loop; |
| auto* android_policy = |
| new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>( |
| base::BindOnce([](const base::Version& version, |
| base::flat_map<std::string, base::ScopedFD>& fd_map, |
| base::Value::Dict manifest) { FAIL(); }), |
| base::BindLambdaForTesting([&](ComponentLoadResult error) { |
| ASSERT_EQ(error, ComponentLoadResult::kInvalidVersion); |
| run_loop.Quit(); |
| }))); |
| |
| android_policy->ComponentLoaded( |
| env_, base::android::ToJavaArrayOfStrings(env_, files_), |
| base::android::ToJavaIntArray(env_, GetFileFds())); |
| run_loop.Run(); |
| |
| histogram_tester_.ExpectBucketCount(kMockComponentHistogramName, |
| ComponentLoadResult::kInvalidVersion, 1); |
| histogram_tester_.ExpectTotalCount(kMockComponentHistogramName, 1); |
| } |
| |
| TEST_F(AndroidComponentLoaderPolicyTest, TestInvalidManifest) { |
| base::test::TaskEnvironment task_environment; |
| |
| WriteFile("file.txt", "test"); |
| WriteFile(kManifestFileName, "{\n\"manifest_version\":}"); |
| |
| base::RunLoop run_loop; |
| auto* android_policy = |
| new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>( |
| base::BindOnce([](const base::Version& version, |
| base::flat_map<std::string, base::ScopedFD>& fd_map, |
| base::Value::Dict manifest) { FAIL(); }), |
| base::BindLambdaForTesting([&](ComponentLoadResult error) { |
| ASSERT_EQ(error, ComponentLoadResult::kMalformedManifest); |
| run_loop.Quit(); |
| }))); |
| |
| android_policy->ComponentLoaded( |
| env_, base::android::ToJavaArrayOfStrings(env_, files_), |
| base::android::ToJavaIntArray(env_, GetFileFds())); |
| run_loop.Run(); |
| |
| histogram_tester_.ExpectBucketCount( |
| kMockComponentHistogramName, ComponentLoadResult::kMalformedManifest, 1); |
| histogram_tester_.ExpectTotalCount(kMockComponentHistogramName, 1); |
| } |
| |
| TEST_F(AndroidComponentLoaderPolicyTest, TestGetComponentId) { |
| base::test::TaskEnvironment task_environment; |
| |
| auto* android_policy = |
| new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>()); |
| |
| base::android::ScopedJavaLocalRef<jstring> jcomponentId = |
| android_policy->GetComponentId(env_); |
| |
| EXPECT_EQ(base::android::ConvertJavaStringToUTF8(jcomponentId), kComponentId); |
| } |
| |
| } // namespace component_updater |