blob: a84286e95822af207e90dc1958c32141b55accce [file]
// Copyright 2026 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/memory/available_memory_monitor.h"
#include <cstdint>
#include <optional>
#include "base/byte_size.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
// Helper to cleanly construct cross-platform mock samples.
std::optional<AvailableMemoryMonitor::MemorySample> CreateSample(
uint64_t phys_bytes
#if BUILDFLAG(IS_WIN)
,
uint64_t avail_commit_bytes = 0,
uint64_t total_commit_bytes = 0
#endif
) {
AvailableMemoryMonitor::MemorySample sample;
sample.timestamp = base::TimeTicks::Now();
sample.available_physical_bytes = base::ByteSize(phys_bytes);
#if BUILDFLAG(IS_WIN)
sample.available_commit_bytes = base::ByteSize(avail_commit_bytes);
sample.total_commit_bytes = base::ByteSize(total_commit_bytes);
#endif
return sample;
}
class TestAvailableMemoryMonitor : public AvailableMemoryMonitor {
public:
TestAvailableMemoryMonitor() = default;
TestAvailableMemoryMonitor(const TestAvailableMemoryMonitor&) = delete;
TestAvailableMemoryMonitor& operator=(const TestAvailableMemoryMonitor&) =
delete;
~TestAvailableMemoryMonitor() override = default;
void SetFakeSample(std::optional<MemorySample> sample) {
fake_sample_ = sample;
}
// Allow the test to manually trigger a poll.
using AvailableMemoryMonitor::OnMemoryCheckTimer;
protected:
std::optional<MemorySample> ComputeAvailableMemory() override {
return fake_sample_;
}
private:
// Initialize with a default valid state.
std::optional<MemorySample> fake_sample_ = CreateSample(100);
};
class TestObserver : public AvailableMemoryMonitor::Observer {
public:
TestObserver() = default;
TestObserver(const TestObserver&) = delete;
TestObserver& operator=(const TestObserver&) = delete;
~TestObserver() override = default;
void OnAvailableMemoryUpdated(
const AvailableMemoryMonitor::MemorySample& sample) override {
last_sample_ = sample;
++sample_count_;
}
const std::optional<AvailableMemoryMonitor::MemorySample>& last_sample()
const {
return last_sample_;
}
int sample_count() const { return sample_count_; }
private:
std::optional<AvailableMemoryMonitor::MemorySample> last_sample_;
int sample_count_ = 0;
};
} // namespace
class AvailableMemoryMonitorTest : public ::testing::Test {
protected:
// Use MOCK_TIME to control the timer deterministically.
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
};
TEST_F(AvailableMemoryMonitorTest, NoImmediateCallbackOnAdd) {
TestAvailableMemoryMonitor monitor;
TestObserver observer;
monitor.SetFakeSample(CreateSample(250));
monitor.AddObserver(&observer);
// The observer should not have received a callback immediately upon addition.
EXPECT_EQ(observer.sample_count(), 0);
EXPECT_FALSE(monitor.GetLastSample().has_value());
monitor.RemoveObserver(&observer);
}
TEST_F(AvailableMemoryMonitorTest, GetLastSample) {
TestAvailableMemoryMonitor monitor;
monitor.SetFakeSample(CreateSample(100));
TestObserver observer;
monitor.AddObserver(&observer);
// The cache is initially empty because the timer has not fired.
EXPECT_FALSE(monitor.GetLastSample().has_value());
// Force a check to simulate the timer firing.
monitor.OnMemoryCheckTimer();
ASSERT_TRUE(monitor.GetLastSample().has_value());
EXPECT_EQ(monitor.GetLastSample()->available_physical_bytes,
base::ByteSize(100));
}
TEST_F(AvailableMemoryMonitorTest, PollsPeriodically) {
TestAvailableMemoryMonitor monitor;
TestObserver observer;
monitor.AddObserver(&observer);
EXPECT_EQ(observer.sample_count(), 0);
// Fast forward by the poll interval (2 seconds) to trigger the first check.
task_environment_.FastForwardBy(base::Seconds(2));
EXPECT_EQ(observer.sample_count(), 1);
// Update the mock sample.
monitor.SetFakeSample(CreateSample(300));
// Advance time again to trigger the second check.
task_environment_.FastForwardBy(base::Seconds(2));
EXPECT_EQ(observer.sample_count(), 2);
EXPECT_EQ(observer.last_sample()->available_physical_bytes,
base::ByteSize(300));
monitor.RemoveObserver(&observer);
}
TEST_F(AvailableMemoryMonitorTest, StopsPollingWhenNoObservers) {
TestAvailableMemoryMonitor monitor;
TestObserver observer;
monitor.SetFakeSample(CreateSample(100));
monitor.AddObserver(&observer);
// Trigger one sample update.
task_environment_.FastForwardBy(base::Seconds(2));
const int initial_count = observer.sample_count();
EXPECT_EQ(initial_count, 1);
monitor.RemoveObserver(&observer);
// Advance time significantly after the observer is removed.
task_environment_.FastForwardBy(base::Seconds(10));
// The callback count should not have increased.
EXPECT_EQ(observer.sample_count(), initial_count);
}
TEST_F(AvailableMemoryMonitorTest, LateObserverCanAccessCachedData) {
TestAvailableMemoryMonitor monitor;
TestObserver observer1;
TestObserver observer2;
monitor.SetFakeSample(CreateSample(100));
monitor.AddObserver(&observer1);
// Fire the timer to populate the cache.
task_environment_.FastForwardBy(base::Seconds(2));
EXPECT_EQ(observer1.sample_count(), 1);
// Add the second observer. It should not get a callback.
monitor.AddObserver(&observer2);
EXPECT_EQ(observer2.sample_count(), 0);
// However, it should be able to read the cached value immediately.
std::optional<AvailableMemoryMonitor::MemorySample> sample =
monitor.GetLastSample();
ASSERT_TRUE(sample.has_value());
EXPECT_EQ(sample->available_physical_bytes, base::ByteSize(100));
monitor.RemoveObserver(&observer1);
monitor.RemoveObserver(&observer2);
}
#if BUILDFLAG(IS_WIN)
TEST_F(AvailableMemoryMonitorTest, WindowsCommitBytesPlumbing) {
TestAvailableMemoryMonitor monitor;
// Create a mock sample with 100 physical, 200 available commit, 300 total
// commit.
monitor.SetFakeSample(CreateSample(100, 200, 300));
TestObserver observer;
monitor.AddObserver(&observer);
// Force a check to simulate the timer firing.
monitor.OnMemoryCheckTimer();
// Verify the cache holds the correct Windows-specific commit bytes.
ASSERT_TRUE(monitor.GetLastSample().has_value());
EXPECT_EQ(monitor.GetLastSample()->available_commit_bytes,
base::ByteSize(200));
EXPECT_EQ(monitor.GetLastSample()->total_commit_bytes, base::ByteSize(300));
// Verify the observer payload received the correct Windows-specific bytes.
ASSERT_EQ(observer.sample_count(), 1);
ASSERT_TRUE(observer.last_sample().has_value());
EXPECT_EQ(observer.last_sample()->available_commit_bytes,
base::ByteSize(200));
EXPECT_EQ(observer.last_sample()->total_commit_bytes, base::ByteSize(300));
}
#endif // BUILDFLAG(IS_WIN)
} // namespace base