Fix Windows delta updates (#31937)
Chromium 142 broke delta updates on Windows. This commit fixes them by reverting upstream commits 13ca199c and a4d120da with a few patches, vendored code and chromium_src overrides. The changes should affect the browser Windows installer only, and not the Omaha update client. Co-authored-by: Max Karolinskiy <max@brave.com>
This commit is contained in:
co-authored by
Max Karolinskiy
parent
d34a60dc3c
commit
49faa97bb6
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) 2025 The Brave Authors. All rights reserved.
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
import("//testing/test.gni")
|
||||
|
||||
assert(is_win)
|
||||
|
||||
source_set("unit_tests") {
|
||||
sources = [
|
||||
"archive_patch_helper_unittest.cc",
|
||||
"brave_setup_util_unittest.cc",
|
||||
]
|
||||
deps = [
|
||||
"//base",
|
||||
"//base/test:test_support",
|
||||
"//chrome/common:constants",
|
||||
"//chrome/installer/setup:lib",
|
||||
"//chrome/installer/util:constants",
|
||||
"//chrome/installer/util:metainstaller_utils",
|
||||
"//chrome/installer/util:with_no_strings",
|
||||
"//testing/gtest",
|
||||
]
|
||||
testonly = true
|
||||
}
|
||||
|
||||
# As of this writing, the sole purpose of this GN target is to call
|
||||
# chrome::RegisterPathProvider() for archive_patch_helper_unittest.cc.
|
||||
source_set("run_all_unittests") {
|
||||
sources = [ "run_all_unittests.cc" ]
|
||||
deps = [
|
||||
"//base",
|
||||
"//base/test:test_support",
|
||||
"//chrome/common:constants",
|
||||
]
|
||||
testonly = true
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
include_rules = [
|
||||
"+brave/third_party/bspatch",
|
||||
"+chrome/common/chrome_paths.h",
|
||||
"+chrome/installer/setup",
|
||||
"+chrome/installer/util/lzma_util.h",
|
||||
"+chrome/installer/util/installation_state.h",
|
||||
"+chrome/installer/util/util_constants.h",
|
||||
"+components/zucchini/zucchini.h",
|
||||
"+components/zucchini/zucchini_integration.h",
|
||||
]
|
||||
@@ -0,0 +1,125 @@
|
||||
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// This file contains code that used to be upstream and had to be restored in
|
||||
// Brave to support delta updates on Windows until we are on Omaha 4. See:
|
||||
// github.com/brave/brave-core/pull/31937
|
||||
|
||||
#include "brave/installer/setup/archive_patch_helper.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "brave/third_party/bspatch/mbspatch.h"
|
||||
#include "chrome/installer/util/lzma_util.h"
|
||||
#include "components/zucchini/zucchini.h"
|
||||
#include "components/zucchini/zucchini_integration.h"
|
||||
|
||||
namespace installer {
|
||||
|
||||
ArchivePatchHelper::ArchivePatchHelper(const base::FilePath& working_directory,
|
||||
const base::FilePath& compressed_archive,
|
||||
const base::FilePath& patch_source,
|
||||
const base::FilePath& target,
|
||||
UnPackConsumer consumer)
|
||||
: working_directory_(working_directory),
|
||||
compressed_archive_(compressed_archive),
|
||||
patch_source_(patch_source),
|
||||
target_(target),
|
||||
consumer_(consumer) {}
|
||||
|
||||
ArchivePatchHelper::~ArchivePatchHelper() = default;
|
||||
|
||||
// static
|
||||
bool ArchivePatchHelper::UncompressAndPatch(
|
||||
const base::FilePath& working_directory,
|
||||
const base::FilePath& compressed_archive,
|
||||
const base::FilePath& patch_source,
|
||||
const base::FilePath& target,
|
||||
UnPackConsumer consumer) {
|
||||
ArchivePatchHelper instance(working_directory, compressed_archive,
|
||||
patch_source, target, consumer);
|
||||
return (instance.Uncompress(nullptr) && instance.ApplyAndDeletePatch());
|
||||
}
|
||||
|
||||
bool ArchivePatchHelper::Uncompress(base::FilePath* last_uncompressed_file) {
|
||||
// The target shouldn't already exist.
|
||||
DCHECK(!base::PathExists(target_));
|
||||
|
||||
// UnPackArchive takes care of logging.
|
||||
base::FilePath output_file;
|
||||
UnPackStatus unpack_status =
|
||||
UnPackArchive(compressed_archive_, working_directory_, &output_file);
|
||||
RecordUnPackMetrics(unpack_status, consumer_);
|
||||
if (unpack_status != UNPACK_NO_ERROR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
last_uncompressed_file_ = output_file;
|
||||
if (last_uncompressed_file) {
|
||||
*last_uncompressed_file = last_uncompressed_file_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArchivePatchHelper::ApplyAndDeletePatch() {
|
||||
const bool succeeded = ZucchiniEnsemblePatch() || BinaryPatch();
|
||||
if (!last_uncompressed_file_.empty()) {
|
||||
base::DeleteFile(last_uncompressed_file_);
|
||||
}
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
bool ArchivePatchHelper::ZucchiniEnsemblePatch() {
|
||||
if (last_uncompressed_file_.empty()) {
|
||||
LOG(ERROR) << "No patch file found in compressed archive.";
|
||||
return false;
|
||||
}
|
||||
|
||||
zucchini::status::Code result =
|
||||
zucchini::Apply(patch_source_, last_uncompressed_file_, target_);
|
||||
|
||||
if (result == zucchini::status::kStatusSuccess) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG(ERROR) << "Failed to apply patch " << last_uncompressed_file_.value()
|
||||
<< " to file " << patch_source_.value() << " and generating file "
|
||||
<< target_.value()
|
||||
<< " using Zucchini. err=" << static_cast<uint32_t>(result);
|
||||
|
||||
// Ensure a partial output is not left behind.
|
||||
base::DeleteFile(target_);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ArchivePatchHelper::BinaryPatch() {
|
||||
if (last_uncompressed_file_.empty()) {
|
||||
LOG(ERROR) << "No patch file found in compressed archive.";
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = ApplyBinaryPatch(patch_source_.value().c_str(),
|
||||
last_uncompressed_file_.value().c_str(),
|
||||
target_.value().c_str());
|
||||
if (result == OK) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG(ERROR) << "Failed to apply patch " << last_uncompressed_file_.value()
|
||||
<< " to file " << patch_source_.value() << " and generating file "
|
||||
<< target_.value() << " using bsdiff. err=" << result;
|
||||
|
||||
// Ensure a partial output is not left behind.
|
||||
base::DeleteFile(target_);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace installer
|
||||
@@ -0,0 +1,112 @@
|
||||
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// This file contains code that used to be upstream and had to be restored in
|
||||
// Brave to support delta updates on Windows until we are on Omaha 4. See:
|
||||
// github.com/brave/brave-core/pull/31937
|
||||
|
||||
#ifndef BRAVE_INSTALLER_SETUP_ARCHIVE_PATCH_HELPER_H_
|
||||
#define BRAVE_INSTALLER_SETUP_ARCHIVE_PATCH_HELPER_H_
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "chrome/installer/setup/setup_util.h"
|
||||
#include "chrome/installer/util/lzma_util.h"
|
||||
|
||||
namespace installer {
|
||||
|
||||
// A helper class that facilitates uncompressing and patching the chrome archive
|
||||
// and installer.
|
||||
//
|
||||
// Chrome's installer is deployed along with a compressed archive containing
|
||||
// either 1) an uncompressd archive of the product binaries or 2) a patch file
|
||||
// to be applied to the uncompressed archive of the version being updated. To
|
||||
// obtain the uncompressed archive, the contents of the compressed archive are
|
||||
// uncompressed and extracted. Installation proceeds directly if the
|
||||
// uncompressed archive is found after this step. Otherwise, the patch is
|
||||
// applied to the previous version's uncompressed archive using either
|
||||
// Zucchini's patching or bspatch.
|
||||
//
|
||||
// Chrome's installer itself may also be deployed as a patch against the
|
||||
// previous version's saved installer binary. The same process is followed to
|
||||
// obtain the new installer. The compressed archive unconditionally contains a
|
||||
// patch file in this case.
|
||||
class ArchivePatchHelper {
|
||||
public:
|
||||
// Constructs an instance that can uncompress |compressed_archive| into
|
||||
// |working_directory| and optionally apply the extracted patch file to
|
||||
// |patch_source|, writing the result to |target|.
|
||||
ArchivePatchHelper(const base::FilePath& working_directory,
|
||||
const base::FilePath& compressed_archive,
|
||||
const base::FilePath& patch_source,
|
||||
const base::FilePath& target,
|
||||
UnPackConsumer consumer);
|
||||
|
||||
ArchivePatchHelper(const ArchivePatchHelper&) = delete;
|
||||
ArchivePatchHelper& operator=(const ArchivePatchHelper&) = delete;
|
||||
|
||||
~ArchivePatchHelper();
|
||||
|
||||
// Uncompresses |compressed_archive| in |working_directory| then applies the
|
||||
// extracted patch file to |patch_source|, writing the result to |target|.
|
||||
// Ensemble patching via Zucchini is attempted first (if it is enabled). If
|
||||
// that fails bspatch is attempted. Returns false if uncompression or all
|
||||
// patching steps fail.
|
||||
static bool UncompressAndPatch(const base::FilePath& working_directory,
|
||||
const base::FilePath& compressed_archive,
|
||||
const base::FilePath& patch_source,
|
||||
const base::FilePath& target,
|
||||
UnPackConsumer consumer);
|
||||
|
||||
// Uncompresses compressed_archive() into the working directory. On success,
|
||||
// last_uncompressed_file (if not nullptr) is populated with the path to the
|
||||
// last file extracted from the archive.
|
||||
bool Uncompress(base::FilePath* last_uncompressed_file);
|
||||
|
||||
// Performs ensemble patching on the uncompressed version of
|
||||
// |compressed_archive| in |working_directory| as specified in the constructor
|
||||
// using files from |patch_source|. Ensemble patching via Zucchini is
|
||||
// attempted first (if it is enabled). Zucchini falls back to bspatch if
|
||||
// unsuccessful. The uncompressed patch file is unconditionally deleted at the
|
||||
// end.
|
||||
bool ApplyAndDeletePatch();
|
||||
|
||||
// Attempts to use Zucchini to apply last_uncompressed_file() to
|
||||
// patch_source() to generate target(). Returns false if patching fails.
|
||||
bool ZucchiniEnsemblePatch();
|
||||
|
||||
// Attempts to use bspatch to apply last_uncompressed_file() to patch_source()
|
||||
// to generate target(). Returns false if patching fails.
|
||||
bool BinaryPatch();
|
||||
|
||||
const base::FilePath& compressed_archive() const {
|
||||
return compressed_archive_;
|
||||
}
|
||||
void set_patch_source(const base::FilePath& patch_source) {
|
||||
patch_source_ = patch_source;
|
||||
}
|
||||
const base::FilePath& patch_source() const { return patch_source_; }
|
||||
const base::FilePath& target() const { return target_; }
|
||||
|
||||
// Returns the path of the last file extracted by Uncompress().
|
||||
const base::FilePath& last_uncompressed_file() const {
|
||||
return last_uncompressed_file_;
|
||||
}
|
||||
void set_last_uncompressed_file(
|
||||
const base::FilePath& last_uncompressed_file) {
|
||||
last_uncompressed_file_ = last_uncompressed_file;
|
||||
}
|
||||
|
||||
private:
|
||||
base::FilePath working_directory_;
|
||||
base::FilePath compressed_archive_;
|
||||
base::FilePath patch_source_;
|
||||
base::FilePath target_;
|
||||
base::FilePath last_uncompressed_file_;
|
||||
UnPackConsumer consumer_;
|
||||
};
|
||||
|
||||
} // namespace installer
|
||||
|
||||
#endif // BRAVE_INSTALLER_SETUP_ARCHIVE_PATCH_HELPER_H_
|
||||
@@ -0,0 +1,83 @@
|
||||
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// This file contains code that used to be upstream and had to be restored in
|
||||
// Brave to support delta updates on Windows until we are on Omaha 4. See:
|
||||
// github.com/brave/brave-core/pull/31937
|
||||
|
||||
#include "brave/installer/setup/archive_patch_helper.h"
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/files/scoped_temp_dir.h"
|
||||
#include "base/path_service.h"
|
||||
#include "chrome/common/chrome_paths.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class ArchivePatchHelperTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// This requires chrome::RegisterPathProvider() to have been called.
|
||||
ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &data_dir_));
|
||||
data_dir_ = data_dir_.AppendASCII("installer");
|
||||
ASSERT_TRUE(base::PathExists(data_dir_));
|
||||
|
||||
// Create a temp directory for testing.
|
||||
ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
data_dir_.clear();
|
||||
// Clean up test directory manually so we can fail if it leaks.
|
||||
ASSERT_TRUE(test_dir_.Delete());
|
||||
}
|
||||
|
||||
// The path to input data used in tests.
|
||||
base::FilePath data_dir_;
|
||||
|
||||
// The temporary directory used to contain the test operations.
|
||||
base::ScopedTempDir test_dir_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(ArchivePatchHelperTest, ZucchiniPatching) {
|
||||
base::FilePath src = data_dir_.AppendASCII("archive1.7z");
|
||||
base::FilePath patch = data_dir_.AppendASCII("zucchini_archive.diff");
|
||||
base::FilePath dest = test_dir_.GetPath().AppendASCII("archive2.7z");
|
||||
installer::ArchivePatchHelper archive_helper(
|
||||
test_dir_.GetPath(), base::FilePath(), src, dest,
|
||||
installer::UnPackConsumer::SETUP_EXE_PATCH);
|
||||
archive_helper.set_last_uncompressed_file(patch);
|
||||
EXPECT_TRUE(archive_helper.ZucchiniEnsemblePatch());
|
||||
base::FilePath base = data_dir_.AppendASCII("archive2.7z");
|
||||
EXPECT_TRUE(base::ContentsEqual(dest, base));
|
||||
}
|
||||
|
||||
TEST_F(ArchivePatchHelperTest, InvalidDiff_MisalignedCblen) {
|
||||
base::FilePath src = data_dir_.AppendASCII("bin.old");
|
||||
base::FilePath patch = data_dir_.AppendASCII("misaligned_cblen.diff");
|
||||
base::FilePath dest = test_dir_.GetPath().AppendASCII("bin.new");
|
||||
installer::ArchivePatchHelper archive_helper(
|
||||
test_dir_.GetPath(), base::FilePath(), src, dest,
|
||||
installer::UnPackConsumer::SETUP_EXE_PATCH);
|
||||
archive_helper.set_last_uncompressed_file(patch);
|
||||
// Should fail, but not crash.
|
||||
EXPECT_FALSE(archive_helper.BinaryPatch());
|
||||
}
|
||||
|
||||
TEST_F(ArchivePatchHelperTest, InvalidDiff_NegativeSeek) {
|
||||
base::FilePath src = data_dir_.AppendASCII("bin.old");
|
||||
base::FilePath patch = data_dir_.AppendASCII("negative_seek.diff");
|
||||
base::FilePath dest = test_dir_.GetPath().AppendASCII("bin.new");
|
||||
installer::ArchivePatchHelper archive_helper(
|
||||
test_dir_.GetPath(), base::FilePath(), src, dest,
|
||||
installer::UnPackConsumer::SETUP_EXE_PATCH);
|
||||
archive_helper.set_last_uncompressed_file(patch);
|
||||
// Should fail, but not crash.
|
||||
EXPECT_FALSE(archive_helper.BinaryPatch());
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "brave/installer/setup/brave_setup_util.h"
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/version.h"
|
||||
#include "chrome/installer/setup/installer_state.h"
|
||||
#include "chrome/installer/setup/setup_constants.h"
|
||||
#include "chrome/installer/setup/setup_util.h"
|
||||
#include "chrome/installer/util/installation_state.h"
|
||||
|
||||
namespace installer {
|
||||
|
||||
// This function implementation used to be upstream and had to be restored in
|
||||
// Brave to support delta updates on Windows until we are on Omaha 4. See:
|
||||
// github.com/brave/brave-core/pull/31937
|
||||
base::FilePath FindArchiveToPatch(const InstallationState& original_state,
|
||||
const InstallerState& installer_state,
|
||||
const base::Version& desired_version) {
|
||||
if (desired_version.IsValid()) {
|
||||
base::FilePath archive(
|
||||
installer_state.GetInstallerDirectory(desired_version)
|
||||
.Append(kChromeArchive));
|
||||
return base::PathExists(archive) ? archive : base::FilePath();
|
||||
}
|
||||
|
||||
// Check based on the version number advertised to Google Update, since that
|
||||
// is the value used to select a specific differential update. If an archive
|
||||
// can't be found using that, fallback to using the newest version present.
|
||||
base::FilePath patch_source;
|
||||
const ProductState* product =
|
||||
original_state.GetProductState(installer_state.system_install());
|
||||
if (product) {
|
||||
patch_source = installer_state.GetInstallerDirectory(product->version())
|
||||
.Append(installer::kChromeArchive);
|
||||
if (base::PathExists(patch_source)) {
|
||||
return patch_source;
|
||||
}
|
||||
}
|
||||
std::unique_ptr<base::Version> version(
|
||||
installer::GetMaxVersionFromArchiveDir(installer_state.target_path()));
|
||||
if (version) {
|
||||
patch_source = installer_state.GetInstallerDirectory(*version).Append(
|
||||
installer::kChromeArchive);
|
||||
if (base::PathExists(patch_source)) {
|
||||
return patch_source;
|
||||
}
|
||||
}
|
||||
return base::FilePath();
|
||||
}
|
||||
|
||||
} // namespace installer
|
||||
@@ -0,0 +1,32 @@
|
||||
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BRAVE_INSTALLER_SETUP_BRAVE_SETUP_UTIL_H_
|
||||
#define BRAVE_INSTALLER_SETUP_BRAVE_SETUP_UTIL_H_
|
||||
|
||||
namespace base {
|
||||
class FilePath;
|
||||
class Version;
|
||||
} // namespace base
|
||||
|
||||
namespace installer {
|
||||
|
||||
class InstallationState;
|
||||
class InstallerState;
|
||||
|
||||
// Returns the uncompressed archive of the installed version that serves as the
|
||||
// source for patching. If |desired_version| is valid, only the path to that
|
||||
// version will be returned, or empty if it doesn't exist.
|
||||
//
|
||||
// This function used to be upstream and had to be restored in Brave to support
|
||||
// delta updates on Windows until we are on Omaha 4. See:
|
||||
// github.com/brave/brave-core/pull/31937
|
||||
base::FilePath FindArchiveToPatch(const InstallationState& original_state,
|
||||
const InstallerState& installer_state,
|
||||
const base::Version& desired_version);
|
||||
|
||||
} // namespace installer
|
||||
|
||||
#endif // BRAVE_INSTALLER_SETUP_BRAVE_SETUP_UTIL_H_
|
||||
@@ -0,0 +1,181 @@
|
||||
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "brave/installer/setup/brave_setup_util.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/files/scoped_temp_dir.h"
|
||||
#include "base/test/test_reg_util_win.h"
|
||||
#include "base/version.h"
|
||||
#include "chrome/installer/setup/installer_state.h"
|
||||
#include "chrome/installer/setup/setup_constants.h"
|
||||
#include "chrome/installer/util/installation_state.h"
|
||||
#include "chrome/installer/util/util_constants.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// A test fixture that configures an InstallationState and an InstallerState
|
||||
// with a product being updated.
|
||||
//
|
||||
// FindArchiveToPatchTest and its test cases used to be upstream and had to be
|
||||
// restored in Brave to support delta updates on Windows until we are on
|
||||
// Omaha 4. See github.com/brave/brave-core/pull/31937.
|
||||
class FindArchiveToPatchTest : public testing::Test {
|
||||
public:
|
||||
FindArchiveToPatchTest(const FindArchiveToPatchTest&) = delete;
|
||||
FindArchiveToPatchTest& operator=(const FindArchiveToPatchTest&) = delete;
|
||||
|
||||
protected:
|
||||
class FakeInstallationState : public installer::InstallationState {};
|
||||
|
||||
class FakeProductState : public installer::ProductState {
|
||||
public:
|
||||
static FakeProductState* FromProductState(const ProductState* product) {
|
||||
return static_cast<FakeProductState*>(const_cast<ProductState*>(product));
|
||||
}
|
||||
|
||||
void set_version(const base::Version& version) {
|
||||
if (version.IsValid()) {
|
||||
version_ = std::make_unique<base::Version>(version);
|
||||
} else {
|
||||
version_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void set_uninstall_command(const base::CommandLine& uninstall_command) {
|
||||
uninstall_command_ = uninstall_command;
|
||||
}
|
||||
};
|
||||
|
||||
FindArchiveToPatchTest() = default;
|
||||
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER));
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
registry_override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE));
|
||||
product_version_ = base::Version("30.0.1559.0");
|
||||
max_version_ = base::Version("47.0.1559.0");
|
||||
|
||||
// Install the product according to the version.
|
||||
original_state_ = std::make_unique<FakeInstallationState>();
|
||||
InstallProduct();
|
||||
|
||||
// Prepare to update the product in the temp dir.
|
||||
installer_state_ = std::make_unique<installer::InstallerState>(
|
||||
installer::InstallerState::USER_LEVEL);
|
||||
installer_state_->set_target_path_for_testing(test_dir_.GetPath());
|
||||
|
||||
// Create archives in the two version dirs.
|
||||
ASSERT_TRUE(
|
||||
base::CreateDirectory(GetProductVersionArchivePath().DirName()));
|
||||
ASSERT_TRUE(base::WriteFile(GetProductVersionArchivePath(), "a"));
|
||||
ASSERT_TRUE(base::CreateDirectory(GetMaxVersionArchivePath().DirName()));
|
||||
ASSERT_TRUE(base::WriteFile(GetMaxVersionArchivePath(), "b"));
|
||||
}
|
||||
|
||||
void TearDown() override { original_state_.reset(); }
|
||||
|
||||
base::FilePath GetArchivePath(const base::Version& version) const {
|
||||
return test_dir_.GetPath()
|
||||
.AppendASCII(version.GetString())
|
||||
.Append(installer::kInstallerDir)
|
||||
.Append(installer::kChromeArchive);
|
||||
}
|
||||
|
||||
base::FilePath GetMaxVersionArchivePath() const {
|
||||
return GetArchivePath(max_version_);
|
||||
}
|
||||
|
||||
base::FilePath GetProductVersionArchivePath() const {
|
||||
return GetArchivePath(product_version_);
|
||||
}
|
||||
|
||||
void InstallProduct() {
|
||||
FakeProductState* product = FakeProductState::FromProductState(
|
||||
original_state_->GetNonVersionedProductState(false));
|
||||
|
||||
product->set_version(product_version_);
|
||||
base::CommandLine uninstall_command(
|
||||
test_dir_.GetPath()
|
||||
.AppendASCII(product_version_.GetString())
|
||||
.Append(installer::kInstallerDir)
|
||||
.Append(installer::kSetupExe));
|
||||
uninstall_command.AppendSwitch(installer::switches::kUninstall);
|
||||
product->set_uninstall_command(uninstall_command);
|
||||
}
|
||||
|
||||
void UninstallProduct() {
|
||||
FakeProductState::FromProductState(
|
||||
original_state_->GetNonVersionedProductState(false))
|
||||
->set_version(base::Version());
|
||||
}
|
||||
|
||||
base::ScopedTempDir test_dir_;
|
||||
base::Version product_version_;
|
||||
base::Version max_version_;
|
||||
std::unique_ptr<FakeInstallationState> original_state_;
|
||||
std::unique_ptr<installer::InstallerState> installer_state_;
|
||||
|
||||
private:
|
||||
registry_util::RegistryOverrideManager registry_override_manager_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// Test that the path to the advertised product version is found.
|
||||
TEST_F(FindArchiveToPatchTest, ProductVersionFound) {
|
||||
base::FilePath patch_source(installer::FindArchiveToPatch(
|
||||
*original_state_, *installer_state_, base::Version()));
|
||||
EXPECT_EQ(GetProductVersionArchivePath().value(), patch_source.value());
|
||||
}
|
||||
|
||||
// Test that the path to the max version is found if the advertised version is
|
||||
// missing.
|
||||
TEST_F(FindArchiveToPatchTest, MaxVersionFound) {
|
||||
// The patch file is absent.
|
||||
ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath()));
|
||||
base::FilePath patch_source(installer::FindArchiveToPatch(
|
||||
*original_state_, *installer_state_, base::Version()));
|
||||
EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source.value());
|
||||
|
||||
// The product doesn't appear to be installed, so the max version is found.
|
||||
UninstallProduct();
|
||||
patch_source = installer::FindArchiveToPatch(
|
||||
*original_state_, *installer_state_, base::Version());
|
||||
EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source.value());
|
||||
}
|
||||
|
||||
// Test that an empty path is returned if no version is found.
|
||||
TEST_F(FindArchiveToPatchTest, NoVersionFound) {
|
||||
// The product doesn't appear to be installed and no archives are present.
|
||||
UninstallProduct();
|
||||
ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath()));
|
||||
ASSERT_TRUE(base::DeleteFile(GetMaxVersionArchivePath()));
|
||||
|
||||
base::FilePath patch_source(installer::FindArchiveToPatch(
|
||||
*original_state_, *installer_state_, base::Version()));
|
||||
EXPECT_EQ(base::FilePath::StringType(), patch_source.value());
|
||||
}
|
||||
|
||||
TEST_F(FindArchiveToPatchTest, DesiredVersionFound) {
|
||||
base::FilePath patch_source1(installer::FindArchiveToPatch(
|
||||
*original_state_, *installer_state_, product_version_));
|
||||
EXPECT_EQ(GetProductVersionArchivePath().value(), patch_source1.value());
|
||||
base::FilePath patch_source2(installer::FindArchiveToPatch(
|
||||
*original_state_, *installer_state_, max_version_));
|
||||
EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source2.value());
|
||||
}
|
||||
|
||||
TEST_F(FindArchiveToPatchTest, DesiredVersionNotFound) {
|
||||
base::FilePath patch_source(installer::FindArchiveToPatch(
|
||||
*original_state_, *installer_state_, base::Version("1.2.3.4")));
|
||||
EXPECT_EQ(base::FilePath().value(), patch_source.value());
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/test/launcher/unit_test_launcher.h"
|
||||
#include "base/test/test_suite.h"
|
||||
#include "chrome/common/chrome_paths.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
base::TestSuite test_suite(argc, argv);
|
||||
|
||||
// For archive_patch_helper_unittest.cc.
|
||||
chrome::RegisterPathProvider();
|
||||
|
||||
return base::LaunchUnitTests(
|
||||
argc, argv,
|
||||
base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
|
||||
}
|
||||
Reference in New Issue
Block a user