Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(53)

Unified Diff: chrome/browser/memory/tab_manager_unittest.cc

Issue 1641813002: Provide renderers with memory pressure signals. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@contentapi
Patch Set: Addressed creis' comments. Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/memory/tab_manager.cc ('k') | chrome/browser/memory/tab_stats.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « chrome/browser/memory/tab_manager.cc ('k') | chrome/browser/memory/tab_stats.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698