blob: 4707b4dd75a361735c35d0de4778c20b45a9077b [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 "components/password_manager/core/browser/store_metrics_reporter.h"
#include <string>
#include "base/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "components/os_crypt/os_crypt_mocker.h"
#include "components/password_manager/core/browser/mock_password_reuse_manager.h"
#include "components/password_manager/core/browser/mock_password_store_interface.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/password_manager/core/browser/password_manager_features_util.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/stub_password_manager_client.h"
#include "components/password_manager/core/browser/sync_username_test_base.h"
#include "components/password_manager/core/browser/test_password_store.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::Bool;
using ::testing::Range;
using ::testing::Return;
namespace password_manager {
namespace {
PasswordForm CreateForm(const std::string& signon_realm,
const std::string& username,
const std::string& password) {
PasswordForm form;
form.signon_realm = signon_realm;
form.username_value = base::ASCIIToUTF16(username);
form.password_value = base::ASCIIToUTF16(password);
return form;
}
void AddMetricsTestData(TestPasswordStore* store) {
PasswordForm password_form;
password_form.url = GURL("http://example.com");
password_form.username_value = u"test1@gmail.com";
password_form.password_value = u"test";
password_form.signon_realm = "http://example.com/";
password_form.times_used = 0;
store->AddLogin(password_form);
password_form.username_value = u"test2@gmail.com";
password_form.times_used = 1;
store->AddLogin(password_form);
password_form.url = GURL("http://second.example.com");
password_form.signon_realm = "http://second.example.com";
password_form.times_used = 3;
store->AddLogin(password_form);
password_form.username_value = u"test3@gmail.com";
password_form.type = PasswordForm::Type::kGenerated;
password_form.times_used = 2;
store->AddLogin(password_form);
password_form.url = GURL("ftp://third.example.com/");
password_form.signon_realm = "ftp://third.example.com/";
password_form.times_used = 4;
password_form.scheme = PasswordForm::Scheme::kOther;
store->AddLogin(password_form);
password_form.url = GURL("http://fourth.example.com/");
password_form.signon_realm = "http://fourth.example.com/";
password_form.type = PasswordForm::Type::kFormSubmission;
password_form.username_value = u"";
password_form.times_used = 10;
password_form.scheme = PasswordForm::Scheme::kHtml;
store->AddLogin(password_form);
password_form.url = GURL("https://fifth.example.com/");
password_form.signon_realm = "https://fifth.example.com/";
password_form.username_value = u"";
password_form.password_value = u"";
password_form.blocked_by_user = true;
store->AddLogin(password_form);
password_form.url = GURL("https://sixth.example.com/");
password_form.signon_realm = "https://sixth.example.com/";
password_form.username_value = u"my_username";
password_form.password_value = u"my_password";
password_form.blocked_by_user = false;
store->AddLogin(password_form);
password_form.url = GURL();
password_form.signon_realm = "android://hash@com.example.android/";
password_form.username_value = u"JohnDoe";
password_form.password_value = u"my_password";
password_form.blocked_by_user = false;
store->AddLogin(password_form);
password_form.username_value = u"JaneDoe";
store->AddLogin(password_form);
password_form.url = GURL("http://rsolomakhin.github.io/autofill/");
password_form.signon_realm = "http://rsolomakhin.github.io/";
password_form.username_value = u"";
password_form.password_value = u"";
password_form.blocked_by_user = true;
store->AddLogin(password_form);
password_form.url = GURL("https://rsolomakhin.github.io/autofill/");
password_form.signon_realm = "https://rsolomakhin.github.io/";
password_form.blocked_by_user = true;
store->AddLogin(password_form);
password_form.url = GURL("http://rsolomakhin.github.io/autofill/123");
password_form.signon_realm = "http://rsolomakhin.github.io/";
password_form.blocked_by_user = true;
store->AddLogin(password_form);
password_form.url = GURL("https://rsolomakhin.github.io/autofill/1234");
password_form.signon_realm = "https://rsolomakhin.github.io/";
password_form.blocked_by_user = true;
store->AddLogin(password_form);
}
class StoreMetricsReporterTest : public SyncUsernameTestBase {
public:
StoreMetricsReporterTest() = default;
void SetUp() override {
// Mock OSCrypt. There is a call to OSCrypt inside HashPasswordManager so it
// should be mocked.
OSCryptMocker::SetUp();
feature_list_.InitWithFeatures(
{features::kPasswordReuseDetectionEnabled, features::kPasswordNotes},
{});
prefs_.registry()->RegisterBooleanPref(prefs::kCredentialsEnableService,
false);
prefs_.registry()->RegisterBooleanPref(
password_manager::prefs::kWasAutoSignInFirstRunExperienceShown, false);
prefs_.registry()->RegisterBooleanPref(prefs::kWereOldGoogleLoginsRemoved,
false);
prefs_.registry()->RegisterDoublePref(
prefs::kLastTimePasswordStoreMetricsReported, 0.0);
}
void TearDown() override { OSCryptMocker::TearDown(); }
~StoreMetricsReporterTest() override = default;
protected:
base::test::ScopedFeatureList feature_list_;
TestingPrefServiceSimple prefs_;
};
// The test fixture is used to test StoreIndependentMetrics. The parameter
// defines whether password manager is enabled.
class StoreMetricsReporterTestWithParams
: public StoreMetricsReporterTest,
public ::testing::WithParamInterface<bool> {};
// Test that store-independent metrics are reported correctly.
TEST_P(StoreMetricsReporterTestWithParams, StoreIndependentMetrics) {
const bool password_manager_enabled = GetParam();
prefs_.SetBoolean(password_manager::prefs::kCredentialsEnableService,
password_manager_enabled);
base::HistogramTester histogram_tester;
StoreMetricsReporter reporter(
/*profile_store=*/nullptr, /*account_store=*/nullptr, sync_service(),
identity_manager(), &prefs_, /*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false,
/*done_callback*/ base::DoNothing());
histogram_tester.ExpectUniqueSample("PasswordManager.Enabled3",
password_manager_enabled, 1);
}
INSTANTIATE_TEST_SUITE_P(All, StoreMetricsReporterTestWithParams, Bool());
TEST_F(StoreMetricsReporterTest, ReportMetricsAtMostOncePerDay) {
auto profile_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
profile_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
base::HistogramTester histogram_tester;
base::MockCallback<base::OnceClosure> done_callback;
StoreMetricsReporter reporter(
profile_store.get(), /*account_store=*/nullptr, sync_service(),
identity_manager(), &prefs_, /*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false, done_callback.Get());
histogram_tester.ExpectTotalCount("PasswordManager.Enabled3", 1);
EXPECT_CALL(done_callback, Run());
RunUntilIdle();
// Immediately try to report metrics again, no metrics should be reported
// since not enough time has passwed, but the done_callback should be invoked
// nevertheless.
base::HistogramTester histogram_tester2;
base::MockCallback<base::OnceClosure> done_callback2;
StoreMetricsReporter reporter2(
profile_store.get(), /*account_store=*/nullptr, sync_service(),
identity_manager(), &prefs_, /*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false, done_callback2.Get());
histogram_tester2.ExpectTotalCount("PasswordManager.Enabled3", 0);
EXPECT_CALL(done_callback2, Run());
RunUntilIdle();
profile_store->ShutdownOnUIThread();
// Make sure the PasswordStore destruction parts on the background sequence
// finish, otherwise we get memory leak reports.
RunUntilIdle();
}
TEST_F(StoreMetricsReporterTest, ReportAccountsPerSiteHiResMetricsTest) {
auto profile_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
profile_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
AddMetricsTestData(profile_store.get());
// Note: We also create and populate an account store here and instruct it to
// report metrics, even though all the checks below only test the profile DB.
// This is to make sure that the account DB doesn't write to any of the same
// histograms.
auto account_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(true));
account_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
AddMetricsTestData(account_store.get());
base::HistogramTester histogram_tester;
StoreMetricsReporter reporter(profile_store.get(), account_store.get(),
sync_service(), identity_manager(), &prefs_,
/*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false,
/*done_callback*/ base::DoNothing());
// Wait for the metrics to get reported, which involves queries to the
// stores, i.e. to background task runners.
RunUntilIdle();
histogram_tester.ExpectUniqueSample(
"PasswordManager.ProfileStore.AccountsPerSiteHiRes2."
"AutoGenerated."
"WithoutCustomPassphrase",
1, 2);
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.AccountsPerSiteHiRes2."
"UserCreated."
"WithoutCustomPassphrase",
1, 3);
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.AccountsPerSiteHiRes2."
"UserCreated."
"WithoutCustomPassphrase",
2, 2);
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.AccountsPerSiteHiRes2."
"Overall."
"WithoutCustomPassphrase",
1, 5);
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.AccountsPerSiteHiRes2."
"Overall."
"WithoutCustomPassphrase",
2, 2);
account_store->ShutdownOnUIThread();
profile_store->ShutdownOnUIThread();
// Make sure the PasswordStore destruction parts on the background sequence
// finish, otherwise we get memory leak reports.
RunUntilIdle();
}
TEST_F(StoreMetricsReporterTest, ReportTotalAccountsHiResMetricsTest) {
auto profile_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
profile_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
AddMetricsTestData(profile_store.get());
// Note: We also create and populate an account store here and instruct it to
// report metrics, even though all the checks below only test the profile DB.
// This is to make sure that the account DB doesn't write to any of the same
// histograms.
auto account_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(true));
account_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
AddMetricsTestData(account_store.get());
base::HistogramTester histogram_tester;
StoreMetricsReporter reporter(profile_store.get(), account_store.get(),
sync_service(), identity_manager(), &prefs_,
/*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false,
/*done_callback*/ base::DoNothing());
// Wait for the metrics to get reported, which involves queries to the
// stores, i.e. to background task runners.
RunUntilIdle();
histogram_tester.ExpectUniqueSample(
"PasswordManager.ProfileStore.TotalAccountsHiRes2."
"ByType."
"AutoGenerated."
"WithoutCustomPassphrase",
2, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.ProfileStore.TotalAccountsHiRes2."
"ByType."
"UserCreated."
"WithoutCustomPassphrase",
7, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.ProfileStore.TotalAccountsHiRes2."
"ByType.Overall."
"WithoutCustomPassphrase",
9, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.ProfileStore.TotalAccountsHiRes2."
"WithScheme."
"Android",
2, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.ProfileStore.TotalAccountsHiRes2."
"WithScheme.Ftp",
1, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.ProfileStore.TotalAccountsHiRes2."
"WithScheme.Http",
5, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.ProfileStore.TotalAccountsHiRes2."
"WithScheme.Https",
1, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.ProfileStore.TotalAccountsHiRes2."
"WithScheme.Other",
0, 1);
account_store->ShutdownOnUIThread();
profile_store->ShutdownOnUIThread();
// Make sure the PasswordStore destruction parts on the background sequence
// finish, otherwise we get memory leak reports.
RunUntilIdle();
}
TEST_F(StoreMetricsReporterTest, ReportTimesPasswordUsedMetricsTest) {
auto profile_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
profile_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
AddMetricsTestData(profile_store.get());
// Note: We also create and populate an account store here and instruct it to
// report metrics, even though all the checks below only test the profile DB.
// This is to make sure that the account DB doesn't write to any of the same
// histograms.
auto account_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(true));
account_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
AddMetricsTestData(account_store.get());
base::HistogramTester histogram_tester;
StoreMetricsReporter reporter(profile_store.get(), account_store.get(),
sync_service(), identity_manager(), &prefs_,
/*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false,
/*done_callback*/ base::DoNothing());
// Wait for the metrics to get reported, which involves queries to the
// stores, i.e. to background task runners.
RunUntilIdle();
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.TimesPasswordUsed2."
"AutoGenerated."
"WithoutCustomPassphrase",
2, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.TimesPasswordUsed2."
"AutoGenerated."
"WithoutCustomPassphrase",
4, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.TimesPasswordUsed2."
"UserCreated."
"WithoutCustomPassphrase",
0, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.TimesPasswordUsed2."
"UserCreated."
"WithoutCustomPassphrase",
1, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.TimesPasswordUsed2."
"UserCreated."
"WithoutCustomPassphrase",
3, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.TimesPasswordUsed2."
"Overall."
"WithoutCustomPassphrase",
0, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.TimesPasswordUsed2."
"Overall."
"WithoutCustomPassphrase",
1, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.TimesPasswordUsed2."
"Overall."
"WithoutCustomPassphrase",
2, 1);
// The bucket for 3 and 4 is the same. Thus we expect two samples here.
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.TimesPasswordUsed2."
"Overall."
"WithoutCustomPassphrase",
3, 2);
account_store->ShutdownOnUIThread();
profile_store->ShutdownOnUIThread();
// Make sure the PasswordStore destruction parts on the background sequence
// finish, otherwise we get memory leak reports.
RunUntilIdle();
}
// The following tests are mostly a copy of Report*MetricsTest, but covering
// the account store instead of the profile store. All the metrics that *are*
// covered have
// ".AccountStore" in their names.
TEST_F(StoreMetricsReporterTest,
ReportAccountStoreAccountsPerSiteHiResMetricsTest) {
// Note: We also populate the profile store here and instruct it to report
// metrics, even though all the checks below only test the account DB. This is
// to make sure that the profile DB doesn't write to any of the same
// histograms.
auto profile_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
profile_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
AddMetricsTestData(profile_store.get());
// Note: We also create and populate an account store here and instruct it to
// report metrics, even though all the checks below only test the profile DB.
// This is to make sure that the account DB doesn't write to any of the same
// histograms.
auto account_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(true));
account_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
AddMetricsTestData(account_store.get());
base::HistogramTester histogram_tester;
StoreMetricsReporter reporter(profile_store.get(), account_store.get(),
sync_service(), identity_manager(), &prefs_,
/*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false,
/*done_callback*/ base::DoNothing());
// Wait for the metrics to get reported, which involves queries to the
// stores, i.e. to background task runners.
RunUntilIdle();
histogram_tester.ExpectUniqueSample(
"PasswordManager.AccountStore.AccountsPerSiteHiRes2."
"AutoGenerated."
"WithoutCustomPassphrase",
1, 2);
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.AccountsPerSiteHiRes2."
"UserCreated."
"WithoutCustomPassphrase",
1, 3);
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.AccountsPerSiteHiRes2."
"UserCreated."
"WithoutCustomPassphrase",
2, 2);
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.AccountsPerSiteHiRes2."
"Overall."
"WithoutCustomPassphrase",
1, 5);
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.AccountsPerSiteHiRes2."
"Overall."
"WithoutCustomPassphrase",
2, 2);
account_store->ShutdownOnUIThread();
profile_store->ShutdownOnUIThread();
// Make sure the PasswordStore destruction parts on the background sequence
// finish, otherwise we get memory leak reports.
RunUntilIdle();
}
TEST_F(StoreMetricsReporterTest,
ReportAccountStoreTotalAccountsHiResMetricsTest) {
// Note: We also populate the profile store here and instruct it to report
// metrics, even though all the checks below only test the account DB. This is
// to make sure that the profile DB doesn't write to any of the same
// histograms.
auto profile_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
profile_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
AddMetricsTestData(profile_store.get());
// Note: We also create and populate an account store here and instruct it to
// report metrics, even though all the checks below only test the profile DB.
// This is to make sure that the account DB doesn't write to any of the same
// histograms.
auto account_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(true));
account_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
AddMetricsTestData(account_store.get());
base::HistogramTester histogram_tester;
StoreMetricsReporter reporter(profile_store.get(), account_store.get(),
sync_service(), identity_manager(), &prefs_,
/*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false,
/*done_callback*/ base::DoNothing());
// Wait for the metrics to get reported, which involves queries to the
// stores, i.e. to background task runners.
RunUntilIdle();
histogram_tester.ExpectUniqueSample(
"PasswordManager.AccountStore.TotalAccountsHiRes2."
"ByType.AutoGenerated."
"WithoutCustomPassphrase",
2, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.AccountStore.TotalAccountsHiRes2."
"ByType.UserCreated."
"WithoutCustomPassphrase",
7, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.AccountStore.TotalAccountsHiRes2."
"ByType.Overall."
"WithoutCustomPassphrase",
9, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.AccountStore.TotalAccountsHiRes2."
"WithScheme.Android",
2, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.AccountStore.TotalAccountsHiRes2."
"WithScheme.Ftp",
1, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.AccountStore.TotalAccountsHiRes2."
"WithScheme.Http",
5, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.AccountStore.TotalAccountsHiRes2."
"WithScheme.Https",
1, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.AccountStore.TotalAccountsHiRes2."
"WithScheme.Other",
0, 1);
account_store->ShutdownOnUIThread();
profile_store->ShutdownOnUIThread();
// Make sure the PasswordStore destruction parts on the background sequence
// finish, otherwise we get memory leak reports.
RunUntilIdle();
}
TEST_F(StoreMetricsReporterTest,
ReportAccountStoreTimesPasswordUsedMetricsTest) {
// Note: We also populate the profile store here and instruct it to report
// metrics, even though all the checks below only test the account DB. This is
// to make sure that the profile DB doesn't write to any of the same
// histograms.
auto profile_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
profile_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
AddMetricsTestData(profile_store.get());
// Note: We also create and populate an account store here and instruct it to
// report metrics, even though all the checks below only test the profile DB.
// This is to make sure that the account DB doesn't write to any of the same
// histograms.
auto account_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(true));
account_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
AddMetricsTestData(account_store.get());
base::HistogramTester histogram_tester;
StoreMetricsReporter reporter(profile_store.get(), account_store.get(),
sync_service(), identity_manager(), &prefs_,
/*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false,
/*done_callback*/ base::DoNothing());
// Wait for the metrics to get reported, which involves queries to the
// stores, i.e. to background task runners.
RunUntilIdle();
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.TimesPasswordUsed2."
"AutoGenerated."
"WithoutCustomPassphrase",
2, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.TimesPasswordUsed2."
"AutoGenerated."
"WithoutCustomPassphrase",
4, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.TimesPasswordUsed2."
"UserCreated."
"WithoutCustomPassphrase",
0, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.TimesPasswordUsed2."
"UserCreated."
"WithoutCustomPassphrase",
1, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.TimesPasswordUsed2."
"UserCreated."
"WithoutCustomPassphrase",
3, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.TimesPasswordUsed2."
"Overall."
"WithoutCustomPassphrase",
0, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.TimesPasswordUsed2."
"Overall."
"WithoutCustomPassphrase",
1, 1);
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.TimesPasswordUsed2."
"Overall."
"WithoutCustomPassphrase",
2, 1);
// The bucket for 3 and 4 is the same. Thus we expect two samples here.
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.TimesPasswordUsed2."
"Overall."
"WithoutCustomPassphrase",
3, 2);
account_store->ShutdownOnUIThread();
profile_store->ShutdownOnUIThread();
// Make sure the PasswordStore destruction parts on the background sequence
// finish, otherwise we get memory leak reports.
RunUntilIdle();
}
TEST_F(StoreMetricsReporterTest, DuplicatesMetrics_NoDuplicates) {
auto profile_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
profile_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
// No duplicate.
PasswordForm password_form;
password_form.signon_realm = "http://example1.com/";
password_form.url = GURL("http://example1.com/");
password_form.username_element = u"userelem_1";
password_form.username_value = u"username_1";
password_form.password_value = u"password_1";
profile_store->AddLogin(password_form);
// Different username -> no duplicate.
password_form.signon_realm = "http://example2.com/";
password_form.url = GURL("http://example2.com/");
password_form.username_value = u"username_1";
profile_store->AddLogin(password_form);
password_form.username_value = u"username_2";
profile_store->AddLogin(password_form);
// Blocklisted forms don't count as duplicates (neither against other
// blocklisted forms nor against actual saved credentials).
password_form.signon_realm = "http://example3.com/";
password_form.url = GURL("http://example3.com/");
password_form.username_value = u"username_1";
profile_store->AddLogin(password_form);
password_form.blocked_by_user = true;
password_form.username_value = u"";
password_form.password_value = u"";
profile_store->AddLogin(password_form);
base::HistogramTester histogram_tester;
StoreMetricsReporter reporter(profile_store.get(), /*account_store=*/nullptr,
sync_service(), identity_manager(), &prefs_,
/*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false,
/*done_callback*/ base::DoNothing());
// Wait for the metrics to get reported, which involves queries to the
// stores, i.e. to background task runners.
RunUntilIdle();
EXPECT_THAT(histogram_tester.GetAllSamples(
"PasswordManager.CredentialsWithDuplicates2"),
testing::ElementsAre(base::Bucket(0, 1)));
EXPECT_THAT(
histogram_tester.GetAllSamples("PasswordManager."
"CredentialsWithMismatchedDuplicates2"),
testing::ElementsAre(base::Bucket(0, 1)));
profile_store->ShutdownOnUIThread();
// Make sure the PasswordStore destruction parts on the background sequence
// finish, otherwise we get memory leak reports.
RunUntilIdle();
}
TEST_F(StoreMetricsReporterTest, DuplicatesMetrics_ExactDuplicates) {
auto profile_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
profile_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
// Add some PasswordForms that are "exact" duplicates (only the
// username_element is different, which doesn't matter).
PasswordForm password_form;
password_form.signon_realm = "http://example1.com/";
password_form.url = GURL("http://example1.com/");
password_form.username_element = u"userelem_1";
password_form.username_value = u"username_1";
profile_store->AddLogin(password_form);
password_form.username_element = u"userelem_2";
profile_store->AddLogin(password_form);
// The number of "identical" credentials doesn't matter; we count the *sets*
// of duplicates.
password_form.username_element = u"userelem_3";
profile_store->AddLogin(password_form);
// Similarly, origin doesn't make forms "different" either.
password_form.signon_realm = "http://example2.com/";
password_form.url = GURL("http://example2.com/path1");
profile_store->AddLogin(password_form);
password_form.url = GURL("http://example2.com/path2");
profile_store->AddLogin(password_form);
base::HistogramTester histogram_tester;
StoreMetricsReporter reporter(profile_store.get(), /*account_store=*/nullptr,
sync_service(), identity_manager(), &prefs_,
/*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false,
/*done_callback*/ base::DoNothing());
// Wait for the metrics to get reported, which involves queries to the
// stores, i.e. to background task runners.
RunUntilIdle();
// There should be 2 groups of "exact" duplicates.
EXPECT_THAT(histogram_tester.GetAllSamples(
"PasswordManager.CredentialsWithDuplicates2"),
testing::ElementsAre(base::Bucket(2, 1)));
EXPECT_THAT(
histogram_tester.GetAllSamples("PasswordManager."
"CredentialsWithMismatchedDuplicates2"),
testing::ElementsAre(base::Bucket(0, 1)));
profile_store->ShutdownOnUIThread();
// Make sure the PasswordStore destruction parts on the background sequence
// finish, otherwise we get memory leak reports.
RunUntilIdle();
}
TEST_F(StoreMetricsReporterTest, DuplicatesMetrics_MismatchedDuplicates) {
auto profile_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
profile_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
// Mismatched duplicates: Identical except for the password.
PasswordForm password_form;
password_form.signon_realm = "http://example1.com/";
password_form.url = GURL("http://example1.com/");
password_form.username_element = u"userelem_1";
password_form.username_value = u"username_1";
password_form.password_element = u"passelem_1";
password_form.password_value = u"password_1";
profile_store->AddLogin(password_form);
// Note: password_value is not part of the unique key, so we need to change
// some other value to be able to insert the duplicate into the DB.
password_form.password_element = u"passelem_2";
password_form.password_value = u"password_2";
profile_store->AddLogin(password_form);
// The number of "identical" credentials doesn't matter; we count the *sets*
// of duplicates.
password_form.password_element = u"passelem_3";
password_form.password_value = u"password_3";
profile_store->AddLogin(password_form);
base::HistogramTester histogram_tester;
StoreMetricsReporter reporter(profile_store.get(), /*account_store=*/nullptr,
sync_service(), identity_manager(), &prefs_,
/*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false,
/*done_callback*/ base::DoNothing());
// Wait for the metrics to get reported, which involves queries to the
// stores, i.e. to background task runners.
RunUntilIdle();
EXPECT_THAT(histogram_tester.GetAllSamples(
"PasswordManager.CredentialsWithDuplicates2"),
testing::ElementsAre(base::Bucket(0, 1)));
EXPECT_THAT(
histogram_tester.GetAllSamples("PasswordManager."
"CredentialsWithMismatchedDuplicates2"),
testing::ElementsAre(base::Bucket(1, 1)));
profile_store->ShutdownOnUIThread();
// Make sure the PasswordStore destruction parts on the background sequence
// finish, otherwise we get memory leak reports.
RunUntilIdle();
}
// A test that covers multi-store metrics, which are recorded by the
// StoreMetricsReporter directly.
TEST_F(StoreMetricsReporterTest, MultiStoreMetrics) {
// This test is only relevant when the passwords accounts store is enabled.
if (!base::FeatureList::IsEnabled(features::kEnablePasswordsAccountStorage))
return;
prefs_.registry()->RegisterDictionaryPref(
prefs::kAccountStoragePerAccountSettings);
auto profile_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
auto account_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(true));
profile_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
account_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
// Simulate account store active.
AccountInfo account_info;
account_info.email = "account@gmail.com";
account_info.gaia = "account";
test_sync_service()->SetAccountInfo(account_info);
test_sync_service()->SetHasSyncConsent(false);
const std::string kRealm1 = "https://example.com";
const std::string kRealm2 = "https://example2.com";
// Add test data to the profile store:
// - 3 credentials that don't exist in the account store
// - 1 credential that conflicts with the account store (exists there with the
// same username but different password)
// - 2 credentials with identical copies in the account store
// Note: In the implementation, the credentials are processed in alphabetical
// order of usernames. Choose usernames here so that some profile-store-only
// credentials end up at both the start and the end of the list, to make sure
// these cases are handled correctly.
profile_store->AddLogin(
CreateForm(kRealm1, "aprofileuser1", "aprofilepass1"));
profile_store->AddLogin(
CreateForm(kRealm1, "aprofileuser2", "aprofilepass2"));
profile_store->AddLogin(
CreateForm(kRealm1, "zprofileuser3", "zprofilepass3"));
profile_store->AddLogin(CreateForm(kRealm1, "conflictinguser", "localpass"));
profile_store->AddLogin(
CreateForm(kRealm1, "identicaluser1", "identicalpass1"));
profile_store->AddLogin(
CreateForm(kRealm1, "identicaluser2", "identicalpass2"));
// Add test data to the account store:
// - 2 credentials that don't exist in the account store
// - 1 credential that conflicts with the profile store (exists there with the
// same username but different password)
// - 2 credentials with identical copies in the profile store
account_store->AddLogin(CreateForm(kRealm1, "accountuser1", "accountpass1"));
account_store->AddLogin(
CreateForm(kRealm1, "zaccountuser2", "zaccountpass2"));
account_store->AddLogin(
CreateForm(kRealm1, "conflictinguser", "accountpass"));
account_store->AddLogin(
CreateForm(kRealm1, "identicaluser1", "identicalpass1"));
account_store->AddLogin(
CreateForm(kRealm1, "identicaluser2", "identicalpass2"));
// Finally, add one more identical credential to the profile store. However
// this one is on a different signon realm, so should be counted as just
// another (4th) credential that's missing in the account store.
profile_store->AddLogin(
CreateForm(kRealm2, "identicaluser1", "identicalpass1"));
for (bool opted_in : {false, true}) {
if (opted_in) {
features_util::OptInToAccountStorage(&prefs_, sync_service());
} else {
features_util::OptOutOfAccountStorageAndClearSettings(&prefs_,
sync_service());
}
// In every pass in the loop, StoreMetricsReporter uses the same pref
// service. Set the kLastTimePasswordStoreMetricsReported to make sure
// metrics will be reported in the second pass too.
prefs_.SetDouble(
password_manager::prefs::kLastTimePasswordStoreMetricsReported, 0.0);
base::HistogramTester histogram_tester;
StoreMetricsReporter reporter(profile_store.get(), account_store.get(),
sync_service(), identity_manager(), &prefs_,
/*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false,
/*done_callback*/ base::DoNothing());
// Wait for the metrics to get reported, which involves queries to the
// stores, i.e. to background task runners.
RunUntilIdle();
if (opted_in) {
histogram_tester.ExpectUniqueSample(
"PasswordManager.AccountStoreVsProfileStore3."
"Additional",
2, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.AccountStoreVsProfileStore3."
"Missing",
4, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.AccountStoreVsProfileStore3."
"Identical",
2, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.AccountStoreVsProfileStore3."
"Conflicting",
1, 1);
} else {
histogram_tester.ExpectTotalCount(
"PasswordManager.AccountStoreVsProfileStore3."
"Additional",
0);
histogram_tester.ExpectTotalCount(
"PasswordManager.AccountStoreVsProfileStore3."
"Missing",
0);
histogram_tester.ExpectTotalCount(
"PasswordManager.AccountStoreVsProfileStore3."
"Identical",
0);
histogram_tester.ExpectTotalCount(
"PasswordManager.AccountStoreVsProfileStore3."
"Conflicting",
0);
}
}
account_store->ShutdownOnUIThread();
profile_store->ShutdownOnUIThread();
// Make sure the PasswordStore destruction parts on the background sequence
// finish, otherwise we get memory leak reports.
RunUntilIdle();
}
TEST_F(StoreMetricsReporterTest, ReportMetricsForAdvancedProtection) {
prefs_.registry()->RegisterListPref(prefs::kPasswordHashDataList,
PrefRegistry::NO_REGISTRATION_FLAGS);
ASSERT_FALSE(prefs_.HasPrefPath(prefs::kSyncPasswordHash));
auto store = base::MakeRefCounted<MockPasswordStoreInterface>();
MockPasswordReuseManager reuse_manager;
const std::string username = "test@google.com";
SetSyncingPasswords(true);
FakeSigninAs(username);
base::HistogramTester histogram_tester;
EXPECT_CALL(reuse_manager, ReportMetrics(username, true));
StoreMetricsReporter reporter(/*profile_store=*/store.get(),
/*account_store=*/nullptr, sync_service(),
identity_manager(), &prefs_, &reuse_manager,
/*is_under_advanced_protection=*/true,
/*done_callback*/ base::DoNothing());
// Wait for the metrics to get reported, which involves queries to the stores,
// i.e. to background task runners.
RunUntilIdle();
}
TEST_F(StoreMetricsReporterTest, ReportPasswordNoteMetrics) {
auto profile_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
profile_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
PasswordForm password_form;
password_form.url = GURL("http://example.com");
password_form.username_value = u"test1@gmail.com";
password_form.notes = {PasswordNote(u"note", base::Time::Now())};
profile_store->AddLogin(password_form);
// ProfileStore - CountCredentialsWithNonEmptyNotes: 1
password_form.username_value = u"test2@gmail.com";
password_form.notes = {PasswordNote(u"another note", base::Time::Now()),
PasswordNote(std::u16string(), base::Time::Now())};
profile_store->AddLogin(password_form);
// ProfileStore - CountCredentialsWithNonEmptyNotes: 2
password_form.username_value = u"test3@gmail.com";
password_form.notes = {PasswordNote(std::u16string(), base::Time::Now()),
PasswordNote(u"some note", base::Time::Now())};
profile_store->AddLogin(password_form);
// ProfileStore - CountCredentialsWithNonEmptyNotes: 3
password_form.username_value = u"test4@gmail.com";
password_form.notes = {PasswordNote(std::u16string(), base::Time::Now())};
profile_store->AddLogin(password_form);
// ProfileStore - CountCredentialsWithNonEmptyNotes: 3
password_form.username_value = u"test5@gmail.com";
password_form.notes = {};
profile_store->AddLogin(password_form);
// ProfileStore - CountCredentialsWithNonEmptyNotes: 3
auto account_store =
base::MakeRefCounted<TestPasswordStore>(IsAccountStore(true));
account_store->Init(&prefs_, /*affiliated_match_helper=*/nullptr);
account_store->AddLogin(password_form);
// AccountStore - CountCredentialsWithNonEmptyNotes: 0
password_form.username_value = u"test6@gmail.com";
password_form.notes = {PasswordNote(std::u16string(), base::Time::Now())};
account_store->AddLogin(password_form);
// AccountStore - CountCredentialsWithNonEmptyNotes: 0
password_form.username_value = u"test7@gmail.com";
password_form.notes = {PasswordNote(u"note", base::Time::Now())};
account_store->AddLogin(password_form);
// AccountStore - CountCredentialsWithNonEmptyNotes: 1
base::HistogramTester histogram_tester;
StoreMetricsReporter reporter(profile_store.get(), account_store.get(),
sync_service(), identity_manager(), &prefs_,
/*password_reuse_manager=*/nullptr,
/*is_under_advanced_protection=*/false,
/*done_callback*/ base::DoNothing());
RunUntilIdle();
EXPECT_THAT(
histogram_tester.GetAllSamples(
"PasswordManager.ProfileStore.PasswordNotes.CountNotesPerCredential"),
BucketsAre(base::Bucket(0, 1), base::Bucket(1, 2), base::Bucket(2, 2)));
histogram_tester.ExpectBucketCount(
"PasswordManager.ProfileStore.PasswordNotes."
"CountCredentialsWithNonEmptyNotes",
3, 1);
EXPECT_THAT(
histogram_tester.GetAllSamples(
"PasswordManager.AccountStore.PasswordNotes.CountNotesPerCredential"),
BucketsAre(base::Bucket(0, 1), base::Bucket(1, 2)));
histogram_tester.ExpectBucketCount(
"PasswordManager.AccountStore.PasswordNotes."
"CountCredentialsWithNonEmptyNotes",
1, 1);
account_store->ShutdownOnUIThread();
profile_store->ShutdownOnUIThread();
// Make sure the PasswordStore destruction parts on the background sequence
// finish, otherwise we get memory leak reports.
RunUntilIdle();
}
} // namespace
} // namespace password_manager