blob: eed16011403a389902c2b3acc9f25c277021934a [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/crostini/crostini_installer.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "chrome/browser/ash/crostini/ansible/ansible_management_test_helper.h"
#include "chrome/browser/ash/crostini/crostini_installer_ui_delegate.h"
#include "chrome/browser/ash/crostini/crostini_test_helper.h"
#include "chrome/browser/ash/crostini/crostini_types.mojom.h"
#include "chrome/browser/component_updater/fake_cros_component_manager.h"
#include "chrome/browser/notifications/system_notification_helper.h"
#include "chrome/test/base/browser_process_platform_part_test_api_chromeos.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/dbus/cicerone/cicerone_client.h"
#include "chromeos/dbus/cicerone/fake_cicerone_client.h"
#include "chromeos/dbus/concierge/concierge_client.h"
#include "chromeos/dbus/concierge/concierge_service.pb.h"
#include "chromeos/dbus/concierge/fake_concierge_client.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/dlcservice/dlcservice_client.h"
#include "chromeos/dbus/seneschal/seneschal_client.h"
#include "chromeos/disks/disk_mount_manager.h"
#include "chromeos/disks/mock_disk_mount_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
using crostini::mojom::InstallerError;
using crostini::mojom::InstallerState;
using testing::_;
using testing::AnyNumber;
using testing::Expectation;
using testing::ExpectationSet;
using testing::Invoke;
using testing::Le;
using testing::MockFunction;
using testing::SaveArg;
using testing::Truly;
namespace crostini {
class CrostiniInstallerTest : public testing::Test {
public:
using ResultCallback = CrostiniInstallerUIDelegate::ResultCallback;
using ProgressCallback = CrostiniInstallerUIDelegate::ProgressCallback;
class MockCallbacks {
public:
MOCK_METHOD2(OnProgress,
void(InstallerState state, double progress_fraction));
MOCK_METHOD1(OnFinished, void(InstallerError error));
MOCK_METHOD0(OnCanceled, void());
};
class WaitingFakeConciergeClient : public chromeos::FakeConciergeClient {
public:
explicit WaitingFakeConciergeClient(chromeos::FakeCiceroneClient* client)
: chromeos::FakeConciergeClient(client) {}
void StartTerminaVm(
const vm_tools::concierge::StartVmRequest& request,
chromeos::DBusMethodCallback<vm_tools::concierge::StartVmResponse>
callback) override {
chromeos::FakeConciergeClient::StartTerminaVm(request,
std::move(callback));
if (quit_closure_) {
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
std::move(quit_closure_));
}
}
void WaitForStartTerminaVmCalled() {
base::RunLoop loop;
quit_closure_ = loop.QuitClosure();
loop.Run();
EXPECT_GE(start_termina_vm_call_count(), 1);
}
private:
base::OnceClosure quit_closure_;
};
CrostiniInstallerTest()
: local_state_(std::make_unique<ScopedTestingLocalState>(
TestingBrowserProcess::GetGlobal())),
browser_part_(g_browser_process->platform_part()) {}
void SetUp() override {
component_manager_ =
base::MakeRefCounted<component_updater::FakeCrOSComponentManager>();
component_manager_->set_supported_components({"cros-termina"});
component_manager_->ResetComponentState(
"cros-termina",
component_updater::FakeCrOSComponentManager::ComponentInfo(
component_updater::CrOSComponentManager::Error::NONE,
base::FilePath("/install/path"), base::FilePath("/mount/path")));
browser_part_.InitializeCrosComponentManager(component_manager_);
chromeos::DlcserviceClient::InitializeFake();
chromeos::DBusThreadManager::Initialize();
chromeos::CiceroneClient::InitializeFake();
waiting_fake_concierge_client_ =
new WaitingFakeConciergeClient(chromeos::FakeCiceroneClient::Get());
chromeos::SeneschalClient::InitializeFake();
disk_mount_manager_mock_ = new chromeos::disks::MockDiskMountManager;
chromeos::disks::DiskMountManager::InitializeForTesting(
disk_mount_manager_mock_);
profile_ = std::make_unique<TestingProfile>();
// Needed at least for passing IsCrostiniUIAllowedForProfile() test in
// CrostiniManager.
crostini_test_helper_ =
std::make_unique<CrostiniTestHelper>(profile_.get());
crostini_installer_ = std::make_unique<CrostiniInstaller>(profile_.get());
crostini_installer_->set_skip_launching_terminal_for_testing();
g_browser_process->platform_part()
->InitializeSchedulerConfigurationManager();
TestingBrowserProcess::GetGlobal()->SetSystemNotificationHelper(
std::make_unique<SystemNotificationHelper>());
}
void TearDown() override {
g_browser_process->platform_part()->ShutdownSchedulerConfigurationManager();
crostini_installer_->Shutdown();
crostini_installer_.reset();
crostini_test_helper_.reset();
profile_.reset();
chromeos::disks::MockDiskMountManager::Shutdown();
chromeos::SeneschalClient::Shutdown();
chromeos::ConciergeClient::Shutdown();
chromeos::CiceroneClient::Shutdown();
chromeos::DBusThreadManager::Shutdown();
chromeos::DlcserviceClient::Shutdown();
browser_part_.ShutdownCrosComponentManager();
component_manager_.reset();
}
void Install() {
CrostiniManager::GetForProfile(profile_.get())
->SetCrostiniDialogStatus(DialogType::INSTALLER, true);
crostini_installer_->Install(
CrostiniManager::RestartOptions{},
base::BindRepeating(&MockCallbacks::OnProgress,
base::Unretained(&mock_callbacks_)),
base::BindOnce(&MockCallbacks::OnFinished,
base::Unretained(&mock_callbacks_)));
}
void Cancel() {
crostini_installer_->Cancel(base::BindOnce(
&MockCallbacks::OnCanceled, base::Unretained(&mock_callbacks_)));
}
protected:
MockCallbacks mock_callbacks_;
content::BrowserTaskEnvironment task_environment_{
content::BrowserTaskEnvironment::REAL_IO_THREAD,
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
base::HistogramTester histogram_tester_;
// Owned by DiskMountManager
chromeos::disks::MockDiskMountManager* disk_mount_manager_mock_ = nullptr;
WaitingFakeConciergeClient* waiting_fake_concierge_client_ = nullptr;
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<CrostiniTestHelper> crostini_test_helper_;
std::unique_ptr<CrostiniInstaller> crostini_installer_;
private:
std::unique_ptr<ScopedTestingLocalState> local_state_;
scoped_refptr<component_updater::FakeCrOSComponentManager> component_manager_;
BrowserProcessPlatformPartTestApi browser_part_;
DISALLOW_COPY_AND_ASSIGN(CrostiniInstallerTest);
};
TEST_F(CrostiniInstallerTest, InstallFlow) {
double last_progress = 0.0;
auto greater_equal_last_progress = Truly(
[&last_progress](double progress) { return progress >= last_progress; });
ExpectationSet expectation_set;
expectation_set +=
EXPECT_CALL(mock_callbacks_,
OnProgress(_, AllOf(greater_equal_last_progress, Le(1.0))))
.WillRepeatedly(SaveArg<1>(&last_progress));
// |OnProgress()| should not happens after |OnFinished()|
EXPECT_CALL(mock_callbacks_, OnFinished(InstallerError::kNone))
.After(expectation_set);
Install();
task_environment_.RunUntilIdle();
histogram_tester_.ExpectUniqueSample(
"Crostini.SetupResult",
static_cast<base::HistogramBase::Sample>(
CrostiniInstaller::SetupResult::kSuccess),
1);
histogram_tester_.ExpectTotalCount("Crostini.Setup.Started", 1);
histogram_tester_.ExpectTotalCount("Crostini.Restarter.Started", 0);
EXPECT_TRUE(crostini_installer_->CanInstall())
<< "Installer should recover to installable state";
}
TEST_F(CrostiniInstallerTest, InstallFlowWithAnsibleInfra) {
AnsibleManagementTestHelper test_helper(profile_.get());
test_helper.SetUpAnsibleInfra();
double last_progress = 0.0;
auto greater_equal_last_progress = Truly(
[&last_progress](double progress) { return progress >= last_progress; });
ExpectationSet expectation_set;
expectation_set +=
EXPECT_CALL(mock_callbacks_,
OnProgress(_, AllOf(greater_equal_last_progress, Le(1.0))))
.WillRepeatedly(SaveArg<1>(&last_progress));
// |OnProgress()| should not happens after |OnFinished()|
EXPECT_CALL(mock_callbacks_, OnFinished(InstallerError::kNone))
.After(expectation_set);
Install();
task_environment_.RunUntilIdle();
test_helper.SendSucceededApplySignal();
task_environment_.RunUntilIdle();
histogram_tester_.ExpectUniqueSample(
"Crostini.SetupResult",
static_cast<base::HistogramBase::Sample>(
CrostiniInstaller::SetupResult::kSuccess),
1);
EXPECT_TRUE(crostini_installer_->CanInstall())
<< "Installer should recover to installable state";
}
TEST_F(CrostiniInstallerTest, CancelBeforeStart) {
crostini_installer_->CancelBeforeStart();
histogram_tester_.ExpectUniqueSample(
"Crostini.SetupResult",
static_cast<base::HistogramBase::Sample>(
CrostiniInstaller::SetupResult::kNotStarted),
1);
}
TEST_F(CrostiniInstallerTest, CancelAfterStart) {
MockFunction<void(std::string check_point_name)> check;
Expectation expect_progresses =
EXPECT_CALL(mock_callbacks_, OnProgress(_, _)).Times(AnyNumber());
// |OnProgress()| should not happen after |Cancel()| is called.
Expectation expect_calling_cancel =
EXPECT_CALL(check, Call("calling Cancel()")).After(expect_progresses);
EXPECT_CALL(mock_callbacks_, OnCanceled()).After(expect_calling_cancel);
Install();
// It is too involved to also fake the cleanup process in CrostiniManager, so
// we will just skip it.
crostini_installer_->set_require_cleanup_for_testing(false);
// Hang the installer flow waiting for Tremplin to start, so we get a chance
// to cancel it.
waiting_fake_concierge_client_->set_send_tremplin_started_signal_delay(
base::TimeDelta::FromDays(1));
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
check.Call("calling Cancel()");
Cancel();
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
histogram_tester_.ExpectUniqueSample(
"Crostini.SetupResult",
static_cast<base::HistogramBase::Sample>(
CrostiniInstaller::SetupResult::kUserCancelledStartTerminaVm),
1);
EXPECT_TRUE(crostini_installer_->CanInstall())
<< "Installer should recover to installable state";
}
// Cancel before |OnAvailableDiskSpace()| happens is a special case, in which we
// haven't started the restarting process yet.
TEST_F(CrostiniInstallerTest, CancelAfterStartBeforeCheckDisk) {
EXPECT_CALL(mock_callbacks_, OnCanceled());
crostini_installer_->Install(CrostiniManager::RestartOptions{},
base::DoNothing(), base::DoNothing());
Cancel(); // Cancel immediately
task_environment_.RunUntilIdle();
histogram_tester_.ExpectUniqueSample(
"Crostini.SetupResult",
static_cast<base::HistogramBase::Sample>(
CrostiniInstaller::SetupResult::kNotStarted),
1);
EXPECT_TRUE(crostini_installer_->CanInstall())
<< "Installer should recover to installable state";
}
TEST_F(CrostiniInstallerTest, InstallerError) {
Expectation expect_progresses =
EXPECT_CALL(mock_callbacks_, OnProgress(_, _)).Times(AnyNumber());
// |OnProgress()| should not happens after |OnFinished()|
EXPECT_CALL(mock_callbacks_,
OnFinished(InstallerError::kErrorStartingTermina))
.After(expect_progresses);
// Fake a failure for starting vm.
vm_tools::concierge::StartVmResponse response;
response.set_status(vm_tools::concierge::VM_STATUS_FAILURE);
waiting_fake_concierge_client_->set_start_vm_response(std::move(response));
Install();
waiting_fake_concierge_client_->WaitForStartTerminaVmCalled();
histogram_tester_.ExpectUniqueSample(
"Crostini.SetupResult",
static_cast<base::HistogramBase::Sample>(
CrostiniInstaller::SetupResult::kErrorStartingTermina),
1);
histogram_tester_.ExpectTotalCount("Crostini.Setup.Started", 1);
histogram_tester_.ExpectTotalCount("Crostini.Restarter.Started", 0);
EXPECT_TRUE(crostini_installer_->CanInstall())
<< "Installer should recover to installable state";
}
TEST_F(CrostiniInstallerTest, InstallerErrorWhileConfiguring) {
AnsibleManagementTestHelper test_helper(profile_.get());
test_helper.SetUpAnsibleInfra();
test_helper.SetUpAnsibleInstallation(
vm_tools::cicerone::InstallLinuxPackageResponse::FAILED);
Expectation expect_progresses =
EXPECT_CALL(mock_callbacks_, OnProgress(_, _)).Times(AnyNumber());
// |OnProgress()| should not happens after |OnFinished()|
EXPECT_CALL(mock_callbacks_,
OnFinished(InstallerError::kErrorConfiguringContainer))
.After(expect_progresses);
Install();
task_environment_.RunUntilIdle();
histogram_tester_.ExpectUniqueSample(
"Crostini.SetupResult",
static_cast<base::HistogramBase::Sample>(
CrostiniInstaller::SetupResult::kErrorConfiguringContainer),
1);
EXPECT_TRUE(crostini_installer_->CanInstall())
<< "Installer should recover to installable state";
}
} // namespace crostini