blob: 21f4a46f7731e550e3fc9be373b61eabbd1fd0a6 [file] [log] [blame]
// Copyright 2014 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/metrics/extensions_metrics_provider.h"
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <string>
#include "base/strings/string16.h"
#include "base/test/scoped_task_environment.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/metrics/client_info.h"
#include "components/metrics/metrics_service.h"
#include "components/metrics/metrics_state_manager.h"
#include "components/metrics/test_enabled_state_provider.h"
#include "components/prefs/testing_pref_service.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/value_builder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/extension_install.pb.h"
#include "third_party/metrics_proto/system_profile.pb.h"
using metrics::ExtensionInstallProto;
using extensions::Extension;
using extensions::ExtensionBuilder;
using extensions::Manifest;
using extensions::DictionaryBuilder;
namespace {
void StoreNoClientInfoBackup(const metrics::ClientInfo& /* client_info */) {
}
std::unique_ptr<metrics::ClientInfo> ReturnNoBackup() {
return std::unique_ptr<metrics::ClientInfo>();
}
class TestExtensionsMetricsProvider : public ExtensionsMetricsProvider {
public:
explicit TestExtensionsMetricsProvider(
metrics::MetricsStateManager* metrics_state_manager)
: ExtensionsMetricsProvider(metrics_state_manager) {}
// Makes the protected HashExtension method available to testing code.
using ExtensionsMetricsProvider::HashExtension;
protected:
// Override the GetInstalledExtensions method to return a set of extensions
// for tests.
std::unique_ptr<extensions::ExtensionSet> GetInstalledExtensions(
Profile* profile) override {
std::unique_ptr<extensions::ExtensionSet> extensions(
new extensions::ExtensionSet());
scoped_refptr<const extensions::Extension> extension;
extension = extensions::ExtensionBuilder()
.SetManifest(extensions::DictionaryBuilder()
.Set("name", "Test extension")
.Set("version", "1.0.0")
.Set("manifest_version", 2)
.Build())
.SetID("ahfgeienlihckogmohjhadlkjgocpleb")
.Build();
extensions->Insert(extension);
extension = extensions::ExtensionBuilder()
.SetManifest(extensions::DictionaryBuilder()
.Set("name", "Test extension 2")
.Set("version", "1.0.0")
.Set("manifest_version", 2)
.Build())
.SetID("pknkgggnfecklokoggaggchhaebkajji")
.Build();
extensions->Insert(extension);
extension = extensions::ExtensionBuilder()
.SetManifest(extensions::DictionaryBuilder()
.Set("name", "Colliding Extension")
.Set("version", "1.0.0")
.Set("manifest_version", 2)
.Build())
.SetID("mdhofdjgenpkhlmddfaegdjddcecipmo")
.Build();
extensions->Insert(extension);
return extensions;
}
// Override GetClientID() to return a specific value on which test
// expectations are based.
uint64_t GetClientID() override { return 0x3f1bfee9; }
};
} // namespace
// Checks that the hash function used to hide precise extension IDs produces
// the expected values.
TEST(ExtensionsMetricsProvider, HashExtension) {
EXPECT_EQ(978,
TestExtensionsMetricsProvider::HashExtension(
"ahfgeienlihckogmohjhadlkjgocpleb", 0));
EXPECT_EQ(10,
TestExtensionsMetricsProvider::HashExtension(
"ahfgeienlihckogmohjhadlkjgocpleb", 3817));
EXPECT_EQ(1007,
TestExtensionsMetricsProvider::HashExtension(
"pknkgggnfecklokoggaggchhaebkajji", 3817));
EXPECT_EQ(10,
TestExtensionsMetricsProvider::HashExtension(
"mdhofdjgenpkhlmddfaegdjddcecipmo", 3817));
}
// Checks that the fake set of extensions provided by
// TestExtensionsMetricsProvider is encoded properly.
TEST(ExtensionsMetricsProvider, SystemProtoEncoding) {
metrics::SystemProfileProto system_profile;
base::test::ScopedTaskEnvironment task_environment;
TestingProfileManager testing_profile_manager(
TestingBrowserProcess::GetGlobal());
ASSERT_TRUE(testing_profile_manager.SetUp());
TestingPrefServiceSimple local_state;
metrics::TestEnabledStateProvider enabled_state_provider(true, true);
metrics::MetricsService::RegisterPrefs(local_state.registry());
std::unique_ptr<metrics::MetricsStateManager> metrics_state_manager(
metrics::MetricsStateManager::Create(
&local_state, &enabled_state_provider, base::string16(),
base::Bind(&StoreNoClientInfoBackup), base::Bind(&ReturnNoBackup)));
TestExtensionsMetricsProvider extension_metrics(metrics_state_manager.get());
extension_metrics.ProvideSystemProfileMetrics(&system_profile);
ASSERT_EQ(2, system_profile.occupied_extension_bucket_size());
EXPECT_EQ(10, system_profile.occupied_extension_bucket(0));
EXPECT_EQ(1007, system_profile.occupied_extension_bucket(1));
}
class ExtensionMetricsProviderInstallsTest
: public extensions::ExtensionServiceTestBase {
public:
ExtensionMetricsProviderInstallsTest() {}
~ExtensionMetricsProviderInstallsTest() override {}
void SetUp() override {
ExtensionServiceTestBase::SetUp();
InitializeEmptyExtensionService();
prefs_ = extensions::ExtensionPrefs::Get(profile());
last_sample_time_ = base::Time::Now() - base::TimeDelta::FromMinutes(30);
}
ExtensionInstallProto ConstructProto(const Extension& extension) {
return ExtensionsMetricsProvider::ConstructInstallProtoForTesting(
extension, prefs_, last_sample_time_);
}
std::vector<ExtensionInstallProto> GetInstallsForProfile() {
return ExtensionsMetricsProvider::GetInstallsForProfileForTesting(
profile(), last_sample_time_);
}
extensions::ExtensionPrefs* prefs() { return prefs_; }
void set_last_sample_time(base::Time last_sample_time) {
last_sample_time_ = last_sample_time;
}
private:
extensions::ExtensionPrefs* prefs_ = nullptr;
base::Time last_sample_time_;
DISALLOW_COPY_AND_ASSIGN(ExtensionMetricsProviderInstallsTest);
};
// Tests the various aspects of constructing a relevant proto for a given
// extension installation.
TEST_F(ExtensionMetricsProviderInstallsTest, TestProtoConstruction) {
auto add_extension = [this](const Extension* extension) {
prefs()->OnExtensionInstalled(extension, Extension::ENABLED,
syncer::StringOrdinal(), std::string());
};
{
// Test basic prototype construction. All fields should be present, except
// disable reasons (which should be empty).
scoped_refptr<const Extension> extension =
ExtensionBuilder("test").SetLocation(Manifest::INTERNAL).Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_TRUE(install.has_type());
EXPECT_EQ(ExtensionInstallProto::EXTENSION, install.type());
EXPECT_TRUE(install.has_install_location());
EXPECT_EQ(ExtensionInstallProto::INTERNAL, install.install_location());
EXPECT_TRUE(install.has_manifest_version());
EXPECT_EQ(2, install.manifest_version());
EXPECT_TRUE(install.has_action_type());
EXPECT_EQ(ExtensionInstallProto::NO_ACTION, install.action_type());
EXPECT_TRUE(install.has_has_file_access());
EXPECT_FALSE(install.has_file_access());
EXPECT_TRUE(install.has_has_incognito_access());
EXPECT_FALSE(install.has_incognito_access());
EXPECT_TRUE(install.has_updates_from_store());
EXPECT_FALSE(install.updates_from_store());
EXPECT_TRUE(install.has_is_from_bookmark());
EXPECT_FALSE(install.is_from_bookmark());
EXPECT_TRUE(install.has_is_converted_from_user_script());
EXPECT_FALSE(install.is_converted_from_user_script());
EXPECT_TRUE(install.has_is_default_installed());
EXPECT_FALSE(install.is_default_installed());
EXPECT_TRUE(install.has_is_oem_installed());
EXPECT_FALSE(install.is_oem_installed());
EXPECT_TRUE(install.has_background_script_type());
EXPECT_EQ(ExtensionInstallProto::NO_BACKGROUND_SCRIPT,
install.background_script_type());
EXPECT_EQ(0, install.disable_reasons_size());
EXPECT_TRUE(install.has_blacklist_state());
EXPECT_EQ(ExtensionInstallProto::NOT_BLACKLISTED,
install.blacklist_state());
EXPECT_TRUE(install.has_installed_in_this_sample_period());
EXPECT_TRUE(install.installed_in_this_sample_period());
}
// It's not helpful to exhaustively test each possible variation of each
// field in the proto (since in many cases the test code would then be
// re-writing the original code), but we test a few of the more interesting
// cases.
{
// Test the type() field; extensions of different types should be reported
// as such.
scoped_refptr<const Extension> extension =
ExtensionBuilder("app", ExtensionBuilder::Type::PLATFORM_APP)
.SetLocation(Manifest::INTERNAL)
.Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::PLATFORM_APP, install.type());
}
{
// Test the install location.
scoped_refptr<const Extension> extension =
ExtensionBuilder("unpacked").SetLocation(Manifest::UNPACKED).Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::UNPACKED, install.install_location());
}
{
// Test the extension action as a browser action.
scoped_refptr<const Extension> extension =
ExtensionBuilder("browser_action")
.SetLocation(Manifest::INTERNAL)
.SetAction(ExtensionBuilder::ActionType::BROWSER_ACTION)
.Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::BROWSER_ACTION, install.action_type());
}
{
// Test the extension action as a page action.
scoped_refptr<const Extension> extension =
ExtensionBuilder("page_action")
.SetLocation(Manifest::INTERNAL)
.SetAction(ExtensionBuilder::ActionType::PAGE_ACTION)
.Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::PAGE_ACTION, install.action_type());
}
{
// Test the disable reasons field.
scoped_refptr<const Extension> extension =
ExtensionBuilder("disable_reasons")
.SetLocation(Manifest::INTERNAL)
.Build();
add_extension(extension.get());
prefs()->SetExtensionDisabled(
extension->id(), extensions::disable_reason::DISABLE_USER_ACTION);
{
ExtensionInstallProto install = ConstructProto(*extension);
ASSERT_EQ(1, install.disable_reasons_size());
EXPECT_EQ(ExtensionInstallProto::USER_ACTION,
install.disable_reasons().Get(0));
}
// Adding additional disable reasons should result in all reasons being
// reported.
prefs()->AddDisableReason(extension->id(),
extensions::disable_reason::DISABLE_CORRUPTED);
{
ExtensionInstallProto install = ConstructProto(*extension);
ASSERT_EQ(2, install.disable_reasons_size());
EXPECT_EQ(ExtensionInstallProto::USER_ACTION,
install.disable_reasons().Get(0));
EXPECT_EQ(ExtensionInstallProto::CORRUPTED,
install.disable_reasons().Get(1));
}
}
{
// Test that event pages are reported correctly.
DictionaryBuilder background;
background.SetBoolean("persistent", false)
.Set("scripts", extensions::ListBuilder().Append("script.js").Build());
scoped_refptr<const Extension> extension =
ExtensionBuilder("event_page")
.SetLocation(Manifest::INTERNAL)
.MergeManifest(DictionaryBuilder()
.Set("background", background.Build())
.Build())
.Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::EVENT_PAGE,
install.background_script_type());
}
{
// Test that persistent background pages are reported correctly.
DictionaryBuilder background;
background.SetBoolean("persistent", true)
.Set("scripts", extensions::ListBuilder().Append("script.js").Build());
scoped_refptr<const Extension> extension =
ExtensionBuilder("persisent_background")
.SetLocation(Manifest::INTERNAL)
.MergeManifest(DictionaryBuilder()
.Set("background", background.Build())
.Build())
.Build();
add_extension(extension.get());
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::PERSISTENT_BACKGROUND_PAGE,
install.background_script_type());
}
{
// Test changing the blacklist state.
scoped_refptr<const Extension> extension =
ExtensionBuilder("blacklist").SetLocation(Manifest::INTERNAL).Build();
add_extension(extension.get());
prefs()->SetExtensionBlacklistState(
extension->id(), extensions::BLACKLISTED_SECURITY_VULNERABILITY);
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_EQ(ExtensionInstallProto::BLACKLISTED_SECURITY_VULNERABILITY,
install.blacklist_state());
}
{
// Test that the installed_in_this_sample_period boolean is correctly
// reported.
scoped_refptr<const Extension> extension =
ExtensionBuilder("installtime").SetLocation(Manifest::INTERNAL).Build();
add_extension(extension.get());
set_last_sample_time(base::Time::Now() + base::TimeDelta::FromMinutes(60));
ExtensionInstallProto install = ConstructProto(*extension);
EXPECT_FALSE(install.installed_in_this_sample_period());
}
}
// Tests that we retrieve all extensions associated with a given profile.
TEST_F(ExtensionMetricsProviderInstallsTest,
TestGettingAllExtensionsInProfile) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("extension").Build();
service()->AddExtension(extension.get());
scoped_refptr<const Extension> app =
ExtensionBuilder("app", ExtensionBuilder::Type::PLATFORM_APP).Build();
service()->AddExtension(app.get());
service()->DisableExtension(app->id(),
extensions::disable_reason::DISABLE_USER_ACTION);
std::vector<ExtensionInstallProto> installs = GetInstallsForProfile();
// There should be two installs total.
ASSERT_EQ(2u, installs.size());
// One should be the extension, and the other should be the app. We don't
// check the specifics of the proto, since that's tested above.
EXPECT_TRUE(std::any_of(installs.begin(), installs.end(),
[](const ExtensionInstallProto& install) {
return install.type() ==
ExtensionInstallProto::EXTENSION;
}));
EXPECT_TRUE(std::any_of(installs.begin(), installs.end(),
[](const ExtensionInstallProto& install) {
return install.type() ==
ExtensionInstallProto::PLATFORM_APP;
}));
}