blob: 501e2fcabd88b044f096c6668969c5f788780be3 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/metrics/feature_discovery_duration_reporter_impl.h"
#include "ash/public/cpp/ash_prefs.h"
#include "ash/public/cpp/feature_discovery_metric_util.h"
#include "ash/public/cpp/tablet_mode.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/containers/contains.h"
#include "base/test/metrics/histogram_tester.h"
namespace ash {
namespace {
// A mock primary user's email.
constexpr char kPrimaryUserEmail[] = "user1@example.com";
// A mock secondary user's email.
constexpr char kSecondaryUserEmail[] = "user2@example.com";
// The mock features' histograms.
constexpr char kMockHistogram[] = "FeatureDiscoveryTestMockFeature";
const char kMockFeatureClamshellHistogram[] =
"FeatureDiscoveryTestMockFeature.clamshell";
const char kMockFeatureTabletHistogram[] =
"FeatureDiscoveryTestMockFeature.tablet";
SessionControllerImpl* GetSessionController() {
return Shell::Get()->session_controller();
}
FeatureDiscoveryDurationReporterImpl* GetFeatureDiscoveryDurationReporter() {
return Shell::Get()->feature_discover_reporter();
}
} // namespace
class FeatureDiscoveryDurationReporterImplTest : public AshTestBase {
public:
FeatureDiscoveryDurationReporterImplTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
FeatureDiscoveryDurationReporterImplTest(
const FeatureDiscoveryDurationReporterImplTest&) = delete;
FeatureDiscoveryDurationReporterImplTest& operator=(
const FeatureDiscoveryDurationReporterImplTest&) = delete;
~FeatureDiscoveryDurationReporterImplTest() override = default;
bool IsReporterActive() {
return GetFeatureDiscoveryDurationReporter()->is_active();
}
// Returns true if the feature discovery reporter has ongoing observations.
bool IsMockFeatureUnderActiveObservation() {
const auto& active_time_recordings =
GetFeatureDiscoveryDurationReporter()->active_time_recordings_;
return base::Contains(active_time_recordings,
feature_discovery::TrackableFeature::kMockFeature);
}
// AshTestBase:
void SetUp() override {
AshTestBase::SetUp();
// Set up the primary account and the secondary account.
GetSessionController()->ClearUserSessionsForTest();
TestSessionControllerClient* session_client = GetSessionControllerClient();
session_client->AddUserSession(kPrimaryUserEmail,
user_manager::UserType::kRegular,
/*provide_pref_service=*/false);
session_client->AddUserSession(kSecondaryUserEmail,
user_manager::UserType::kRegular,
/*provide_pref_service=*/false);
auto user_1_prefs = std::make_unique<TestingPrefServiceSimple>();
RegisterUserProfilePrefs(user_1_prefs->registry(), /*country=*/"",
/*for_test=*/true);
auto user_2_prefs = std::make_unique<TestingPrefServiceSimple>();
RegisterUserProfilePrefs(user_2_prefs->registry(), /*country=*/"",
/*for_test=*/true);
session_client->SetUserPrefService(primary_account_id_,
std::move(user_1_prefs));
session_client->SetUserPrefService(secondary_account_id_,
std::move(user_2_prefs));
// Switch to the primary account and lock the screen.
session_client->SwitchActiveUser(primary_account_id_);
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOCKED);
}
AccountId primary_account_id_ = AccountId::FromUserEmail(kPrimaryUserEmail);
AccountId secondary_account_id_ =
AccountId::FromUserEmail(kSecondaryUserEmail);
};
// Verifies feature discovery duration is only recorded for primary accounts.
TEST_F(FeatureDiscoveryDurationReporterImplTest, OnlyRecordForNewPrimaryUser) {
// Activate the primary user session then verify that the reporter is active.
EXPECT_FALSE(IsReporterActive());
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
EXPECT_TRUE(IsReporterActive());
// Switch to the secondary account. The session should still be active.
TestSessionControllerClient* session_controller =
GetSessionControllerClient();
session_controller->SwitchActiveUser(secondary_account_id_);
EXPECT_EQ(session_manager::SessionState::ACTIVE,
GetSessionController()->GetSessionState());
// The current user is not primary so the reporter is inactive.
EXPECT_FALSE(IsReporterActive());
// The metric data should not be recorded because the reporter is inactive.
base::HistogramTester histogram_tester;
FeatureDiscoveryDurationReporterImpl* reporter =
GetFeatureDiscoveryDurationReporter();
reporter->MaybeActivateObservation(
feature_discovery::TrackableFeature::kMockFeature);
task_environment()->FastForwardBy(base::Minutes(1));
reporter->MaybeFinishObservation(
feature_discovery::TrackableFeature::kMockFeature);
histogram_tester.ExpectTotalCount(kMockHistogram, 0);
}
// Verifies that the feature discovery duration is recorded correctly in one
// active session.
TEST_F(FeatureDiscoveryDurationReporterImplTest, CountDurationInOneSession) {
EXPECT_FALSE(IsReporterActive());
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
EXPECT_TRUE(IsReporterActive());
// Start observation. Emulate to wait for one minute then stop observation.
base::HistogramTester histogram_tester;
// Finishing the observation that has not started should not record any data.
FeatureDiscoveryDurationReporterImpl* reporter =
GetFeatureDiscoveryDurationReporter();
reporter->MaybeFinishObservation(
feature_discovery::TrackableFeature::kMockFeature);
histogram_tester.ExpectTotalCount(kMockHistogram, 0);
reporter->MaybeActivateObservation(
feature_discovery::TrackableFeature::kMockFeature);
task_environment()->FastForwardBy(base::Minutes(1));
reporter->MaybeFinishObservation(
feature_discovery::TrackableFeature::kMockFeature);
// Check that one-minute duration is recorded.
histogram_tester.ExpectUniqueTimeSample(kMockHistogram, base::Minutes(1), 1);
// Try to observe again. No additional data should be recorded for the same
// histogram.
reporter->MaybeActivateObservation(
feature_discovery::TrackableFeature::kMockFeature);
task_environment()->FastForwardBy(base::Minutes(1));
reporter->MaybeFinishObservation(
feature_discovery::TrackableFeature::kMockFeature);
histogram_tester.ExpectTotalCount(kMockHistogram, 1);
// Deactivate the reporter then reactivate it.
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOCKED);
EXPECT_FALSE(IsReporterActive());
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
// Verify that the finished observation does not resume.
EXPECT_TRUE(IsReporterActive());
EXPECT_FALSE(IsMockFeatureUnderActiveObservation());
}
// Verifies that the feature discovery duration is recorded correctly across
// session states (i.e. deactivate a session then activate it before finishing
// the observation).
TEST_F(FeatureDiscoveryDurationReporterImplTest,
CountDurationAcrossSessionStates) {
EXPECT_FALSE(IsReporterActive());
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
EXPECT_TRUE(IsReporterActive());
// Start observation. Emulate to wait for one minute then lock the screen.
base::HistogramTester histogram_tester;
FeatureDiscoveryDurationReporterImpl* reporter =
GetFeatureDiscoveryDurationReporter();
reporter->MaybeActivateObservation(
feature_discovery::TrackableFeature::kMockFeature);
constexpr base::TimeDelta delta1(base::Minutes(1));
task_environment()->FastForwardBy(delta1);
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOCKED);
// Because the session is inactive, the reporter should be inactive.
EXPECT_FALSE(IsReporterActive());
// Emulate to wait for three minutes before reactivating the session.
task_environment()->FastForwardBy(base::Minutes(3));
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
// Emulate to wait for five minutes before finishing observation.
constexpr base::TimeDelta delta2(base::Minutes(5));
task_environment()->FastForwardBy(delta2);
reporter->MaybeFinishObservation(
feature_discovery::TrackableFeature::kMockFeature);
// Check that only the time duration under the active session is recorded.
histogram_tester.ExpectUniqueTimeSample(kMockHistogram, delta1 + delta2, 1);
// Try to observe again. No additional data should be recorded for the same
// histogram.
reporter->MaybeActivateObservation(
feature_discovery::TrackableFeature::kMockFeature);
task_environment()->FastForwardBy(base::Minutes(1));
reporter->MaybeFinishObservation(
feature_discovery::TrackableFeature::kMockFeature);
histogram_tester.ExpectTotalCount(kMockHistogram, 1);
}
// Verifies each feature that is supported by the feature discovery duration
// reporter has the unique feature name.
TEST_F(FeatureDiscoveryDurationReporterImplTest, VerifyFeatureNameIsUnique) {
auto cmp = [](const char* a, const char* b) { return std::strcmp(a, b) > 0; };
std::set<const char*, decltype(cmp)> feature_names(cmp);
for (const auto& feature_info : feature_discovery::kTrackableFeatureArray) {
bool success = feature_names.emplace(feature_info.name).second;
EXPECT_TRUE(success) << " " << feature_info.name
<< " is used more than once";
}
}
// Used to verify that collected data can be split by tablet mode states.
class FeatureDiscoveryDurationReporterDataSplitTest
: public FeatureDiscoveryDurationReporterImplTest,
public testing::WithParamInterface</*is_tablet=*/bool> {
public:
FeatureDiscoveryDurationReporterDataSplitTest() = default;
FeatureDiscoveryDurationReporterDataSplitTest(
const FeatureDiscoveryDurationReporterDataSplitTest&) = delete;
FeatureDiscoveryDurationReporterDataSplitTest& operator=(
const FeatureDiscoveryDurationReporterDataSplitTest&) = delete;
~FeatureDiscoveryDurationReporterDataSplitTest() override = default;
// FeatureDiscoveryDurationReporterImplTest:
void SetUp() override {
FeatureDiscoveryDurationReporterImplTest::SetUp();
TabletMode::Get()->SetEnabledForTest(GetParam());
}
};
INSTANTIATE_TEST_SUITE_P(All,
FeatureDiscoveryDurationReporterDataSplitTest,
testing::Bool());
TEST_P(FeatureDiscoveryDurationReporterDataSplitTest, Basics) {
EXPECT_FALSE(IsReporterActive());
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
EXPECT_TRUE(IsReporterActive());
base::HistogramTester histogram_tester;
FeatureDiscoveryDurationReporterImpl* reporter =
GetFeatureDiscoveryDurationReporter();
reporter->MaybeActivateObservation(
feature_discovery::TrackableFeature::kModeSeparateMockFeature);
constexpr base::TimeDelta delta(base::Minutes(1));
task_environment()->FastForwardBy(delta);
reporter->MaybeFinishObservation(
feature_discovery::TrackableFeature::kModeSeparateMockFeature);
// Verify that data is recorded with the correct histogram.
if (GetParam()) {
histogram_tester.ExpectUniqueTimeSample(kMockFeatureTabletHistogram, delta,
1);
histogram_tester.ExpectTotalCount(kMockFeatureClamshellHistogram, 0);
} else {
histogram_tester.ExpectUniqueTimeSample(kMockFeatureClamshellHistogram,
delta, 1);
histogram_tester.ExpectTotalCount(kMockFeatureTabletHistogram, 0);
}
}
// Verifies that the metric data is correctly reported when the tablet mode
// switches before the end of observation.
TEST_P(FeatureDiscoveryDurationReporterDataSplitTest,
SwitchModeBeforeMetricReport) {
EXPECT_FALSE(IsReporterActive());
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
EXPECT_TRUE(IsReporterActive());
base::HistogramTester histogram_tester;
FeatureDiscoveryDurationReporterImpl* reporter =
GetFeatureDiscoveryDurationReporter();
reporter->MaybeActivateObservation(
feature_discovery::TrackableFeature::kModeSeparateMockFeature);
constexpr base::TimeDelta delta1(base::Minutes(1));
task_environment()->FastForwardBy(delta1);
// Toggle the tablet mode. Wait for the time duration of `delta2`. Then finish
// the observation.
TabletMode::Get()->SetEnabledForTest(!GetParam());
constexpr base::TimeDelta delta2(base::Minutes(2));
task_environment()->FastForwardBy(delta2);
reporter->MaybeFinishObservation(
feature_discovery::TrackableFeature::kModeSeparateMockFeature);
// Verify that data is recorded with the correct histogram.
if (GetParam()) {
histogram_tester.ExpectUniqueTimeSample(kMockFeatureTabletHistogram,
delta1 + delta2, 1);
histogram_tester.ExpectTotalCount(kMockFeatureClamshellHistogram, 0);
} else {
histogram_tester.ExpectUniqueTimeSample(kMockFeatureClamshellHistogram,
delta1 + delta2, 1);
histogram_tester.ExpectTotalCount(kMockFeatureTabletHistogram, 0);
}
}
// Verifies the metric data that is not split by tablet mode should be recorded
// under both clamshell and tablet.
TEST_P(FeatureDiscoveryDurationReporterDataSplitTest, CheckUnsplitMetric) {
EXPECT_FALSE(IsReporterActive());
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
EXPECT_TRUE(IsReporterActive());
base::HistogramTester histogram_tester;
FeatureDiscoveryDurationReporterImpl* reporter =
GetFeatureDiscoveryDurationReporter();
reporter->MaybeActivateObservation(
feature_discovery::TrackableFeature::kMockFeature);
constexpr base::TimeDelta delta(base::Minutes(1));
task_environment()->FastForwardBy(delta);
reporter->MaybeFinishObservation(
feature_discovery::TrackableFeature::kMockFeature);
histogram_tester.ExpectUniqueTimeSample(kMockHistogram, delta, 1);
}
} // namespace ash