blob: b31581e7ce1c0f5d8a188337b8e3058f0c06c930 [file] [log] [blame]
// Copyright 2018 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/chromeos/crostini/crostini_registry_service.h"
#include <stddef.h>
#include "base/macros.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_clock.h"
#include "chrome/browser/chromeos/crostini/crostini_test_helper.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/dbus/vm_applications/apps.pb.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using vm_tools::apps::App;
using vm_tools::apps::ApplicationList;
constexpr char kCrostiniAppsInstalledHistogram[] =
"Crostini.AppsInstalledAtLogin";
namespace crostini {
class CrostiniRegistryServiceTest : public testing::Test {
public:
CrostiniRegistryServiceTest() = default;
// testing::Test:
void SetUp() override {
SetCrostiniUIAllowedForTesting(true);
CrostiniTestHelper::EnableCrostini(&profile_);
RecreateService();
}
void TearDown() override { SetCrostiniUIAllowedForTesting(false); }
protected:
void RecreateService() {
service_.reset(nullptr);
service_ = std::make_unique<CrostiniRegistryService>(&profile_);
service_->SetClockForTesting(&test_clock_);
}
class Observer : public CrostiniRegistryService::Observer {
public:
MOCK_METHOD4(OnRegistryUpdated,
void(CrostiniRegistryService*,
const std::vector<std::string>&,
const std::vector<std::string>&,
const std::vector<std::string>&));
};
std::string WindowIdForWMClass(const std::string& wm_class) {
return "org.chromium.termina.wmclass." + wm_class;
}
CrostiniRegistryService* service() { return service_.get(); }
protected:
base::SimpleTestClock test_clock_;
private:
content::TestBrowserThreadBundle thread_bundle_;
TestingProfile profile_;
std::unique_ptr<CrostiniRegistryService> service_;
DISALLOW_COPY_AND_ASSIGN(CrostiniRegistryServiceTest);
};
TEST_F(CrostiniRegistryServiceTest, SetAndGetRegistration) {
std::string desktop_file_id = "vim";
std::string vm_name = "awesomevm";
std::string container_name = "awesomecontainer";
std::map<std::string, std::string> name = {{"", "Vim"}};
std::map<std::string, std::string> comment = {{"", "Edit text files"}};
std::set<std::string> mime_types = {"text/plain", "text/x-python"};
bool no_display = true;
std::string app_id = CrostiniTestHelper::GenerateAppId(
desktop_file_id, vm_name, container_name);
EXPECT_FALSE(service()->GetRegistration(app_id).has_value());
ApplicationList app_list;
app_list.set_vm_name(vm_name);
app_list.set_container_name(container_name);
App* app = app_list.add_apps();
app->set_desktop_file_id(desktop_file_id);
app->set_no_display(no_display);
for (const auto& localized_name : name) {
App::LocaleString::Entry* entry = app->mutable_name()->add_values();
entry->set_locale(localized_name.first);
entry->set_value(localized_name.second);
}
for (const auto& localized_comment : comment) {
App::LocaleString::Entry* entry = app->mutable_comment()->add_values();
entry->set_locale(localized_comment.first);
entry->set_value(localized_comment.second);
}
for (const std::string& mime_type : mime_types)
app->add_mime_types(mime_type);
service()->UpdateApplicationList(app_list);
base::Optional<CrostiniRegistryService::Registration> result =
service()->GetRegistration(app_id);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->DesktopFileId(), desktop_file_id);
EXPECT_EQ(result->VmName(), vm_name);
EXPECT_EQ(result->ContainerName(), container_name);
EXPECT_EQ(result->Name(), name[""]);
EXPECT_EQ(result->Comment(), comment[""]);
EXPECT_EQ(result->MimeTypes(), mime_types);
EXPECT_EQ(result->NoDisplay(), no_display);
}
TEST_F(CrostiniRegistryServiceTest, Observer) {
ApplicationList app_list =
CrostiniTestHelper::BasicAppList("app 1", "vm", "container");
*app_list.add_apps() = CrostiniTestHelper::BasicApp("app 2");
*app_list.add_apps() = CrostiniTestHelper::BasicApp("app 3");
std::string app_id_1 =
CrostiniTestHelper::GenerateAppId("app 1", "vm", "container");
std::string app_id_2 =
CrostiniTestHelper::GenerateAppId("app 2", "vm", "container");
std::string app_id_3 =
CrostiniTestHelper::GenerateAppId("app 3", "vm", "container");
std::string app_id_4 =
CrostiniTestHelper::GenerateAppId("app 4", "vm", "container");
Observer observer;
service()->AddObserver(&observer);
EXPECT_CALL(observer,
OnRegistryUpdated(
service(), testing::IsEmpty(), testing::IsEmpty(),
testing::UnorderedElementsAre(app_id_1, app_id_2, app_id_3)));
service()->UpdateApplicationList(app_list);
// Rename desktop file for "app 2" to "app 4" (deletion+insertion)
app_list.mutable_apps(1)->set_desktop_file_id("app 4");
// Rename name for "app 3" to "banana"
app_list.mutable_apps(2)->mutable_name()->mutable_values(0)->set_value(
"banana");
EXPECT_CALL(observer,
OnRegistryUpdated(service(), testing::ElementsAre(app_id_3),
testing::ElementsAre(app_id_2),
testing::ElementsAre(app_id_4)));
service()->UpdateApplicationList(app_list);
}
TEST_F(CrostiniRegistryServiceTest, ZeroAppsInstalledHistogram) {
base::HistogramTester histogram_tester;
RecreateService();
// Check that there are no apps installed.
histogram_tester.ExpectTotalCount(kCrostiniAppsInstalledHistogram, 1);
histogram_tester.ExpectBucketCount(kCrostiniAppsInstalledHistogram, 0, 1);
}
TEST_F(CrostiniRegistryServiceTest, NAppsInstalledHistogram) {
base::HistogramTester histogram_tester;
// Set up an app list with the expected number of apps.
ApplicationList app_list =
CrostiniTestHelper::BasicAppList("app 0", "vm", "container");
*app_list.add_apps() = CrostiniTestHelper::BasicApp("app 1");
*app_list.add_apps() = CrostiniTestHelper::BasicApp("app 2");
*app_list.add_apps() = CrostiniTestHelper::BasicApp("app 3");
// Add apps that should not be counted.
App app4 = CrostiniTestHelper::BasicApp("no display app 4");
app4.set_no_display(true);
*app_list.add_apps() = app4;
App app5 = CrostiniTestHelper::BasicApp("no display app 5");
app5.set_no_display(true);
*app_list.add_apps() = app5;
// Force the registry to have a prefs entry for the Terminal.
service()->AppLaunched(kCrostiniTerminalId);
// Update the list of apps so that they can be counted.
service()->UpdateApplicationList(app_list);
RecreateService();
histogram_tester.ExpectTotalCount(kCrostiniAppsInstalledHistogram, 1);
histogram_tester.ExpectBucketCount(kCrostiniAppsInstalledHistogram, 4, 1);
}
TEST_F(CrostiniRegistryServiceTest, InstallAndLaunchTime) {
ApplicationList app_list =
CrostiniTestHelper::BasicAppList("app", "vm", "container");
std::string app_id =
CrostiniTestHelper::GenerateAppId("app", "vm", "container");
test_clock_.Advance(base::TimeDelta::FromHours(1));
Observer observer;
service()->AddObserver(&observer);
EXPECT_CALL(observer, OnRegistryUpdated(service(), testing::IsEmpty(),
testing::IsEmpty(),
testing::ElementsAre(app_id)));
service()->UpdateApplicationList(app_list);
base::Optional<CrostiniRegistryService::Registration> result =
service()->GetRegistration(app_id);
base::Time install_time = test_clock_.Now();
EXPECT_EQ(result->InstallTime(), install_time);
EXPECT_EQ(result->LastLaunchTime(), base::Time());
// UpdateApplicationList with nothing changed. Times shouldn't be updated and
// the observer shouldn't fire.
test_clock_.Advance(base::TimeDelta::FromHours(1));
EXPECT_CALL(observer, OnRegistryUpdated(_, _, _, _)).Times(0);
service()->UpdateApplicationList(app_list);
result = service()->GetRegistration(app_id);
EXPECT_EQ(result->InstallTime(), install_time);
EXPECT_EQ(result->LastLaunchTime(), base::Time());
// Launch the app
test_clock_.Advance(base::TimeDelta::FromHours(1));
service()->AppLaunched(app_id);
result = service()->GetRegistration(app_id);
EXPECT_EQ(result->InstallTime(), install_time);
EXPECT_EQ(result->LastLaunchTime(),
base::Time() + base::TimeDelta::FromHours(3));
// The install time shouldn't change if fields change.
test_clock_.Advance(base::TimeDelta::FromHours(1));
app_list.mutable_apps(0)->set_no_display(true);
EXPECT_CALL(observer,
OnRegistryUpdated(service(), testing::ElementsAre(app_id),
testing::IsEmpty(), testing::IsEmpty()));
service()->UpdateApplicationList(app_list);
result = service()->GetRegistration(app_id);
EXPECT_EQ(result->InstallTime(), install_time);
EXPECT_EQ(result->LastLaunchTime(),
base::Time() + base::TimeDelta::FromHours(3));
}
// Test that UpdateApplicationList doesn't clobber apps from different VMs or
// containers.
TEST_F(CrostiniRegistryServiceTest, MultipleContainers) {
service()->UpdateApplicationList(
CrostiniTestHelper::BasicAppList("app", "vm 1", "container 1"));
service()->UpdateApplicationList(
CrostiniTestHelper::BasicAppList("app", "vm 1", "container 2"));
service()->UpdateApplicationList(
CrostiniTestHelper::BasicAppList("app", "vm 2", "container 1"));
std::string app_id_1 =
CrostiniTestHelper::GenerateAppId("app", "vm 1", "container 1");
std::string app_id_2 =
CrostiniTestHelper::GenerateAppId("app", "vm 1", "container 2");
std::string app_id_3 =
CrostiniTestHelper::GenerateAppId("app", "vm 2", "container 1");
EXPECT_THAT(service()->GetRegisteredAppIds(),
testing::UnorderedElementsAre(app_id_1, app_id_2, app_id_3,
kCrostiniTerminalId));
// Clobber app_id_2
service()->UpdateApplicationList(
CrostiniTestHelper::BasicAppList("app 2", "vm 1", "container 2"));
std::string new_app_id =
CrostiniTestHelper::GenerateAppId("app 2", "vm 1", "container 2");
EXPECT_THAT(service()->GetRegisteredAppIds(),
testing::UnorderedElementsAre(app_id_1, app_id_3, new_app_id,
kCrostiniTerminalId));
}
// Test that ClearApplicationList works, and only removes apps from the
// specified container.
TEST_F(CrostiniRegistryServiceTest, ClearApplicationList) {
service()->UpdateApplicationList(
CrostiniTestHelper::BasicAppList("app", "vm 1", "container 1"));
service()->UpdateApplicationList(
CrostiniTestHelper::BasicAppList("app", "vm 1", "container 2"));
ApplicationList app_list =
CrostiniTestHelper::BasicAppList("app", "vm 2", "container 1");
*app_list.add_apps() = CrostiniTestHelper::BasicApp("app 2");
service()->UpdateApplicationList(app_list);
std::string app_id_1 =
CrostiniTestHelper::GenerateAppId("app", "vm 1", "container 1");
std::string app_id_2 =
CrostiniTestHelper::GenerateAppId("app", "vm 1", "container 2");
std::string app_id_3 =
CrostiniTestHelper::GenerateAppId("app", "vm 2", "container 1");
std::string app_id_4 =
CrostiniTestHelper::GenerateAppId("app 2", "vm 2", "container 1");
EXPECT_THAT(service()->GetRegisteredAppIds(),
testing::UnorderedElementsAre(app_id_1, app_id_2, app_id_3,
app_id_4, kCrostiniTerminalId));
service()->ClearApplicationList("vm 2", "container 1");
EXPECT_THAT(
service()->GetRegisteredAppIds(),
testing::UnorderedElementsAre(app_id_1, app_id_2, kCrostiniTerminalId));
}
TEST_F(CrostiniRegistryServiceTest, GetCrostiniAppIdNoStartupID) {
ApplicationList app_list =
CrostiniTestHelper::BasicAppList("app", "vm", "container");
*app_list.add_apps() = CrostiniTestHelper::BasicApp("cool.app");
*app_list.add_apps() = CrostiniTestHelper::BasicApp("super");
service()->UpdateApplicationList(app_list);
service()->UpdateApplicationList(
CrostiniTestHelper::BasicAppList("super", "vm 2", "container"));
EXPECT_THAT(service()->GetRegisteredAppIds(), testing::SizeIs(5));
EXPECT_TRUE(service()->GetCrostiniShelfAppId(nullptr, nullptr).empty());
std::string window_app_id = WindowIdForWMClass("App");
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
CrostiniTestHelper::GenerateAppId("app", "vm", "container"));
window_app_id = WindowIdForWMClass("cool.app");
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
CrostiniTestHelper::GenerateAppId("cool.app", "vm", "container"));
window_app_id = WindowIdForWMClass("super");
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
"crostini:" + WindowIdForWMClass("super"));
window_app_id = "org.chromium.termina.wmclientleader.1234";
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
"crostini:org.chromium.termina.wmclientleader.1234");
window_app_id = "org.chromium.termina.xid.654321";
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
"crostini:org.chromium.termina.xid.654321");
window_app_id = "cool.app";
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
CrostiniTestHelper::GenerateAppId("cool.app", "vm", "container"));
window_app_id = "fancy.app";
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
"crostini:fancy.app");
window_app_id = "org.chromium.arc.h";
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
std::string());
}
TEST_F(CrostiniRegistryServiceTest, GetCrostiniAppIdStartupWMClass) {
ApplicationList app_list =
CrostiniTestHelper::BasicAppList("app", "vm", "container");
app_list.mutable_apps(0)->set_startup_wm_class("app_start");
*app_list.add_apps() = CrostiniTestHelper::BasicApp("app2");
*app_list.add_apps() = CrostiniTestHelper::BasicApp("app3");
app_list.mutable_apps(1)->set_startup_wm_class("app2");
app_list.mutable_apps(2)->set_startup_wm_class("app2");
service()->UpdateApplicationList(app_list);
EXPECT_THAT(service()->GetRegisteredAppIds(), testing::SizeIs(4));
std::string window_app_id = WindowIdForWMClass("app_start");
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
CrostiniTestHelper::GenerateAppId("app", "vm", "container"));
window_app_id = WindowIdForWMClass("app2");
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
"crostini:" + WindowIdForWMClass("app2"));
}
TEST_F(CrostiniRegistryServiceTest, GetCrostiniAppIdStartupNotify) {
ApplicationList app_list =
CrostiniTestHelper::BasicAppList("app", "vm", "container");
app_list.mutable_apps(0)->set_startup_notify(true);
*app_list.add_apps() = CrostiniTestHelper::BasicApp("app2");
service()->UpdateApplicationList(app_list);
std::string window_app_id = "whatever";
std::string startup_id = "app";
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, &startup_id),
CrostiniTestHelper::GenerateAppId("app", "vm", "container"));
startup_id = "app2";
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, &startup_id),
"crostini:whatever");
startup_id = "app";
EXPECT_EQ(service()->GetCrostiniShelfAppId(nullptr, &startup_id),
CrostiniTestHelper::GenerateAppId("app", "vm", "container"));
}
TEST_F(CrostiniRegistryServiceTest, GetCrostiniAppIdName) {
ApplicationList app_list =
CrostiniTestHelper::BasicAppList("app", "vm", "container");
*app_list.add_apps() = CrostiniTestHelper::BasicApp("app2", "name2");
service()->UpdateApplicationList(app_list);
std::string window_app_id = WindowIdForWMClass("name2");
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
CrostiniTestHelper::GenerateAppId("app2", "vm", "container"));
}
TEST_F(CrostiniRegistryServiceTest, GetCrostiniAppIdNameSkipNoDisplay) {
ApplicationList app_list =
CrostiniTestHelper::BasicAppList("app", "vm", "container");
*app_list.add_apps() = CrostiniTestHelper::BasicApp("app2", "name2");
*app_list.add_apps() = CrostiniTestHelper::BasicApp("another_app2", "name2",
true /* no_display */);
service()->UpdateApplicationList(app_list);
std::string window_app_id = WindowIdForWMClass("name2");
EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
CrostiniTestHelper::GenerateAppId("app2", "vm", "container"));
}
TEST_F(CrostiniRegistryServiceTest, IsScaledReturnFalseWhenNotSet) {
std::string app_id =
CrostiniTestHelper::GenerateAppId("app", "vm", "container");
ApplicationList app_list =
CrostiniTestHelper::BasicAppList("app", "vm", "container");
service()->UpdateApplicationList(app_list);
base::Optional<CrostiniRegistryService::Registration> registration =
service()->GetRegistration(app_id);
EXPECT_TRUE(registration.has_value());
EXPECT_FALSE(registration.value().IsScaled());
}
TEST_F(CrostiniRegistryServiceTest, SetScaledWorks) {
std::string app_id =
CrostiniTestHelper::GenerateAppId("app", "vm", "container");
ApplicationList app_list =
CrostiniTestHelper::BasicAppList("app", "vm", "container");
service()->UpdateApplicationList(app_list);
service()->SetAppScaled(app_id, true);
base::Optional<CrostiniRegistryService::Registration> registration =
service()->GetRegistration(app_id);
EXPECT_TRUE(registration.has_value());
EXPECT_TRUE(registration.value().IsScaled());
service()->SetAppScaled(app_id, false);
registration = service()->GetRegistration(app_id);
EXPECT_TRUE(registration.has_value());
EXPECT_FALSE(registration.value().IsScaled());
}
} // namespace crostini