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 |