blob: f4bcfa2a1c29ab4b79ca69148cffa0a70879397f [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/tabs/organization/trigger_policies.h"
#include <memory>
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
class UsageTickClockTest : public testing::Test {
public:
UsageTickClockTest() {
metrics::DesktopSessionDurationTracker::Initialize();
clock_ = std::make_unique<UsageTickClock>(&inner_clock_);
}
~UsageTickClockTest() override {
clock_.reset();
metrics::DesktopSessionDurationTracker::CleanupForTesting();
}
UsageTickClock* clock() { return clock_.get(); }
base::SimpleTestTickClock* inner_clock() { return &inner_clock_; }
private:
// Required to use DesktopSessionDurationTracker.
base::test::TaskEnvironment task_environment;
base::SimpleTestTickClock inner_clock_;
std::unique_ptr<UsageTickClock> clock_;
};
TEST_F(UsageTickClockTest, TestClock) {
const base::TimeTicks start_time = clock()->NowTicks();
auto* tracker = metrics::DesktopSessionDurationTracker::Get();
// Time stands still while Chrome is not in use.
inner_clock()->Advance(base::Minutes(1));
EXPECT_EQ(start_time, clock()->NowTicks());
// Time moves normally while Chrome is in use.
tracker->OnVisibilityChanged(true, base::TimeDelta());
tracker->OnUserEvent();
inner_clock()->Advance(base::Minutes(1));
EXPECT_EQ(base::Minutes(1), clock()->NowTicks() - start_time);
// Only active time is counted if there's some active and some inactive time.
inner_clock()->Advance(base::Minutes(1));
tracker->OnVisibilityChanged(false, base::TimeDelta());
inner_clock()->Advance(base::Minutes(1));
EXPECT_EQ(base::Minutes(2), clock()->NowTicks() - start_time);
}
TEST_F(UsageTickClockTest, TestClockWithoutInitialization) {
metrics::DesktopSessionDurationTracker::CleanupForTesting();
// Time moves since tracker is not initialized.
inner_clock()->Advance(base::Minutes(1));
EXPECT_EQ(inner_clock()->NowTicks(), clock()->NowTicks());
// Create the tracker again to safely clean test.
metrics::DesktopSessionDurationTracker::Initialize();
}
class FakeBackoffLevelProvider final : public BackoffLevelProvider {
unsigned int Get() const override { return level_; }
void Increment() override { level_++; }
void Decrement() override { level_ = std::max(1u, level_) - 1; }
private:
unsigned int level_ = 0;
};
class TargetFrequencyTriggerTest : public testing::Test {
public:
TargetFrequencyTriggerTest() {
auto clock = std::make_unique<base::SimpleTestTickClock>();
clock_ = clock.get();
backoff_level_provider_ = std::make_unique<FakeBackoffLevelProvider>();
policy_ = std::make_unique<TargetFrequencyTriggerPolicy>(
std::move(clock), base::Days(1.0f), 2.0f,
backoff_level_provider_.get());
// Start 10% into the period so +1 period doesn't put us exactly on the
// rollover instant.
clock_->Advance(base::Days(0.1f));
}
void advance_to_next_phase() { clock()->Advance(base::Days(0.5f)); }
base::SimpleTestTickClock* clock() { return clock_.get(); }
TargetFrequencyTriggerPolicy* policy() { return policy_.get(); }
private:
std::unique_ptr<BackoffLevelProvider> backoff_level_provider_;
std::unique_ptr<TargetFrequencyTriggerPolicy> policy_;
raw_ptr<base::SimpleTestTickClock> clock_;
};
TEST_F(TargetFrequencyTriggerTest, TriggersWhenBestScoreBeaten) {
EXPECT_FALSE(policy()->ShouldTrigger(1.0f));
advance_to_next_phase();
EXPECT_FALSE(policy()->ShouldTrigger(0.5f));
EXPECT_TRUE(policy()->ShouldTrigger(2.0f));
}
TEST_F(TargetFrequencyTriggerTest, DoesntTriggerInObservationPhase) {
EXPECT_FALSE(policy()->ShouldTrigger(1.0f));
}
TEST_F(TargetFrequencyTriggerTest, DoesntTriggerWithoutObservation) {
advance_to_next_phase();
EXPECT_FALSE(policy()->ShouldTrigger(9001.0f));
}
TEST_F(TargetFrequencyTriggerTest, TriggersOncePerPeriod) {
EXPECT_FALSE(policy()->ShouldTrigger(1.0f));
advance_to_next_phase();
EXPECT_TRUE(policy()->ShouldTrigger(2.0f));
EXPECT_FALSE(policy()->ShouldTrigger(9001.0f));
}
TEST_F(TargetFrequencyTriggerTest, TriggersAgainNextPeriod) {
// Go through one period, with a trigger.
EXPECT_FALSE(policy()->ShouldTrigger(1.0f));
advance_to_next_phase();
EXPECT_TRUE(policy()->ShouldTrigger(2.0f));
// Next period, observe.
advance_to_next_phase();
EXPECT_FALSE(policy()->ShouldTrigger(0.5f));
advance_to_next_phase();
// Should trigger again, as long as we beat this period's best score.
EXPECT_TRUE(policy()->ShouldTrigger(0.75f));
}
TEST_F(TargetFrequencyTriggerTest, BacksOffAfterFailure) {
EXPECT_FALSE(policy()->ShouldTrigger(1.0f));
policy()->OnTriggerFailed();
advance_to_next_phase();
EXPECT_FALSE(policy()->ShouldTrigger(2.0f));
advance_to_next_phase();
EXPECT_TRUE(policy()->ShouldTrigger(3.0f));
}
TEST_F(TargetFrequencyTriggerTest, UnBacksOffAfterSuccess) {
EXPECT_FALSE(policy()->ShouldTrigger(1.0f));
policy()->OnTriggerFailed();
policy()->OnTriggerSucceeded();
advance_to_next_phase();
EXPECT_TRUE(policy()->ShouldTrigger(2.0f));
}
TEST_F(TargetFrequencyTriggerTest, PeriodClampedToBasePeriod) {
EXPECT_FALSE(policy()->ShouldTrigger(1.0f));
policy()->OnTriggerSucceeded();
advance_to_next_phase();
EXPECT_TRUE(policy()->ShouldTrigger(2.0f));
}
TEST_F(TargetFrequencyTriggerTest,
HistogramLoggedOnShouldTriggerAfterPeriodExpiration) {
base::HistogramTester histogram_tester;
// Observation phase 1
EXPECT_FALSE(policy()->ShouldTrigger(1.0f));
advance_to_next_phase();
// trigger phase 1
EXPECT_TRUE(policy()->ShouldTrigger(2.0f));
advance_to_next_phase();
// observation phase 2. should log the results of triggering from phase 1.
EXPECT_FALSE(policy()->ShouldTrigger(1.0f));
histogram_tester.ExpectBucketCount(
"Tab.Organization.Trigger.TriggeredInPeriod", true, 1);
advance_to_next_phase();
// trigger phase 2
EXPECT_FALSE(policy()->ShouldTrigger(0.0f));
advance_to_next_phase();
// observation phase 3. should log the results of triggering from phase 2.
EXPECT_FALSE(policy()->ShouldTrigger(1.0f));
histogram_tester.ExpectBucketCount(
"Tab.Organization.Trigger.TriggeredInPeriod", false, 1);
}