blob: 3760172dfd72ae315b1e2fbc2946e01e7e0c03d9 [file] [log] [blame]
// Copyright 2020 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/ui/views/crostini/crostini_recovery_view.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_base.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chrome/browser/chromeos/crostini/crostini_test_helper.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/chromeos/guest_os/guest_os_registry_service.h"
#include "chrome/browser/chromeos/guest_os/guest_os_registry_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/views/crostini/crostini_browser_test_util.h"
#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_concierge_client.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
constexpr crostini::CrostiniUISurface kUiSurface =
crostini::CrostiniUISurface::kAppList;
constexpr char kDesktopFileId[] = "test_app";
constexpr int kDisplayId = 0;
namespace {
void ExpectFailure(const std::string& expected_failure_reason,
bool success,
const std::string& failure_reason) {
EXPECT_FALSE(success);
EXPECT_EQ(expected_failure_reason, failure_reason);
}
} // namespace
class CrostiniRecoveryViewBrowserTest : public CrostiniDialogBrowserTest {
public:
CrostiniRecoveryViewBrowserTest()
: CrostiniDialogBrowserTest(true /*register_termina*/),
app_id_(crostini::CrostiniTestHelper::GenerateAppId(kDesktopFileId)) {}
void SetUpOnMainThread() override {
CrostiniDialogBrowserTest::SetUpOnMainThread();
}
// DialogBrowserTest:
void ShowUi(const std::string& name) override {
ShowCrostiniRecoveryView(browser()->profile(), kUiSurface, app_id(),
kDisplayId, {}, base::DoNothing());
}
void SetUncleanStartup() {
crostini::CrostiniManager::GetForProfile(browser()->profile())
->SetUncleanStartupForTesting(true);
}
CrostiniRecoveryView* ActiveView() {
return CrostiniRecoveryView::GetActiveViewForTesting();
}
void WaitForViewDestroyed() {
base::RunLoop().RunUntilIdle();
ExpectNoView();
}
void ExpectView() {
// A new Widget was created in ShowUi() or since the last VerifyUi().
EXPECT_TRUE(VerifyUi());
// There is one view, and it's ours.
EXPECT_NE(nullptr, ActiveView());
EXPECT_EQ(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL,
ActiveView()->GetDialogButtons());
EXPECT_NE(ActiveView()->GetOkButton(), nullptr);
EXPECT_NE(ActiveView()->GetCancelButton(), nullptr);
EXPECT_TRUE(ActiveView()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK));
EXPECT_TRUE(ActiveView()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_CANCEL));
}
void ExpectNoView() {
// No new Widget was created in ShowUi() or since the last VerifyUi().
EXPECT_FALSE(VerifyUi());
// Our view has really been deleted.
EXPECT_EQ(nullptr, ActiveView());
}
bool IsUncleanStartup() {
return crostini::CrostiniManager::GetForProfile(browser()->profile())
->IsUncleanStartup();
}
void RegisterApp() {
vm_tools::apps::ApplicationList app_list =
crostini::CrostiniTestHelper::BasicAppList(
kDesktopFileId, crostini::kCrostiniDefaultVmName,
crostini::kCrostiniDefaultContainerName);
guest_os::GuestOsRegistryServiceFactory::GetForProfile(browser()->profile())
->UpdateApplicationList(app_list);
}
const std::string& app_id() const { return app_id_; }
private:
std::string app_id_;
DISALLOW_COPY_AND_ASSIGN(CrostiniRecoveryViewBrowserTest);
};
// Test the dialog is actually launched.
IN_PROC_BROWSER_TEST_F(CrostiniRecoveryViewBrowserTest, InvokeUi_default) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(CrostiniRecoveryViewBrowserTest, NoViewOnNormalStartup) {
base::HistogramTester histogram_tester;
RegisterApp();
crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId);
ExpectNoView();
histogram_tester.ExpectUniqueSample(
"Crostini.RecoverySource",
static_cast<base::HistogramBase::Sample>(kUiSurface), 0);
}
IN_PROC_BROWSER_TEST_F(CrostiniRecoveryViewBrowserTest, Cancel) {
base::HistogramTester histogram_tester;
SetUncleanStartup();
RegisterApp();
// Ensure Terminal System App is installed.
web_app::WebAppProvider::Get(browser()->profile())
->system_web_app_manager()
.InstallSystemAppsForTesting();
// First app should fail with 'cancelled for recovery'.
crostini::LaunchCrostiniApp(
browser()->profile(), app_id(), kDisplayId, {},
base::BindOnce(&ExpectFailure, "cancelled for recovery"));
ExpectView();
// Apps launched while dialog is shown should fail with 'recovery in
// progress'.
crostini::LaunchCrostiniApp(
browser()->profile(), app_id(), kDisplayId, {},
base::BindOnce(&ExpectFailure, "recovery in progress"));
// Click 'Cancel'.
ActiveView()->CancelDialog();
WaitForViewDestroyed();
// Terminal should launch after use clicks 'Cancel'.
Browser* terminal_browser = web_app::FindSystemWebAppBrowser(
browser()->profile(), web_app::SystemAppType::TERMINAL);
CHECK_NE(nullptr, terminal_browser);
WaitForLoadFinished(terminal_browser->tab_strip_model()->GetWebContentsAt(0));
// Any new apps launched should show the dialog again.
crostini::LaunchCrostiniApp(
browser()->profile(), app_id(), kDisplayId, {},
base::BindOnce(&ExpectFailure, "cancelled for recovery"));
ExpectView();
ActiveView()->CancelDialog();
WaitForViewDestroyed();
EXPECT_TRUE(IsUncleanStartup());
histogram_tester.ExpectUniqueSample(
"Crostini.RecoverySource",
static_cast<base::HistogramBase::Sample>(kUiSurface), 3);
}
IN_PROC_BROWSER_TEST_F(CrostiniRecoveryViewBrowserTest, Accept) {
base::HistogramTester histogram_tester;
SetUncleanStartup();
RegisterApp();
crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId);
ExpectView();
// Apps launched while dialog is shown should fail with 'recovery in
// progress'.
crostini::LaunchCrostiniApp(
browser()->profile(), app_id(), kDisplayId, {},
base::BindOnce(&ExpectFailure, "recovery in progress"));
// Click 'Accept'.
ActiveView()->AcceptDialog();
// Buttons should be disabled after clicking Accept.
EXPECT_FALSE(ActiveView()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK));
EXPECT_FALSE(ActiveView()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_CANCEL));
WaitForViewDestroyed();
EXPECT_FALSE(IsUncleanStartup());
// Apps now launch successfully.
crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId);
ExpectNoView();
histogram_tester.ExpectUniqueSample(
"Crostini.RecoverySource",
static_cast<base::HistogramBase::Sample>(kUiSurface), 2);
}