blob: 6790005827a31aa564555845edd22a80af4d279f [file] [log] [blame]
// 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