// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/android/pre_freeze_background_memory_trimmer.h"
#include <optional>
#include "base/task/thread_pool.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base::android {
namespace {
static int s_counter = 0;
void ResetGlobalCounter() {
s_counter = 0;
}
void IncGlobalCounter() {
s_counter++;
}
void DecGlobalCounter() {
s_counter--;
}
void PostDelayedIncGlobal() {
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
base::BindRepeating(&IncGlobalCounter), base::Seconds(10));
}
class MockMetric : public PreFreezeBackgroundMemoryTrimmer::PreFreezeMetric {
public:
MockMetric() : PreFreezeBackgroundMemoryTrimmer::PreFreezeMetric("Mock") {
count_++;
}
std::optional<uint64_t> Measure() const override { return 0; }
static size_t count_;
~MockMetric() override { count_--; }
};
size_t MockMetric::count_ = 0;
} // namespace
class PreFreezeBackgroundMemoryTrimmerTest : public testing::Test {
public:
PreFreezeBackgroundMemoryTrimmerTest() {
fl_.InitAndEnableFeature(kOnPreFreezeMemoryTrim);
}
void SetUp() override {
PreFreezeBackgroundMemoryTrimmer::SetSupportsModernTrimForTesting(true);
PreFreezeBackgroundMemoryTrimmer::ClearMetricsForTesting();
ResetGlobalCounter();
}
protected:
size_t pending_task_count() {
return PreFreezeBackgroundMemoryTrimmer::Instance()
.GetNumberOfPendingBackgroundTasksForTesting();
}
bool did_register_tasks() {
return PreFreezeBackgroundMemoryTrimmer::Instance()
.DidRegisterTasksForTesting();
}
size_t measurements_count() {
return PreFreezeBackgroundMemoryTrimmer::Instance()
.GetNumberOfKnownMetricsForTesting();
}
size_t values_before_count() {
return PreFreezeBackgroundMemoryTrimmer::Instance()
.GetNumberOfValuesBeforeForTesting();
}
test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
private:
test::ScopedFeatureList fl_;
};
// We do not expect any tasks to be registered with
// PreFreezeBackgroundMemoryTrimmer on Android versions before U.
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, PostTaskPreFreezeUnsupported) {
PreFreezeBackgroundMemoryTrimmer::SetSupportsModernTrimForTesting(false);
ASSERT_FALSE(did_register_tasks());
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
base::BindRepeating(&IncGlobalCounter), base::Seconds(30));
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(did_register_tasks());
task_environment_.FastForwardBy(base::Seconds(30));
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(s_counter, 1);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, PostTaskPreFreezeWithoutTrim) {
test::ScopedFeatureList fl;
fl.InitAndDisableFeature(kOnPreFreezeMemoryTrim);
ASSERT_FALSE(did_register_tasks());
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
base::BindRepeating(&IncGlobalCounter), base::Seconds(30));
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_TRUE(did_register_tasks());
task_environment_.FastForwardBy(base::Seconds(30));
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(s_counter, 1);
}
// TODO(thiabaud): Test that the histograms are recorded too.
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, RegisterMetric) {
ASSERT_EQ(measurements_count(), 0u);
ASSERT_EQ(MockMetric::count_, 0u);
{
MockMetric mock_metric;
PreFreezeBackgroundMemoryTrimmer::RegisterMemoryMetric(&mock_metric);
EXPECT_EQ(MockMetric::count_, 1u);
EXPECT_EQ(measurements_count(), 1u);
PreFreezeBackgroundMemoryTrimmer::UnregisterMemoryMetric(&mock_metric);
// Unregistering does not destroy the metric.
EXPECT_EQ(MockMetric::count_, 1u);
EXPECT_EQ(measurements_count(), 0u);
}
EXPECT_EQ(MockMetric::count_, 0u);
EXPECT_EQ(measurements_count(), 0u);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, UnregisterDuringPreFreeze) {
ASSERT_EQ(measurements_count(), 0u);
ASSERT_EQ(MockMetric::count_, 0u);
{
MockMetric mock_metric;
PreFreezeBackgroundMemoryTrimmer::RegisterMemoryMetric(&mock_metric);
EXPECT_EQ(MockMetric::count_, 1u);
EXPECT_EQ(measurements_count(), 1u);
// This posts a metrics task.
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
EXPECT_EQ(measurements_count(), 1u);
EXPECT_EQ(values_before_count(), 1u);
PreFreezeBackgroundMemoryTrimmer::UnregisterMemoryMetric(&mock_metric);
// Unregistering does not destroy the metric, but does remove its value
// from |before_values_|.
EXPECT_EQ(MockMetric::count_, 1u);
EXPECT_EQ(measurements_count(), 0u);
EXPECT_EQ(values_before_count(), 0u);
}
EXPECT_EQ(MockMetric::count_, 0u);
EXPECT_EQ(measurements_count(), 0u);
EXPECT_EQ(values_before_count(), 0u);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, PostDelayedTaskSimple) {
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
base::BindRepeating(&IncGlobalCounter), base::Seconds(30));
ASSERT_TRUE(did_register_tasks());
ASSERT_EQ(pending_task_count(), 1u);
task_environment_.FastForwardBy(base::Seconds(30));
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(s_counter, 1);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, PostDelayedTaskMultiple) {
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
base::BindRepeating(&IncGlobalCounter), base::Seconds(40));
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
base::BindRepeating(&IncGlobalCounter), base::Seconds(30));
ASSERT_EQ(pending_task_count(), 2u);
task_environment_.FastForwardBy(base::Seconds(30));
ASSERT_EQ(pending_task_count(), 1u);
EXPECT_EQ(s_counter, 1);
task_environment_.FastForwardBy(base::Seconds(10));
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(s_counter, 2);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, PostDelayedTaskPreFreeze) {
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
base::BindRepeating(&IncGlobalCounter), base::Seconds(60));
ASSERT_EQ(pending_task_count(), 1u);
task_environment_.FastForwardBy(base::Seconds(30));
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(s_counter, 1);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, PostDelayedTaskMultiThreaded) {
base::WaitableEvent event1(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
base::WaitableEvent event2(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
auto task_runner =
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()});
ASSERT_FALSE(task_runner->RunsTasksInCurrentSequence());
task_runner->PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<base::SequencedTaskRunner> task_runner,
base::WaitableEvent* event1, base::WaitableEvent* event2) {
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
task_runner, FROM_HERE,
base::BindOnce(
[](base::WaitableEvent* event) {
IncGlobalCounter();
event->Signal();
},
base::Unretained(event2)),
base::Seconds(30));
event1->Signal();
},
task_runner, base::Unretained(&event1), base::Unretained(&event2)));
task_environment_.FastForwardBy(base::Seconds(1));
event1.Wait();
ASSERT_EQ(pending_task_count(), 1u);
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
event2.Wait();
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(s_counter, 1);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest,
PostDelayedTaskBeforeAndAfterPreFreeze) {
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
base::BindRepeating(&IncGlobalCounter), base::Seconds(60));
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
base::BindRepeating(&IncGlobalCounter), base::Seconds(30));
ASSERT_EQ(pending_task_count(), 2u);
task_environment_.FastForwardBy(base::Seconds(30));
ASSERT_EQ(pending_task_count(), 1u);
EXPECT_EQ(s_counter, 1);
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(s_counter, 2);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, AddDuringPreFreeze) {
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
base::BindRepeating(&PostDelayedIncGlobal), base::Seconds(10));
ASSERT_EQ(pending_task_count(), 1u);
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
ASSERT_EQ(pending_task_count(), 1u);
EXPECT_EQ(s_counter, 0);
// Fast forward to run the metrics task.
task_environment_.FastForwardBy(base::Seconds(2));
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(s_counter, 1);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, AddDuringPreFreezeRunNormally) {
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
base::BindRepeating(&PostDelayedIncGlobal), base::Seconds(10));
ASSERT_EQ(pending_task_count(), 1u);
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
ASSERT_EQ(pending_task_count(), 1u);
EXPECT_EQ(s_counter, 0);
task_environment_.FastForwardBy(base::Seconds(30));
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(s_counter, 1);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, TimerNeverStarted) {
OneShotDelayedBackgroundTimer timer;
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
task_environment_.FastForwardBy(base::Seconds(30));
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
ASSERT_FALSE(did_register_tasks());
EXPECT_EQ(s_counter, 0);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, TimerFastForward) {
OneShotDelayedBackgroundTimer timer;
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
ASSERT_FALSE(did_register_tasks());
timer.Start(FROM_HERE, base::Seconds(30), base::BindOnce(&IncGlobalCounter));
ASSERT_EQ(pending_task_count(), 1u);
ASSERT_TRUE(timer.IsRunning());
ASSERT_TRUE(did_register_tasks());
task_environment_.FastForwardBy(base::Seconds(30));
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
EXPECT_EQ(s_counter, 1);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, TimerOnPreFreeze) {
OneShotDelayedBackgroundTimer timer;
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
ASSERT_FALSE(did_register_tasks());
timer.Start(FROM_HERE, base::Seconds(30), base::BindOnce(&IncGlobalCounter));
ASSERT_EQ(pending_task_count(), 1u);
ASSERT_TRUE(timer.IsRunning());
ASSERT_TRUE(did_register_tasks());
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
EXPECT_EQ(s_counter, 1);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, TimerStopSingle) {
OneShotDelayedBackgroundTimer timer;
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
ASSERT_FALSE(did_register_tasks());
timer.Start(FROM_HERE, base::Seconds(30), base::BindOnce(&IncGlobalCounter));
ASSERT_EQ(pending_task_count(), 1u);
ASSERT_TRUE(timer.IsRunning());
ASSERT_TRUE(did_register_tasks());
timer.Stop();
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
EXPECT_EQ(s_counter, 0);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, TimerStopMultiple) {
OneShotDelayedBackgroundTimer timer;
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
ASSERT_FALSE(did_register_tasks());
timer.Start(FROM_HERE, base::Seconds(30), base::BindOnce(&IncGlobalCounter));
ASSERT_EQ(pending_task_count(), 1u);
ASSERT_TRUE(timer.IsRunning());
ASSERT_TRUE(did_register_tasks());
timer.Stop();
timer.Stop();
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
EXPECT_EQ(s_counter, 0);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, TimerDestroyed) {
// Add scope here to destroy timer.
{
OneShotDelayedBackgroundTimer timer;
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
ASSERT_FALSE(did_register_tasks());
timer.Start(FROM_HERE, base::Seconds(30),
base::BindOnce(&IncGlobalCounter));
ASSERT_EQ(pending_task_count(), 1u);
ASSERT_TRUE(timer.IsRunning());
ASSERT_TRUE(did_register_tasks());
}
ASSERT_EQ(pending_task_count(), 0u);
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(s_counter, 0);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, TimerStartedWhileRunning) {
IncGlobalCounter();
ASSERT_EQ(s_counter, 1);
OneShotDelayedBackgroundTimer timer;
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
ASSERT_FALSE(did_register_tasks());
timer.Start(FROM_HERE, base::Seconds(30), base::BindOnce(&IncGlobalCounter));
ASSERT_EQ(pending_task_count(), 1u);
ASSERT_TRUE(timer.IsRunning());
ASSERT_TRUE(did_register_tasks());
timer.Start(FROM_HERE, base::Seconds(10), base::BindOnce(&DecGlobalCounter));
// Previous task was cancelled, so s_counter should still be 1.
ASSERT_EQ(s_counter, 1);
ASSERT_EQ(pending_task_count(), 1u);
ASSERT_TRUE(timer.IsRunning());
ASSERT_TRUE(did_register_tasks());
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
ASSERT_TRUE(did_register_tasks());
// Expect 0 here because we decremented it. The incrementing task was
// cancelled when we restarted the experiment.
EXPECT_EQ(s_counter, 0);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, BoolTaskRunDirectly) {
std::optional<MemoryReductionTaskContext> called_task_type = std::nullopt;
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
base::BindOnce(
[](std::optional<MemoryReductionTaskContext>& called_task_type,
MemoryReductionTaskContext task_type) {
called_task_type = task_type;
},
std::ref(called_task_type)),
base::Seconds(30));
ASSERT_FALSE(called_task_type.has_value());
ASSERT_EQ(pending_task_count(), 1u);
task_environment_.FastForwardBy(base::Seconds(30));
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(called_task_type.value(),
MemoryReductionTaskContext::kDelayExpired);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, BoolTaskRunFromPreFreeze) {
std::optional<MemoryReductionTaskContext> called_task_type = std::nullopt;
PreFreezeBackgroundMemoryTrimmer::PostDelayedBackgroundTask(
SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
base::BindOnce(
[](std::optional<MemoryReductionTaskContext>& called_task_type,
MemoryReductionTaskContext task_type) {
called_task_type = task_type;
},
std::ref(called_task_type)),
base::Seconds(30));
ASSERT_FALSE(called_task_type.has_value());
ASSERT_EQ(pending_task_count(), 1u);
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(called_task_type.value(), MemoryReductionTaskContext::kProactive);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, TimerBoolTaskRunDirectly) {
OneShotDelayedBackgroundTimer timer;
std::optional<MemoryReductionTaskContext> called_task_type = std::nullopt;
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
timer.Start(
FROM_HERE, base::Seconds(30),
base::BindOnce(
[](std::optional<MemoryReductionTaskContext>& called_task_type,
MemoryReductionTaskContext task_type) {
called_task_type = task_type;
},
std::ref(called_task_type)));
ASSERT_FALSE(called_task_type.has_value());
ASSERT_EQ(pending_task_count(), 1u);
task_environment_.FastForwardBy(base::Seconds(30));
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(called_task_type.value(),
MemoryReductionTaskContext::kDelayExpired);
}
TEST_F(PreFreezeBackgroundMemoryTrimmerTest, TimerBoolTaskRunFromPreFreeze) {
OneShotDelayedBackgroundTimer timer;
std::optional<MemoryReductionTaskContext> called_task_type = std::nullopt;
ASSERT_EQ(pending_task_count(), 0u);
ASSERT_FALSE(timer.IsRunning());
timer.Start(
FROM_HERE, base::Seconds(30),
base::BindOnce(
[](std::optional<MemoryReductionTaskContext>& called_task_type,
MemoryReductionTaskContext task_type) {
called_task_type = task_type;
},
std::ref(called_task_type)));
ASSERT_FALSE(called_task_type.has_value());
ASSERT_EQ(pending_task_count(), 1u);
PreFreezeBackgroundMemoryTrimmer::OnPreFreezeForTesting();
ASSERT_EQ(pending_task_count(), 0u);
EXPECT_EQ(called_task_type.value(), MemoryReductionTaskContext::kProactive);
}
} // namespace base::android