| Index: chrome/browser/memory/tab_manager_unittest.cc
|
| diff --git a/chrome/browser/memory/tab_manager_unittest.cc b/chrome/browser/memory/tab_manager_unittest.cc
|
| index 719afca4859fa9b267e4e3ef41a344c88252c032..08f2e8f83cff6882b80aa2cff7b97d2bf30f5b1b 100644
|
| --- a/chrome/browser/memory/tab_manager_unittest.cc
|
| +++ b/chrome/browser/memory/tab_manager_unittest.cc
|
| @@ -10,6 +10,7 @@
|
| #include "base/logging.h"
|
| #include "base/macros.h"
|
| #include "base/strings/string16.h"
|
| +#include "base/test/simple_test_tick_clock.h"
|
| #include "base/time/time.h"
|
| #include "build/build_config.h"
|
| #include "chrome/browser/memory/tab_manager_web_contents_data.h"
|
| @@ -22,6 +23,7 @@
|
| #include "chrome/test/base/testing_profile.h"
|
| #include "content/public/browser/web_contents.h"
|
| #include "content/public/test/web_contents_tester.h"
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
| #include "testing/gtest/include/gtest/gtest.h"
|
| #include "url/gurl.h"
|
|
|
| @@ -76,6 +78,96 @@ class MockTabStripModelObserver : public TabStripModelObserver {
|
| DISALLOW_COPY_AND_ASSIGN(MockTabStripModelObserver);
|
| };
|
|
|
| +// A mock task runner. This isn't directly a TaskRunner as the reference
|
| +// counting confuses gmock.
|
| +class LenientMockTaskRunner {
|
| + public:
|
| + LenientMockTaskRunner() {}
|
| + MOCK_METHOD3(PostDelayedTask,
|
| + bool(const tracked_objects::Location&,
|
| + const base::Closure&,
|
| + base::TimeDelta));
|
| + private:
|
| + DISALLOW_COPY_AND_ASSIGN(LenientMockTaskRunner);
|
| +};
|
| +using MockTaskRunner = testing::StrictMock<LenientMockTaskRunner>;
|
| +
|
| +// Represents a pending task.
|
| +using Task = std::pair<base::TimeTicks, base::Closure>;
|
| +
|
| +// Comparator used for sorting Task objects. Can't use std::pair's default
|
| +// comparison operators because Closure's are comparable.
|
| +struct TaskComparator {
|
| + bool operator()(const Task& t1, const Task& t2) const {
|
| + // Reverse the sort order so this can be used with a min-heap.
|
| + return t1.first > t2.first;
|
| + }
|
| +};
|
| +
|
| +// A TaskRunner implementation that delegates to a MockTaskRunner for asserting
|
| +// on task insertion order, and which stores tasks for actual later execution.
|
| +class TaskRunnerProxy : public base::TaskRunner {
|
| + public:
|
| + TaskRunnerProxy(MockTaskRunner* mock,
|
| + base::SimpleTestTickClock* clock)
|
| + : mock_(mock), clock_(clock) {}
|
| + bool RunsTasksOnCurrentThread() const override { return true; }
|
| + bool PostDelayedTask(const tracked_objects::Location& location,
|
| + const base::Closure& closure,
|
| + base::TimeDelta delta) override {
|
| + mock_->PostDelayedTask(location, closure, delta);
|
| + base::TimeTicks when = clock_->NowTicks() + delta;
|
| + tasks_.push_back(Task(when, closure));
|
| + // Use 'greater' comparator to make this a min heap.
|
| + std::push_heap(tasks_.begin(), tasks_.end(), TaskComparator());
|
| + return true;
|
| + }
|
| +
|
| + // Runs tasks up to the current time. Returns the number of tasks executed.
|
| + size_t RunTasks() {
|
| + base::TimeTicks now = clock_->NowTicks();
|
| + size_t count = 0;
|
| + while (!tasks_.empty() && tasks_.front().first <= now) {
|
| + tasks_.front().second.Run();
|
| + std::pop_heap(tasks_.begin(), tasks_.end(), TaskComparator());
|
| + tasks_.pop_back();
|
| + ++count;
|
| + }
|
| + return count;
|
| + }
|
| +
|
| + // Advances the clock and runs the next task in the queue. Can run more than
|
| + // one task if multiple tasks have the same scheduled time.
|
| + size_t RunNextTask() {
|
| + // Get the time of the next task that can possibly run.
|
| + base::TimeTicks when = tasks_.front().first;
|
| +
|
| + // Advance the clock to exactly that time.
|
| + base::TimeTicks now = clock_->NowTicks();
|
| + if (now < when) {
|
| + base::TimeDelta delta = when - now;
|
| + clock_->Advance(delta);
|
| + }
|
| +
|
| + // Run whatever tasks are now eligible to run.
|
| + return RunTasks();
|
| + }
|
| +
|
| + size_t size() const { return tasks_.size(); }
|
| +
|
| + private:
|
| + ~TaskRunnerProxy() override {}
|
| +
|
| + MockTaskRunner* mock_;
|
| + base::SimpleTestTickClock* clock_;
|
| +
|
| + // A min-heap of outstanding tasks.
|
| + using Task = std::pair<base::TimeTicks, base::Closure>;
|
| + std::vector<Task> tasks_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(TaskRunnerProxy);
|
| +};
|
| +
|
| enum TestIndicies {
|
| kSelected,
|
| kPinned,
|
| @@ -90,12 +182,17 @@ enum TestIndicies {
|
| };
|
| } // namespace
|
|
|
| -class TabManagerTest : public ChromeRenderViewHostTestHarness {
|
| +class LenientTabManagerTest : public ChromeRenderViewHostTestHarness {
|
| public:
|
| WebContents* CreateWebContents() {
|
| return WebContents::Create(WebContents::CreateParams(profile()));
|
| }
|
| +
|
| + MOCK_METHOD2(NotifyRendererProcess,
|
| + void(const content::RenderProcessHost*,
|
| + base::MemoryPressureListener::MemoryPressureLevel));
|
| };
|
| +using TabManagerTest = testing::StrictMock<LenientTabManagerTest>;
|
|
|
| // TODO(georgesak): Add tests for protection to tabs with form input and
|
| // playing audio;
|
| @@ -336,4 +433,154 @@ TEST_F(TabManagerTest, DiscardedTabKeepsLastActiveTime) {
|
| EXPECT_TRUE(tabstrip.empty());
|
| }
|
|
|
| +namespace {
|
| +
|
| +using MemoryPressureLevel = base::MemoryPressureListener::MemoryPressureLevel;
|
| +
|
| +// Function that always indicates the absence of memory pressure.
|
| +MemoryPressureLevel ReturnNoPressure() {
|
| + return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
|
| +}
|
| +
|
| +// Function that simply parrots back an externally specified memory pressure
|
| +// level.
|
| +MemoryPressureLevel ReturnSpecifiedPressure(
|
| + const MemoryPressureLevel* level) {
|
| + return *level;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +// Ensure that memory pressure notifications are forwarded to child processes.
|
| +TEST_F(TabManagerTest, ChildProcessNotifications) {
|
| + TabManager tm;
|
| +
|
| + // Set up the tab strip.
|
| + TabStripDummyDelegate delegate;
|
| + TabStripModel tabstrip(&delegate, profile());
|
| +
|
| + // Create test clock, task runner.
|
| + base::SimpleTestTickClock test_clock;
|
| + MockTaskRunner mock_task_runner;
|
| + scoped_refptr<TaskRunnerProxy> task_runner(
|
| + new TaskRunnerProxy(&mock_task_runner, &test_clock));
|
| +
|
| + // Configure the TabManager for testing.
|
| + tabstrip.AddObserver(&tm);
|
| + tm.test_tab_strip_models_.push_back(
|
| + TabManager::TestTabStripModel(&tabstrip, false /* !is_app */));
|
| + tm.test_tick_clock_ = &test_clock;
|
| + tm.task_runner_ = task_runner;
|
| + tm.notify_renderer_process_ = base::Bind(
|
| + &TabManagerTest::NotifyRendererProcess, base::Unretained(this));
|
| +
|
| + // Create two dummy tabs.
|
| + auto tab1 = CreateWebContents();
|
| + auto tab2 = CreateWebContents();
|
| + tabstrip.AppendWebContents(tab1, true); // Foreground tab.
|
| + tabstrip.AppendWebContents(tab2, false); // Opened in background.
|
| + const content::RenderProcessHost* renderer1 = tab1->GetRenderProcessHost();
|
| + const content::RenderProcessHost* renderer2 = tab2->GetRenderProcessHost();
|
| +
|
| + // Expect that the tab manager has not yet encountered memory pressure.
|
| + EXPECT_FALSE(tm.under_memory_pressure_);
|
| + EXPECT_EQ(0u, tm.notified_renderers_.size());
|
| +
|
| + // Simulate a memory pressure situation that will immediately end. This should
|
| + // cause no notifications or scheduled tasks.
|
| + auto level = base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE;
|
| + tm.get_current_pressure_level_ = base::Bind(&ReturnNoPressure);
|
| + tm.OnMemoryPressure(level);
|
| + testing::Mock::VerifyAndClearExpectations(&mock_task_runner);
|
| + EXPECT_FALSE(tm.under_memory_pressure_);
|
| + EXPECT_EQ(0u, task_runner->size());
|
| + EXPECT_EQ(0u, tm.notified_renderers_.size());
|
| +
|
| + // START OF MEMORY PRESSURE
|
| +
|
| + // Simulate a memory pressure situation that persists. This should cause a
|
| + // task to be scheduled.
|
| + tm.get_current_pressure_level_ = base::Bind(
|
| + &ReturnSpecifiedPressure, base::Unretained(&level));
|
| + EXPECT_CALL(mock_task_runner, PostDelayedTask(
|
| + testing::_,
|
| + testing::_,
|
| + base::TimeDelta::FromSeconds(tm.kRendererNotificationDelayInSeconds)));
|
| + EXPECT_CALL(*this, NotifyRendererProcess(renderer2, level));
|
| + tm.OnMemoryPressure(level);
|
| + testing::Mock::VerifyAndClearExpectations(&mock_task_runner);
|
| + testing::Mock::VerifyAndClearExpectations(this);
|
| + EXPECT_TRUE(tm.under_memory_pressure_);
|
| + EXPECT_EQ(1u, task_runner->size());
|
| + EXPECT_EQ(1u, tm.notified_renderers_.size());
|
| + EXPECT_EQ(1u, tm.notified_renderers_.count(renderer2));
|
| +
|
| + // REPEATED MEMORY PRESSURE SIGNAL
|
| +
|
| + // Simulate another memory pressure event. This should not cause any
|
| + // additional tasks to be scheduled, nor any further renderer notifications.
|
| + tm.OnMemoryPressure(level);
|
| + testing::Mock::VerifyAndClearExpectations(&mock_task_runner);
|
| + testing::Mock::VerifyAndClearExpectations(this);
|
| + EXPECT_TRUE(tm.under_memory_pressure_);
|
| + EXPECT_EQ(1u, task_runner->size());
|
| + EXPECT_EQ(1u, tm.notified_renderers_.size());
|
| +
|
| + // FIRST SCHEDULED NOTIFICATION
|
| +
|
| + // Run the scheduled task. This should cause another notification, but this
|
| + // time to the foreground tab. It should also cause another scheduled event.
|
| + EXPECT_CALL(mock_task_runner, PostDelayedTask(
|
| + testing::_,
|
| + testing::_,
|
| + base::TimeDelta::FromSeconds(tm.kRendererNotificationDelayInSeconds)));
|
| + EXPECT_CALL(*this, NotifyRendererProcess(renderer1, level));
|
| + EXPECT_EQ(1u, task_runner->RunNextTask());
|
| + testing::Mock::VerifyAndClearExpectations(&mock_task_runner);
|
| + testing::Mock::VerifyAndClearExpectations(this);
|
| + EXPECT_TRUE(tm.under_memory_pressure_);
|
| + EXPECT_EQ(1u, task_runner->size());
|
| + EXPECT_EQ(2u, tm.notified_renderers_.size());
|
| + EXPECT_EQ(1u, tm.notified_renderers_.count(renderer1));
|
| +
|
| + // SECOND SCHEDULED NOTIFICATION
|
| +
|
| + // Run the scheduled task. This should cause another notification, to the
|
| + // background tab. It should also cause another scheduled event.
|
| + EXPECT_CALL(mock_task_runner, PostDelayedTask(
|
| + testing::_,
|
| + testing::_,
|
| + base::TimeDelta::FromSeconds(tm.kRendererNotificationDelayInSeconds)));
|
| + EXPECT_CALL(*this, NotifyRendererProcess(renderer2, level));
|
| + EXPECT_EQ(1u, task_runner->RunNextTask());
|
| + testing::Mock::VerifyAndClearExpectations(&mock_task_runner);
|
| + testing::Mock::VerifyAndClearExpectations(this);
|
| + EXPECT_TRUE(tm.under_memory_pressure_);
|
| + EXPECT_EQ(1u, task_runner->size());
|
| + EXPECT_EQ(1u, tm.notified_renderers_.size());
|
| +
|
| + // Expect that the background tab has been notified. The list of notified
|
| + // renderers maintained by the TabManager should also have been reset because
|
| + // the notification logic restarts after all renderers are notified.
|
| + EXPECT_EQ(1u, tm.notified_renderers_.count(renderer2));
|
| +
|
| + // END OF MEMORY PRESSURE
|
| +
|
| + // End the memory pressure condition and run the scheduled event. This should
|
| + // clean up the various state data. It should not schedule another event nor
|
| + // fire any notifications.
|
| + level = base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
|
| + EXPECT_EQ(1u, task_runner->RunNextTask());
|
| + testing::Mock::VerifyAndClearExpectations(&mock_task_runner);
|
| + testing::Mock::VerifyAndClearExpectations(this);
|
| + EXPECT_FALSE(tm.under_memory_pressure_);
|
| + EXPECT_EQ(0u, task_runner->size());
|
| + EXPECT_EQ(0u, tm.notified_renderers_.size());
|
| +
|
| +
|
| + // Clean up the tabstrip.
|
| + tabstrip.CloseAllTabs();
|
| + ASSERT_TRUE(tabstrip.empty());
|
| +}
|
| +
|
| } // namespace memory
|
|
|