blob: 429376898178df27cfa4f5c3b0e3c2133f35d819 [file] [log] [blame]
// Copyright 2022 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 "cobalt/updater/utils.h"
#include <vector>
#include "base/files/file_path.h"
#include "base/strings/strcat.h"
#include "base/values.h"
#include "gmock/gmock.h"
#include "starboard/common/file.h"
#include "starboard/directory.h"
#include "starboard/extension/installation_manager.h"
#include "starboard/file.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cobalt {
namespace updater {
namespace {
const char kEvergreenManifestFilename[] = "manifest.json";
const char kEvergreenLibDirname[] = "lib";
// Based on the CobaltExtensionInstallationManagerApi struct typedef.
class MockInstallationManagerApi {
public:
MOCK_METHOD0(GetCurrentInstallationIndex, int());
MOCK_METHOD3(GetInstallationPath,
int(int installation_index, char* path, int path_length));
};
MockInstallationManagerApi* GetMockInstallationManagerApi() {
static auto* const installation_manager_api = []() {
auto* inner_installation_manager_api = new MockInstallationManagerApi;
// Because the mocked methods are non-state-changing, this mock is really
// just used as a stub. It's therefore ok for this mock object to be leaked
// and not verified.
testing::Mock::AllowLeak(inner_installation_manager_api);
return inner_installation_manager_api;
}();
return installation_manager_api;
}
// Stub definitions that delegate to the mock installation manager API.
int StubGetCurrentInstallationIndex() {
return GetMockInstallationManagerApi()->GetCurrentInstallationIndex();
}
int StubGetInstallationPath(int installation_index, char* path,
int path_length) {
return GetMockInstallationManagerApi()->GetInstallationPath(
installation_index, path, path_length);
}
// No-op stub definitions for functions that aren't exercised.
int StubMarkInstallationSuccessful(int installation_index) {
return IM_EXT_SUCCESS;
}
int StubRequestRollForwardToInstallation(int installation_index) {
return IM_EXT_SUCCESS;
}
int StubSelectNewInstallationIndex() { return IM_EXT_SUCCESS; }
int StubGetAppKey(char* app_key, int app_key_length) { return IM_EXT_SUCCESS; }
int StubGetMaxNumberInstallations() { return IM_EXT_SUCCESS; }
int StubResetInstallation(int installation_index) { return IM_EXT_SUCCESS; }
int StubReset() { return IM_EXT_SUCCESS; }
const CobaltExtensionInstallationManagerApi kStubInstallationManagerApi = {
kCobaltExtensionInstallationManagerName,
1,
&StubGetCurrentInstallationIndex,
&StubMarkInstallationSuccessful,
&StubRequestRollForwardToInstallation,
&StubGetInstallationPath,
&StubSelectNewInstallationIndex,
&StubGetAppKey,
&StubGetMaxNumberInstallations,
&StubResetInstallation,
&StubReset,
};
bool EnsureDirectoryExists(const char* path){
return mkdir(path, 0700) == 0 || SbDirectoryCanOpen(path);
}
class UtilsTest : public testing::Test {
protected:
void SetUp() override {
temp_dir_path_.resize(kSbFileMaxPath);
ASSERT_TRUE(SbSystemGetPath(kSbSystemPathTempDirectory,
temp_dir_path_.data(), temp_dir_path_.size()));
}
void TearDown() override {
ASSERT_TRUE(starboard::SbFileDeleteRecursive(temp_dir_path_.data(), true));
}
void CreateManifest(const char* content, const std::string& directory) {
std::string manifest_path =
base::StrCat({directory, kSbFileSepString, kEvergreenManifestFilename});
SbFile sb_file =
SbFileOpen(manifest_path.c_str(), kSbFileOpenAlways | kSbFileRead,
nullptr, nullptr);
ASSERT_TRUE(SbFileIsValid(sb_file));
ASSERT_TRUE(SbFileClose(sb_file));
ASSERT_TRUE(
SbFileAtomicReplace(manifest_path.c_str(), content, strlen(content)));
}
void DeleteManifest(const std::string& directory) {
std::string manifest_path =
base::StrCat({directory, kSbFileSepString, kEvergreenManifestFilename});
ASSERT_TRUE(SbFileDelete(manifest_path.c_str()));
}
void CreateEmptyLibrary(const std::string& name,
const std::string& installation_path) {
std::string lib_path = base::StrCat(
{installation_path, kSbFileSepString, kEvergreenLibDirname});
ASSERT_TRUE(EnsureDirectoryExists(lib_path.c_str()));
lib_path = base::StrCat({lib_path, kSbFileSepString, name});
SbFile sb_file = SbFileOpen(
lib_path.c_str(), kSbFileOpenAlways | kSbFileRead, nullptr, nullptr);
ASSERT_TRUE(SbFileIsValid(sb_file));
ASSERT_TRUE(SbFileClose(sb_file));
}
void DeleteLibraryDirRecursively(const std::string& installation_path) {
std::string lib_path = base::StrCat(
{installation_path, kSbFileSepString, kEvergreenLibDirname});
ASSERT_TRUE(starboard::SbFileDeleteRecursive(lib_path.c_str(), false));
}
std::vector<char> temp_dir_path_;
};
TEST_F(UtilsTest, ReadEvergreenVersionReturnsVersionForValidManifest) {
std::string installation_path = base::StrCat(
{temp_dir_path_.data(), kSbFileSepString, "some_installation_path"});
ASSERT_TRUE(EnsureDirectoryExists(installation_path.c_str()));
char manifest_content[] = R"json(
{
"manifest_version": 2,
"name": "Cobalt",
"description": "Cobalt",
"version": "1.2.0"
})json";
CreateManifest(manifest_content, installation_path);
base::Version version =
ReadEvergreenVersion(base::FilePath(installation_path));
ASSERT_EQ(version.GetString(), "1.2.0");
DeleteManifest(installation_path);
}
TEST_F(UtilsTest,
ReadEvergreenVersionReturnsInvalidVersionForVersionlessManifest) {
std::string installation_path = base::StrCat(
{temp_dir_path_.data(), kSbFileSepString, "some_installation_path"});
ASSERT_TRUE(EnsureDirectoryExists(installation_path.c_str()));
char versionless_manifest_content[] = R"json(
{
"manifest_version": 2,
"name": "Cobalt",
"description": "Cobalt",
})json";
CreateManifest(versionless_manifest_content, installation_path);
base::Version version =
ReadEvergreenVersion(base::FilePath(installation_path));
ASSERT_FALSE(version.IsValid());
DeleteManifest(installation_path);
}
TEST_F(UtilsTest, ReadEvergreenVersionReturnsInvalidVersionForMissingManifest) {
base::Version version =
ReadEvergreenVersion(base::FilePath("nonexistent_manifest_path"));
ASSERT_FALSE(version.IsValid());
}
TEST_F(UtilsTest, ReturnsEvergreenVersionFromCurrentManagedInstallation) {
std::string installation_path = base::StrCat(
{temp_dir_path_.data(), kSbFileSepString, "some_installation_path"});
int installation_index = 1;
MockInstallationManagerApi* mock_installation_manager_api =
GetMockInstallationManagerApi();
EXPECT_CALL(*mock_installation_manager_api, GetCurrentInstallationIndex())
.Times(1)
.WillOnce(testing::Return(installation_index));
EXPECT_CALL(
*mock_installation_manager_api,
GetInstallationPath(installation_index, testing::_, kSbFileMaxPath))
.Times(1)
.WillOnce(
testing::DoAll(testing::SetArrayArgument<1>(installation_path.begin(),
installation_path.end()),
testing::Return(IM_EXT_SUCCESS)));
std::function<const void*(const char*)> stub_get_extension_fn =
[](const char* name) { return &kStubInstallationManagerApi; };
ASSERT_TRUE(EnsureDirectoryExists(installation_path.c_str()));
char manifest_content[] = R"json(
{
"manifest_version": 2,
"name": "Cobalt",
"description": "Cobalt",
"version": "1.2.0"
})json";
CreateManifest(manifest_content, installation_path);
std::string version = GetCurrentEvergreenVersion(stub_get_extension_fn);
ASSERT_EQ(version, "1.2.0");
DeleteManifest(installation_path);
}
TEST_F(UtilsTest,
ReturnsDefaultVersionWhenManifestMissingFromCurrentManagedInstallation) {
std::string installation_path = base::StrCat(
{temp_dir_path_.data(), kSbFileSepString, "some_installation_path"});
int installation_index = 1;
MockInstallationManagerApi* mock_installation_manager_api =
GetMockInstallationManagerApi();
EXPECT_CALL(*mock_installation_manager_api, GetCurrentInstallationIndex())
.Times(1)
.WillOnce(testing::Return(installation_index));
EXPECT_CALL(
*mock_installation_manager_api,
GetInstallationPath(installation_index, testing::_, kSbFileMaxPath))
.Times(1)
.WillOnce(
testing::DoAll(testing::SetArrayArgument<1>(installation_path.begin(),
installation_path.end()),
testing::Return(IM_EXT_SUCCESS)));
std::function<const void*(const char*)> stub_get_extension_fn =
[](const char* name) { return &kStubInstallationManagerApi; };
ASSERT_TRUE(EnsureDirectoryExists(installation_path.c_str()));
// No manifest is created in the installation directory.
std::string version = GetCurrentEvergreenVersion(stub_get_extension_fn);
ASSERT_EQ(version, kDefaultManifestVersion);
}
TEST_F(UtilsTest,
ReturnsVersionFromLoadedInstallationWhenErrorGettingInstallationPath) {
int installation_index = 1;
MockInstallationManagerApi* mock_installation_manager_api =
GetMockInstallationManagerApi();
EXPECT_CALL(*mock_installation_manager_api, GetCurrentInstallationIndex())
.Times(1)
.WillOnce(testing::Return(installation_index));
EXPECT_CALL(
*mock_installation_manager_api,
GetInstallationPath(installation_index, testing::_, kSbFileMaxPath))
.Times(1)
.WillOnce(testing::Return(IM_EXT_ERROR));
std::function<const void*(const char*)> stub_get_extension_fn =
[](const char* name) { return &kStubInstallationManagerApi; };
std::vector<char> system_path_content_dir(kSbFileMaxPath);
SbSystemGetPath(kSbSystemPathContentDirectory, system_path_content_dir.data(),
kSbFileMaxPath);
// Since the libupdater_test.so library has already been loaded,
// kSbSystemPathContentDirectory points to the content dir of the running
// library and the installation dir is therefore its parent.
std::string installation_path =
base::FilePath(std::string(system_path_content_dir.begin(),
system_path_content_dir.end()))
.DirName()
.value();
char manifest_content[] = R"json(
{
"manifest_version": 2,
"name": "Cobalt",
"description": "Cobalt",
"version": "1.2.0"
})json";
CreateManifest(manifest_content, installation_path);
std::string version = GetCurrentEvergreenVersion(stub_get_extension_fn);
ASSERT_EQ(version, "1.2.0");
DeleteManifest(installation_path);
}
TEST_F(UtilsTest,
ReturnsVersionFromLoadedInstallationWhenErrorGettingInstallationIndex) {
MockInstallationManagerApi* mock_installation_manager_api =
GetMockInstallationManagerApi();
EXPECT_CALL(*mock_installation_manager_api, GetCurrentInstallationIndex())
.Times(1)
.WillOnce(testing::Return(IM_EXT_ERROR));
std::function<const void*(const char*)> stub_get_extension_fn =
[](const char* name) { return &kStubInstallationManagerApi; };
std::vector<char> system_path_content_dir(kSbFileMaxPath);
SbSystemGetPath(kSbSystemPathContentDirectory, system_path_content_dir.data(),
kSbFileMaxPath);
// Since the libupdater_test.so library has already been loaded,
// kSbSystemPathContentDirectory points to the content dir of the running
// library and the installation dir is therefore its parent.
std::string installation_path =
base::FilePath(std::string(system_path_content_dir.begin(),
system_path_content_dir.end()))
.DirName()
.value();
char manifest_content[] = R"json(
{
"manifest_version": 2,
"name": "Cobalt",
"description": "Cobalt",
"version": "1.2.0"
})json";
CreateManifest(manifest_content, installation_path);
std::string version = GetCurrentEvergreenVersion(stub_get_extension_fn);
ASSERT_EQ(version, "1.2.0");
DeleteManifest(installation_path);
}
TEST_F(UtilsTest,
ReturnsVersionFromLoadedInstallationWhenErrorGettingIMExtension) {
std::function<const void*(const char*)> stub_get_extension_fn =
[](const char* name) { return nullptr; };
std::vector<char> system_path_content_dir(kSbFileMaxPath);
SbSystemGetPath(kSbSystemPathContentDirectory, system_path_content_dir.data(),
kSbFileMaxPath);
// Since the libupdater_test.so library has already been loaded,
// kSbSystemPathContentDirectory points to the content dir of the running
// library and the installation dir is therefore its parent.
std::string installation_path =
base::FilePath(std::string(system_path_content_dir.begin(),
system_path_content_dir.end()))
.DirName()
.value();
char manifest_content[] = R"json(
{
"manifest_version": 2,
"name": "Cobalt",
"description": "Cobalt",
"version": "1.2.0"
})json";
CreateManifest(manifest_content, installation_path);
std::string version = GetCurrentEvergreenVersion(stub_get_extension_fn);
ASSERT_EQ(version, "1.2.0");
DeleteManifest(installation_path);
}
TEST_F(UtilsTest,
ReturnsDefaultVersionWhenManifestMissingFromLoadedInstallation) {
// Two levels of resilience are actually tested...
// First, the loaded installation is used because an error is encountered
// while getting the Installation Manager extension. This is similar to
// previous test cases.
std::function<const void*(const char*)> stub_get_extension_fn =
[](const char* name) { return nullptr; };
// And second, kDefaultManifestVersion should be used because no manifest is
// found in the loaded installation.
std::string version = GetCurrentEvergreenVersion(stub_get_extension_fn);
ASSERT_EQ(version, kDefaultManifestVersion);
}
TEST_F(UtilsTest,
ReturnsEvergreenLibraryMetadataFromCurrentManagedInstallation) {
std::string installation_path = base::StrCat(
{temp_dir_path_.data(), kSbFileSepString, "some_installation_path"});
int installation_index = 1;
MockInstallationManagerApi* mock_installation_manager_api =
GetMockInstallationManagerApi();
EXPECT_CALL(*mock_installation_manager_api, GetCurrentInstallationIndex())
.Times(1)
.WillOnce(testing::Return(installation_index));
EXPECT_CALL(
*mock_installation_manager_api,
GetInstallationPath(installation_index, testing::_, kSbFileMaxPath))
.Times(1)
.WillOnce(
testing::DoAll(testing::SetArrayArgument<1>(installation_path.begin(),
installation_path.end()),
testing::Return(IM_EXT_SUCCESS)));
std::function<const void*(const char*)> stub_get_extension_fn =
[](const char* name) { return &kStubInstallationManagerApi; };
ASSERT_TRUE(EnsureDirectoryExists(installation_path.c_str()));
char manifest_content[] = R"json(
{
"manifest_version": 2,
"name": "Cobalt",
"description": "Cobalt",
"version": "1.2.0"
})json";
CreateManifest(manifest_content, installation_path);
CreateEmptyLibrary("libcobalt.so", installation_path);
EvergreenLibraryMetadata metadata =
GetCurrentEvergreenLibraryMetadata(stub_get_extension_fn);
ASSERT_EQ(metadata.version, "1.2.0");
ASSERT_EQ(metadata.file_type, "Uncompressed");
DeleteManifest(installation_path);
DeleteLibraryDirRecursively(installation_path);
}
TEST_F(UtilsTest,
ReturnsFileTypeUnknownInMetadataForUnexpectedLibInManagedInstallation) {
std::string installation_path = base::StrCat(
{temp_dir_path_.data(), kSbFileSepString, "some_installation_path"});
int installation_index = 1;
MockInstallationManagerApi* mock_installation_manager_api =
GetMockInstallationManagerApi();
EXPECT_CALL(*mock_installation_manager_api, GetCurrentInstallationIndex())
.Times(1)
.WillOnce(testing::Return(installation_index));
EXPECT_CALL(
*mock_installation_manager_api,
GetInstallationPath(installation_index, testing::_, kSbFileMaxPath))
.Times(1)
.WillOnce(
testing::DoAll(testing::SetArrayArgument<1>(installation_path.begin(),
installation_path.end()),
testing::Return(IM_EXT_SUCCESS)));
std::function<const void*(const char*)> stub_get_extension_fn =
[](const char* name) { return &kStubInstallationManagerApi; };
ASSERT_TRUE(EnsureDirectoryExists(installation_path.c_str()));
CreateEmptyLibrary("libcobalt.unexpected", installation_path);
EvergreenLibraryMetadata metadata =
GetCurrentEvergreenLibraryMetadata(stub_get_extension_fn);
ASSERT_EQ(metadata.file_type, "FileTypeUnknown");
DeleteLibraryDirRecursively(installation_path);
}
TEST_F(UtilsTest,
ReturnsEvergreenLibMetadataFromLoadedInstallationWhenErrorGettingIM) {
std::function<const void*(const char*)> stub_get_extension_fn =
[](const char* name) { return nullptr; };
std::vector<char> system_path_content_dir(kSbFileMaxPath);
SbSystemGetPath(kSbSystemPathContentDirectory, system_path_content_dir.data(),
kSbFileMaxPath);
// Since the libupdater_test.so library has already been loaded,
// kSbSystemPathContentDirectory points to the content dir of the running
// library and the installation dir is therefore its parent.
std::string installation_path =
base::FilePath(std::string(system_path_content_dir.begin(),
system_path_content_dir.end()))
.DirName()
.value();
char manifest_content[] = R"json(
{
"manifest_version": 2,
"name": "Cobalt",
"description": "Cobalt",
"version": "1.2.0"
})json";
CreateManifest(manifest_content, installation_path);
CreateEmptyLibrary("libcobalt.lz4", installation_path);
EvergreenLibraryMetadata metadata =
GetCurrentEvergreenLibraryMetadata(stub_get_extension_fn);
ASSERT_EQ(metadata.version, "1.2.0");
ASSERT_EQ(metadata.file_type, "Compressed");
DeleteManifest(installation_path);
DeleteLibraryDirRecursively(installation_path);
}
} // namespace
} // namespace updater
} // namespace cobalt