blob: 46153f96ce27405d519f8d4ca5335c31aeedfe57 [file] [log] [blame]
// 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 <iostream>
#include <map>
#include <string>
#include <utility>
#include "base/at_exit.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/launcher/unit_test_launcher.h"
#include "base/test/task_environment.h"
#include "base/test/test_suite.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/updater/app/app.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/ipc/ipc_support.h"
#include "chrome/updater/test/integration_tests_impl.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/util/unittest_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_WIN)
#include "base/win/scoped_com_initializer.h"
#include "chrome/updater/util/win_util.h"
#endif
namespace updater {
namespace test {
namespace {
using ::testing::EmptyTestEventListener;
using ::testing::Test;
using ::testing::TestCase;
using ::testing::TestEventListeners;
using ::testing::TestInfo;
using ::testing::TestPartResult;
using ::testing::UnitTest;
constexpr int kSuccess = 0;
constexpr int kUnknownSwitch = 101;
constexpr int kBadCommand = 102;
base::Value ValueFromString(const std::string& values) {
absl::optional<base::Value> results_value = base::JSONReader::Read(values);
EXPECT_TRUE(results_value);
return results_value->Clone();
}
template <typename... Args>
base::RepeatingCallback<bool(Args...)> WithSwitch(
const std::string& flag,
base::RepeatingCallback<bool(const std::string&, Args...)> callback) {
return base::BindLambdaForTesting([=](Args... args) {
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(flag)) {
return callback.Run(command_line->GetSwitchValueASCII(flag),
std::move(args)...);
}
LOG(ERROR) << "Missing switch: " << flag;
return false;
});
}
// Overload for bool switches, represented by literals "false" and "true".
template <typename... Args>
base::RepeatingCallback<bool(Args...)> WithSwitch(
const std::string& flag,
base::RepeatingCallback<bool(bool, Args...)> callback) {
return WithSwitch(
flag,
base::BindLambdaForTesting([=](const std::string& flag, Args... args) {
if (flag == "false" || flag == "true") {
return callback.Run(flag == "true", std::move(args)...);
}
return false;
}));
}
// Overload for int switches.
template <typename... Args>
base::RepeatingCallback<bool(Args...)> WithSwitch(
const std::string& flag,
base::RepeatingCallback<bool(int, Args...)> callback) {
return WithSwitch(
flag,
base::BindLambdaForTesting([=](const std::string& flag, Args... args) {
int flag_int = -1;
if (base::StringToInt(flag, &flag_int)) {
return callback.Run(flag_int, std::move(args)...);
}
return false;
}));
}
// Overload for GURL switches.
template <typename... Args>
base::RepeatingCallback<bool(Args...)> WithSwitch(
const std::string& flag,
base::RepeatingCallback<bool(const GURL&, Args...)> callback) {
return WithSwitch(
flag,
base::BindLambdaForTesting([=](const std::string& flag, Args... args) {
return callback.Run(GURL(flag), std::move(args)...);
}));
}
// Overload for FilePath switches.
template <typename... Args>
base::RepeatingCallback<bool(Args...)> WithSwitch(
const std::string& flag,
base::RepeatingCallback<bool(const base::FilePath&, Args...)> callback) {
return WithSwitch(
flag,
base::BindLambdaForTesting([=](const std::string& flag, Args... args) {
return callback.Run(base::FilePath::FromUTF8Unsafe(flag),
std::move(args)...);
}));
}
// Overload for Version switches.
template <typename... Args>
base::RepeatingCallback<bool(Args...)> WithSwitch(
const std::string& flag,
base::RepeatingCallback<bool(const base::Version&, Args...)> callback) {
return WithSwitch(
flag,
base::BindLambdaForTesting([=](const std::string& flag, Args... args) {
return callback.Run(base::Version(flag), std::move(args)...);
}));
}
// Overload for base::Value::Dict switches.
template <typename... Args>
base::RepeatingCallback<bool(Args...)> WithSwitch(
const std::string& flag,
base::RepeatingCallback<bool(const base::Value::Dict&, Args...)> callback) {
return WithSwitch(
flag,
base::BindLambdaForTesting([=](const std::string& flag, Args... args) {
return callback.Run(std::move(ValueFromString(flag).GetDict()),
std::move(args)...);
}));
}
// Overload for base::Value::List switches.
template <typename... Args>
base::RepeatingCallback<bool(Args...)> WithSwitch(
const std::string& flag,
base::RepeatingCallback<bool(const base::Value::List&, Args...)> callback) {
return WithSwitch(
flag,
base::BindLambdaForTesting([=](const std::string& flag, Args... args) {
return callback.Run(std::move(ValueFromString(flag).GetList()),
std::move(args)...);
}));
}
// Overload for `AppBundleWebCreateMode` switches, represented by ints.
template <typename... Args>
base::RepeatingCallback<bool(Args...)> WithSwitch(
const std::string& flag,
base::RepeatingCallback<bool(AppBundleWebCreateMode, Args...)> callback) {
return WithSwitch(
flag,
base::BindLambdaForTesting([=](const std::string& flag, Args... args) {
int flag_app_bundle_web_create_mode = -1;
if (base::StringToInt(flag, &flag_app_bundle_web_create_mode) &&
flag_app_bundle_web_create_mode >=
static_cast<int>(AppBundleWebCreateMode::kCreateApp) &&
flag_app_bundle_web_create_mode <=
static_cast<int>(AppBundleWebCreateMode::kCreateInstalledApp)) {
return callback.Run(static_cast<AppBundleWebCreateMode>(
flag_app_bundle_web_create_mode),
std::move(args)...);
}
return false;
}));
}
template <typename Arg, typename... RemainingArgs>
base::RepeatingCallback<bool(RemainingArgs...)> WithArg(
Arg arg,
base::RepeatingCallback<bool(Arg, RemainingArgs...)> callback) {
return base::BindRepeating(callback, arg);
}
// Adapts the input callback to take a shutdown callback as the final parameter.
template <typename... Args>
base::RepeatingCallback<bool(Args..., base::OnceCallback<void(int)>)>
WithShutdown(base::RepeatingCallback<int(Args...)> callback) {
return base::BindLambdaForTesting(
[=](Args... args, base::OnceCallback<void(int)> shutdown) {
std::move(shutdown).Run(callback.Run(args...));
return true;
});
}
// Short-named wrapper around BindOnce.
template <typename... Args, typename... ProvidedArgs>
base::RepeatingCallback<bool(Args..., base::OnceCallback<void(int)>)> Wrap(
int (*func)(Args...),
ProvidedArgs... provided_args) {
return WithShutdown(base::BindRepeating(func, provided_args...));
}
// Overload of Wrap for functions that return void. (Returns kSuccess.)
template <typename... Args>
base::RepeatingCallback<bool(Args..., base::OnceCallback<void(int)>)> Wrap(
void (*func)(Args...)) {
return WithShutdown(base::BindLambdaForTesting([=](Args... args) {
func(args...);
return kSuccess;
}));
}
// Helper to shorten lines below.
template <typename... Args>
base::RepeatingCallback<bool(Args...)> WithSystemScope(
base::RepeatingCallback<bool(UpdaterScope, Args...)> callback) {
return WithArg(UpdaterScope::kSystem, callback);
}
class AppTestHelper : public App {
private:
~AppTestHelper() override = default;
void FirstTaskRun() override;
};
void AppTestHelper::FirstTaskRun() {
std::map<std::string,
base::RepeatingCallback<bool(base::OnceCallback<void(int)>)>>
commands =
{
// To add additional commands, first Wrap a pointer to the target
// function (which should be declared in integration_tests_impl.h), and
// then use the With* helper functions to provide its arguments.
{"clean", WithSystemScope(Wrap(&Clean))},
{"enter_test_mode",
WithSwitch("device_management_url",
WithSwitch("crash_upload_url",
WithSwitch("update_url", Wrap(&EnterTestMode))))},
{"exit_test_mode", WithSystemScope(Wrap(&ExitTestMode))},
{"set_group_policies", WithSwitch("values", Wrap(&SetGroupPolicies))},
{"fill_log", WithSystemScope(Wrap(&FillLog))},
{"expect_log_rotated", WithSystemScope(Wrap(&ExpectLogRotated))},
{"expect_registered",
WithSwitch("app_id", WithSystemScope(Wrap(&ExpectRegistered)))},
{"expect_not_registered",
WithSwitch("app_id", WithSystemScope(Wrap(&ExpectNotRegistered)))},
{"expect_app_version",
WithSwitch("version", WithSwitch("app_id", WithSystemScope(
Wrap(&ExpectAppVersion))))},
{"expect_candidate_uninstalled",
WithSystemScope(Wrap(&ExpectCandidateUninstalled))},
{"expect_clean", WithSystemScope(Wrap(&ExpectClean))},
{"expect_installed", WithSystemScope(Wrap(&ExpectInstalled))},
#if BUILDFLAG(IS_WIN)
{"expect_interfaces_registered",
WithSystemScope(Wrap(&ExpectInterfacesRegistered))},
{"expect_marshal_interface_succeeds",
WithSystemScope(Wrap(&ExpectMarshalInterfaceSucceeds))},
{"expect_legacy_update3web_succeeds",
WithSwitch(
"expected_error_code",
WithSwitch(
"expected_final_state",
WithSwitch("app_bundle_web_create_mode",
WithSwitch("app_id",
WithSystemScope(Wrap(
&ExpectLegacyUpdate3WebSucceeds))))))},
{"expect_legacy_process_launcher_succeeds",
WithSystemScope(Wrap(&ExpectLegacyProcessLauncherSucceeds))},
{"expect_legacy_app_command_web_succeeds",
WithSwitch(
"expected_exit_code",
WithSwitch(
"parameters",
WithSwitch(
"command_id",
WithSwitch("app_id",
WithSystemScope(
Wrap(&ExpectLegacyAppCommandWebSucceeds))))))},
{"expect_legacy_policy_status_succeeds",
WithSystemScope(Wrap(&ExpectLegacyPolicyStatusSucceeds))},
{"run_uninstall_cmd_line", WithSystemScope(Wrap(&RunUninstallCmdLine))},
{"run_handoff", WithSwitch("app_id", WithSystemScope(Wrap(&RunHandoff)))},
#endif // BUILDFLAG(IS_WIN)
{"expect_version_active",
WithSwitch("version", WithSystemScope(Wrap(&ExpectVersionActive)))},
{"expect_version_not_active",
WithSwitch("version", WithSystemScope(Wrap(&ExpectVersionNotActive)))},
{"install", WithSystemScope(Wrap(&Install))},
{"install_updater_and_app",
WithSwitch("app_id", WithSystemScope(Wrap(&InstallUpdaterAndApp)))},
{"print_log", WithSystemScope(Wrap(&PrintLog))},
{"run_wake", WithSwitch("exit_code", WithSystemScope(Wrap(&RunWake)))},
{"run_wake_all", WithSystemScope(Wrap(&RunWakeAll))},
{"run_wake_active",
WithSwitch("exit_code", WithSystemScope(Wrap(&RunWakeActive)))},
{"run_crash_me", WithSystemScope(Wrap(&RunCrashMe))},
{"update",
WithSwitch("install_data_index",
(WithSwitch("app_id", WithSystemScope(Wrap(&Update)))))},
{"check_for_update",
(WithSwitch("app_id", WithSystemScope(Wrap(&CheckForUpdate))))},
{"update_all", WithSystemScope(Wrap(&UpdateAll))},
{"delete_updater_directory",
WithSystemScope(Wrap(&DeleteUpdaterDirectory))},
{"delete_file", (WithSwitch("path", WithSystemScope(Wrap(&DeleteFile))))},
{"install_app", WithSwitch("app_id", WithSystemScope(Wrap(&InstallApp)))},
{"uninstall_app",
WithSwitch("app_id", WithSystemScope(Wrap(&UninstallApp)))},
{"set_existence_checker_path",
WithSwitch("path",
(WithSwitch("app_id",
WithSystemScope(Wrap(&SetExistenceCheckerPath)))))},
{"setup_fake_updater_higher_version",
WithSystemScope(Wrap(&SetupFakeUpdaterHigherVersion))},
{"setup_fake_updater_lower_version",
WithSystemScope(Wrap(&SetupFakeUpdaterLowerVersion))},
{"setup_real_updater_lower_version",
WithSystemScope(Wrap(&SetupRealUpdaterLowerVersion))},
{"set_first_registration_counter",
WithSwitch("value", WithSystemScope(Wrap(&SetServerStarts)))},
{"stress_update_service", WithSystemScope(Wrap(&StressUpdateService))},
{"uninstall", WithSystemScope(Wrap(&Uninstall))},
{"call_service_update",
WithSwitch("same_version_update_allowed",
WithSwitch("install_data_index",
WithSwitch("app_id", WithSystemScope(Wrap(
&CallServiceUpdate)))))},
{"setup_fake_legacy_updater",
WithSystemScope(Wrap(&SetupFakeLegacyUpdater))},
#if BUILDFLAG(IS_WIN)
{"run_fake_legacy_updater", WithSystemScope(Wrap(&RunFakeLegacyUpdater))},
#endif // BUILDFLAG(IS_WIN)
{"expect_legacy_updater_migrated",
WithSystemScope(Wrap(&ExpectLegacyUpdaterMigrated))},
{"run_recovery_component",
WithSwitch("version", WithSwitch("app_id", WithSystemScope(Wrap(
&RunRecoveryComponent))))},
{"expect_last_checked", WithSystemScope(Wrap(&ExpectLastChecked))},
{"expect_last_started", WithSystemScope(Wrap(&ExpectLastStarted))},
{"run_offline_install",
WithSwitch("silent",
WithSwitch("legacy_install",
WithSystemScope(Wrap(&RunOfflineInstall))))},
{"dm_deregister_device", WithSystemScope(Wrap(&DMDeregisterDevice))},
{"dm_cleanup", WithSystemScope(Wrap(&DMCleanup))},
};
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
for (const auto& entry : commands) {
if (command_line->HasSwitch(entry.first)) {
base::ScopedAllowBlockingForTesting allow_blocking;
if (!entry.second.Run(base::BindOnce(&AppTestHelper::Shutdown, this))) {
Shutdown(kBadCommand);
}
return;
}
}
LOG(ERROR) << "No supported switch provided. Command: "
<< command_line->GetCommandLineString();
Shutdown(kUnknownSwitch);
}
scoped_refptr<App> MakeAppTestHelper() {
return base::MakeRefCounted<AppTestHelper>();
}
// Provides custom formatting for the unit test output.
class TersePrinter : public EmptyTestEventListener {
private:
// Called before any test activity starts.
void OnTestProgramStart(const UnitTest& /*unit_test*/) override {}
// Called after all test activities have ended.
void OnTestProgramEnd(const UnitTest& unit_test) override {
VLOG(0) << "Command " << (unit_test.Passed() ? "SUCCEEDED" : "FAILED")
<< ".";
}
// Called before a test starts.
void OnTestStart(const TestInfo& /*test_info*/) override {}
// Called after a failed assertion or a SUCCEED() invocation. Prints a
// backtrace showing the failure.
void OnTestPartResult(const TestPartResult& result) override {
if (!result.failed()) {
return;
}
logging::LogMessage(result.file_name(), result.line_number(),
logging::LOGGING_ERROR)
.stream()
<< "*** Failure" << std::endl
<< result.message();
}
// Called after a test ends.
void OnTestEnd(const TestInfo& /*test_info*/) override {}
};
int IntegrationTestsHelperMain(int argc, char** argv) {
base::PlatformThread::SetName("IntegrationTestsHelperMain");
base::CommandLine::Init(argc, argv);
// Use the ${ISOLATED_OUTDIR} as a log destination. `test_suite` must be
// defined before setting log items. The integration test helper always
// logs into the same file as the `updater_tests_system` because the programs
// are used together.
base::TestSuite test_suite(argc, argv);
updater::test::InitLoggingForUnitTest(
base::FilePath(FILE_PATH_LITERAL("updater_test_system.log")));
#if BUILDFLAG(IS_WIN)
auto scoped_com_initializer =
std::make_unique<base::win::ScopedCOMInitializer>(
base::win::ScopedCOMInitializer::kMTA);
// Failing to disable COM exception handling is a critical error.
CHECK(SUCCEEDED(DisableCOMExceptionHandling()))
<< "Failed to disable COM exception handling.";
#endif
chrome::RegisterPathProvider();
TestEventListeners& listeners = UnitTest::GetInstance()->listeners();
delete listeners.Release(listeners.default_result_printer());
listeners.Append(new TersePrinter);
return base::LaunchUnitTestsSerially(
argc, argv,
base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
}
// Do not disable this test when encountering integration tests failures.
// This is not a unit test. It just wraps the execution of an integration test
// command, which is typical a step of an integration test.
TEST(TestHelperCommandRunner, Run) {
base::test::TaskEnvironment environment;
ScopedIPCSupportWrapper ipc_support_;
ASSERT_EQ(MakeAppTestHelper()->Run(), 0);
}
} // namespace
} // namespace test
} // namespace updater
// Wraps the execution of one integration test command in a unit test. The test
// commands contain gtest assertions, therefore the invocation of test commands
// must occur within the scope of a unit test of a gtest program. The test
// helper defines a unit test "TestHelperCommandRunner.Run", which runs the
// actual test command. Returns 0 if the test command succeeded.
int main(int argc, char** argv) {
return updater::test::IntegrationTestsHelperMain(argc, argv);
}