| // Copyright 2020 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "components/update_client/cobalt_slot_management.h" |
| |
| #include <sys/stat.h> |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "base/strings/string_util.h" |
| #include "starboard/common/file.h" |
| #include "starboard/common/time.h" |
| #include "starboard/extension/free_space.h" |
| #include "starboard/loader_app/app_key_files.h" |
| #include "starboard/loader_app/drain_file.h" |
| #include "starboard/loader_app/drain_file_helper.h" |
| #include "starboard/loader_app/installation_manager.h" |
| #include "starboard/loader_app/system_get_extension_shim.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace update_client { |
| namespace { |
| |
| constexpr char kTestAppKey1[] = "test_key1"; |
| constexpr char kTestAppKey2[] = "test_key2"; |
| constexpr char kManifestV1[] = R"json({ |
| "manifest_version": 2, |
| "name": "Cobalt", |
| "description": "Cobalt", |
| "version": "1.1.0" |
| })json"; |
| |
| constexpr char kManifestV2[] = R"json( |
| { |
| "manifest_version": 2, |
| "name": "Cobalt", |
| "description": "Cobalt", |
| "version": "1.2.0" |
| })json"; |
| |
| class CobaltSlotManagementTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| std::vector<char> buf(kSbFileMaxPath); |
| storage_path_implemented_ = SbSystemGetPath(kSbSystemPathStorageDirectory, |
| buf.data(), kSbFileMaxPath); |
| if (!storage_path_implemented_) { |
| return; |
| } |
| |
| storage_path_ = buf.data(); |
| ASSERT_TRUE(!storage_path_.empty()); |
| |
| starboard::SbFileDeleteRecursive(storage_path_.c_str(), true); |
| ImInitialize(3, kTestAppKey1); |
| api_ = static_cast<const CobaltExtensionInstallationManagerApi*>( |
| starboard::loader_app::SbSystemGetExtensionShim( |
| kCobaltExtensionInstallationManagerName)); |
| } |
| |
| void TearDown() override { |
| starboard::SbFileDeleteRecursive(storage_path_.c_str(), true); |
| ImUninitialize(); |
| } |
| |
| void CreateManifest(const char* slot_path, |
| const char* data, |
| size_t data_length) { |
| std::string manifest1_path = storage_path_; |
| manifest1_path += kSbFileSepString; |
| manifest1_path += slot_path; |
| manifest1_path += kSbFileSepString; |
| manifest1_path += "manifest.json"; |
| |
| ASSERT_TRUE(SbFileAtomicReplace(manifest1_path.c_str(), data, data_length)); |
| } |
| |
| const CobaltExtensionInstallationManagerApi* api_; |
| bool storage_path_implemented_; |
| std::string storage_path_; |
| }; |
| |
| TEST_F(CobaltSlotManagementTest, Init) { |
| if (!storage_path_implemented_) { |
| return; |
| } |
| CobaltSlotManagement cobalt_slot_management; |
| ASSERT_TRUE(cobalt_slot_management.Init(api_)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, NegativeInit) { |
| if (!storage_path_implemented_) { |
| return; |
| } |
| CobaltSlotManagement cobalt_slot_management; |
| ASSERT_FALSE(cobalt_slot_management.Init(nullptr)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, SelectEmptySlot) { |
| if (!storage_path_implemented_) { |
| return; |
| } |
| CobaltSlotManagement cobalt_slot_management; |
| ASSERT_TRUE(cobalt_slot_management.Init(api_)); |
| base::FilePath dir; |
| ASSERT_TRUE(cobalt_slot_management.SelectSlot(&dir)); |
| ASSERT_TRUE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1)); |
| ASSERT_TRUE(base::EndsWith(dir.value(), "installation_1", |
| base::CompareCase::SENSITIVE)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, SelectSlotBailsIfOtherAppIsDraining) { |
| if (!storage_path_implemented_) { |
| return; |
| } |
| |
| CobaltSlotManagement cobalt_slot_management; |
| std::string slot_path = storage_path_; |
| slot_path += kSbFileSepString; |
| slot_path += "installation_1"; |
| |
| // If there is is non-expired drain file from |
| // different app the current app should bail out. |
| ASSERT_TRUE(DrainFileTryDrain(slot_path.c_str(), kTestAppKey2)); |
| ASSERT_TRUE(cobalt_slot_management.Init(api_)); |
| |
| base::FilePath dir; |
| ASSERT_FALSE(cobalt_slot_management.SelectSlot(&dir)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, SelectMinVersionSlot) { |
| if (!storage_path_implemented_) { |
| return; |
| } |
| |
| // In slot 2 create manifest v1. |
| CreateManifest("installation_2", kManifestV1, strlen(kManifestV1)); |
| |
| // In slot 1 create manifest v2. |
| CreateManifest("installation_1", kManifestV2, strlen(kManifestV2)); |
| |
| CobaltSlotManagement cobalt_slot_management; |
| ASSERT_TRUE(cobalt_slot_management.Init(api_)); |
| base::FilePath dir; |
| cobalt_slot_management.SelectSlot(&dir); |
| ASSERT_TRUE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1)); |
| LOG(INFO) << "dir=" << dir; |
| |
| ASSERT_TRUE(base::EndsWith(dir.value(), "installation_2", |
| base::CompareCase::SENSITIVE)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, ConfirmSlot) { |
| if (!storage_path_implemented_) { |
| return; |
| } |
| |
| ImRollForward(1); |
| ImDecrementInstallationNumTries(1); |
| ASSERT_LE(ImGetInstallationNumTriesLeft(1), IM_MAX_NUM_TRIES); |
| ImMarkInstallationSuccessful(1); |
| ASSERT_EQ(IM_INSTALLATION_STATUS_SUCCESS, ImGetInstallationStatus(1)); |
| CobaltSlotManagement cobalt_slot_management; |
| ASSERT_TRUE(cobalt_slot_management.Init(api_)); |
| base::FilePath dir; |
| ASSERT_TRUE(cobalt_slot_management.SelectSlot(&dir)); |
| LOG(INFO) << "dir=" << dir; |
| |
| ASSERT_TRUE(base::EndsWith(dir.value(), "installation_1", |
| base::CompareCase::SENSITIVE)); |
| |
| ASSERT_TRUE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1)); |
| |
| ASSERT_TRUE(cobalt_slot_management.ConfirmSlot(dir)); |
| |
| ASSERT_EQ(IM_INSTALLATION_STATUS_NOT_SUCCESS, ImGetInstallationStatus(1)); |
| ASSERT_EQ(IM_MAX_NUM_TRIES, ImGetInstallationNumTriesLeft(1)); |
| } |
| |
| // Tests the "racing updaters" scenario. |
| TEST_F(CobaltSlotManagementTest, ConfirmSlotBailsIfOtherAppStartedDrainFirst) { |
| if (!storage_path_implemented_) { |
| return; |
| } |
| |
| CobaltSlotManagement cobalt_slot_management; |
| std::string slot_path = storage_path_; |
| slot_path += kSbFileSepString; |
| slot_path += "installation_1"; |
| ASSERT_TRUE(cobalt_slot_management.Init(api_)); |
| base::FilePath dir; |
| ASSERT_TRUE(cobalt_slot_management.SelectSlot(&dir)); |
| |
| // In order to be higher ranked, the other app's drain file needs to be older |
| // but not expired. |
| int64_t current_time_us = |
| starboard::PosixTimeToWindowsTime(starboard::CurrentPosixTime()); |
| starboard::loader_app::ScopedDrainFile racing_drain_file( |
| slot_path, kTestAppKey2, |
| current_time_us - (kDrainFileMaximumAgeUsec / 2)); |
| |
| ASSERT_FALSE(cobalt_slot_management.ConfirmSlot(dir)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, CleanupAllDrainFiles) { |
| if (!storage_path_implemented_) { |
| return; |
| } |
| CobaltSlotManagement cobalt_slot_management; |
| ASSERT_TRUE(cobalt_slot_management.Init(api_)); |
| base::FilePath dir; |
| ASSERT_TRUE(cobalt_slot_management.SelectSlot(&dir)); |
| ASSERT_TRUE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1)); |
| cobalt_slot_management.CleanupAllDrainFiles(); |
| ASSERT_FALSE(DrainFileIsAppDraining(dir.value().c_str(), kTestAppKey1)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, CobaltFinishInstallation) { |
| std::string slot_path = storage_path_; |
| slot_path += kSbFileSepString; |
| slot_path += "installation_1"; |
| std::string good_file_path = |
| starboard::loader_app::GetGoodAppKeyFilePath(slot_path, kTestAppKey1); |
| |
| ImRollForward(2); |
| |
| // Cleanup pending requests for roll forward. |
| ASSERT_EQ(IM_SUCCESS, ImRollForwardIfNeeded()); |
| |
| ASSERT_EQ(2, ImGetCurrentInstallationIndex()); |
| struct stat file_info; |
| ASSERT_FALSE(stat(good_file_path.c_str(), &file_info) == 0); |
| ASSERT_TRUE(CobaltFinishInstallation(api_, 1, slot_path, kTestAppKey1)); |
| ASSERT_TRUE(stat(good_file_path.c_str(), &file_info) == 0); |
| ASSERT_EQ(IM_SUCCESS, ImRollForwardIfNeeded()); |
| ASSERT_EQ(1, ImGetCurrentInstallationIndex()); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, GoodCobaltQuickUpdate) { |
| // In slot 1 create manifest v1. |
| CreateManifest("installation_1", kManifestV1, strlen(kManifestV1)); |
| |
| // In slot 2 create manifest v2. |
| CreateManifest("installation_2", kManifestV2, strlen(kManifestV2)); |
| |
| // Mark slot 2 good for app 2. |
| std::string slot_path = storage_path_; |
| slot_path += kSbFileSepString; |
| slot_path += "installation_2"; |
| std::string good_file_path = |
| starboard::loader_app::GetGoodAppKeyFilePath(slot_path, kTestAppKey2); |
| starboard::loader_app::CreateAppKeyFile(good_file_path); |
| base::Version version("1.1.0"); |
| ASSERT_TRUE(CobaltQuickUpdate(api_, version)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, NegativeCobaltQuickUpdateBadVersion) { |
| base::Version version; |
| ASSERT_FALSE(CobaltQuickUpdate(api_, version)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, NegativeCobaltQuickUpdate) { |
| base::Version version("1.0.0"); |
| ASSERT_FALSE(CobaltQuickUpdate(api_, version)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateNoInstallationMoreSpace) { |
| ASSERT_FALSE( |
| CobaltSkipUpdate(api_, 1024 /* min */, 1025 /* free */, 0 /* cleanup */)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateNoInstallationExactSpace) { |
| ASSERT_FALSE( |
| CobaltSkipUpdate(api_, 1024 /* min */, 1024 /* free */, 0 /* cleanup */)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateNoInstallationNotEnoughSpace) { |
| ASSERT_TRUE( |
| CobaltSkipUpdate(api_, 1024 /* min */, 1023 /* free */, 0 /* cleanup */)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateWithInstallationMoreSpace) { |
| ASSERT_FALSE( |
| CobaltSkipUpdate(api_, 1024 /* min */, 1024 /* free */, 1 /* cleanup */)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateWithInstallationExactSpace) { |
| ASSERT_FALSE( |
| CobaltSkipUpdate(api_, 1024 /* min */, 1023 /* free */, 1 /* cleanup */)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, |
| CobaltSkipUpdateWithInstallationNotEnoughSpace) { |
| ASSERT_TRUE( |
| CobaltSkipUpdate(api_, 1024 /* min */, 1022 /* free */, 1 /* cleanup */)); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, CobaltInstallationCleanupSizeNoInstallation) { |
| uint64_t size = CobaltInstallationCleanupSize(api_); |
| ASSERT_EQ(size, 0); |
| } |
| |
| TEST_F(CobaltSlotManagementTest, |
| CobaltInstallationCleanupSizeTwoInstallations) { |
| int len1 = strlen(kManifestV1); |
| int len2 = strlen(kManifestV2); |
| ASSERT_NE(len1, len2); |
| |
| CreateManifest("installation_1", kManifestV2, len1); |
| CreateManifest("installation_2", kManifestV1, len2); |
| |
| uint64_t size = CobaltInstallationCleanupSize(api_); |
| ASSERT_EQ(size, std::min(len1, len2)); |
| } |
| } // namespace |
| |
| } // namespace update_client |