Commit ef6cbc2d authored by Gabriel Charette's avatar Gabriel Charette Committed by Commit Bot

[ScopedTaskEnvironment] Always mock TimeTicks::Now() under MOCK_TIME mode

This (PS1) passes all existing tests as-is but
ParkableStringTest.ReportTotalUnparkingTime.

This failure happens because samples of Now() within the same task
no longer have any delta under MOCK_TIME regardless of how CPU expensive
the operation actually is.

That test was using a state of the art approach, base::ElapsedTimer,
MOCK_TIME, base::HistogramTester, etc.

But base::ElaspedTimer is neutered by the change to MOCK_TIME in this CL.

There are two potential solutions:
  1) Have ElapsedTimer play a role in auto-advancing mock-time.
  2) Mock timings returned by ElpasedTimer.

(1) is undesirable because it would make it possible for delayed tasks
to run as part of a basic RunUntilIdle() which takes control of delayed
tasks away from the ScopedTaskEnvironment owner and is highly undesired.

(2) is more explicit but also easier to control. This CL does that by
adding base::ScopedMockElapsedTimersForTest.

This actually makes ParkableStringTest.ReportTotalUnparkingTime cleaner
because it can now test for a specific timing instead of just
"has a non-zero timing".

Bug: 905412
Change-Id: I91fab300b4005fd7802ce837733665e77766d4b2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1707870
Auto-Submit: Gabriel Charette <gab@chromium.org>
Commit-Queue: Kentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarkylechar <kylechar@chromium.org>
Reviewed-by: default avatarBenoit L <lizeb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#683487}
parent ebcbdcdb
...@@ -96,9 +96,10 @@ class BASE_EXPORT WaitableEvent { ...@@ -96,9 +96,10 @@ class BASE_EXPORT WaitableEvent {
// delete e; // delete e;
void Wait(); void Wait();
// Wait up until wait_delta has passed for the event to be signaled. Returns // Wait up until wait_delta has passed for the event to be signaled
// true if the event was signaled. Handles spurious wakeups and guarantees // (real-time; ignores time overrides). Returns true if the event was
// that |wait_delta| will have elapsed if this returns false. // signaled. Handles spurious wakeups and guarantees that |wait_delta| will
// have elapsed if this returns false.
// //
// TimedWait can synchronise its own destruction like |Wait|. // TimedWait can synchronise its own destruction like |Wait|.
bool TimedWait(const TimeDelta& wait_delta); bool TimedWait(const TimeDelta& wait_delta);
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "base/threading/scoped_blocking_call.h" #include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_restrictions.h" #include "base/threading/thread_restrictions.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/time/time_override.h"
#include "build/build_config.h" #include "build/build_config.h"
namespace base { namespace base {
...@@ -140,11 +141,12 @@ bool WaitableEvent::TimedWait(const TimeDelta& wait_delta) { ...@@ -140,11 +141,12 @@ bool WaitableEvent::TimedWait(const TimeDelta& wait_delta) {
} }
// TimeTicks takes care of overflow but we special case is_max() nonetheless // TimeTicks takes care of overflow but we special case is_max() nonetheless
// to avoid invoking Now() unnecessarily (same for the increment step of the // to avoid invoking TimeTicksNowIgnoringOverride() unnecessarily (same for
// for loop if the condition variable returns early). // the increment step of the for loop if the condition variable returns
// Ref: https://crbug.com/910524#c7 // early). Ref: https://crbug.com/910524#c7
const TimeTicks end_time = const TimeTicks end_time =
wait_delta.is_max() ? TimeTicks::Max() : TimeTicks::Now() + wait_delta; wait_delta.is_max() ? TimeTicks::Max()
: subtle::TimeTicksNowIgnoringOverride() + wait_delta;
// Fake |kr| value to boostrap the for loop. // Fake |kr| value to boostrap the for loop.
kern_return_t kr = MACH_RCV_INTERRUPTED; kern_return_t kr = MACH_RCV_INTERRUPTED;
for (mach_msg_timeout_t timeout = wait_delta.is_max() for (mach_msg_timeout_t timeout = wait_delta.is_max()
...@@ -160,8 +162,8 @@ bool WaitableEvent::TimedWait(const TimeDelta& wait_delta) { ...@@ -160,8 +162,8 @@ bool WaitableEvent::TimedWait(const TimeDelta& wait_delta) {
end_time.is_max() end_time.is_max()
? MACH_MSG_TIMEOUT_NONE ? MACH_MSG_TIMEOUT_NONE
: std::max<int64_t>( : std::max<int64_t>(
0, 0, (end_time - subtle::TimeTicksNowIgnoringOverride())
(end_time - TimeTicks::Now()).InMillisecondsRoundedUp())) { .InMillisecondsRoundedUp())) {
kr = mach_msg(&msg.header, options, 0, rcv_size, receive_right_->Name(), kr = mach_msg(&msg.header, options, 0, rcv_size, receive_right_->Name(),
timeout, MACH_PORT_NULL); timeout, MACH_PORT_NULL);
} }
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include "base/threading/scoped_blocking_call.h" #include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_restrictions.h" #include "base/threading/thread_restrictions.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/time/time_override.h"
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// A WaitableEvent on POSIX is implemented as a wait-list. Currently we don't // A WaitableEvent on POSIX is implemented as a wait-list. Currently we don't
...@@ -196,14 +197,16 @@ bool WaitableEvent::TimedWait(const TimeDelta& wait_delta) { ...@@ -196,14 +197,16 @@ bool WaitableEvent::TimedWait(const TimeDelta& wait_delta) {
// again before unlocking it. // again before unlocking it.
// TimeTicks takes care of overflow but we special case is_max() nonetheless // TimeTicks takes care of overflow but we special case is_max() nonetheless
// to avoid invoking Now() unnecessarily (same for the increment step of the // to avoid invoking TimeTicksNowIgnoringOverride() unnecessarily (same for
// for loop if the condition variable returns early). // the increment step of the for loop if the condition variable returns
// Ref: https://crbug.com/910524#c7 // early). Ref: https://crbug.com/910524#c7
const TimeTicks end_time = const TimeTicks end_time =
wait_delta.is_max() ? TimeTicks::Max() : TimeTicks::Now() + wait_delta; wait_delta.is_max() ? TimeTicks::Max()
: subtle::TimeTicksNowIgnoringOverride() + wait_delta;
for (TimeDelta remaining = wait_delta; remaining > TimeDelta() && !sw.fired(); for (TimeDelta remaining = wait_delta; remaining > TimeDelta() && !sw.fired();
remaining = end_time.is_max() ? TimeDelta::Max() remaining = end_time.is_max()
: end_time - TimeTicks::Now()) { ? TimeDelta::Max()
: end_time - subtle::TimeTicksNowIgnoringOverride()) {
if (end_time.is_max()) if (end_time.is_max())
sw.cv()->Wait(); sw.cv()->Wait();
else else
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include "base/threading/scoped_blocking_call.h" #include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_restrictions.h" #include "base/threading/thread_restrictions.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/time/time_override.h"
namespace base { namespace base {
...@@ -88,13 +89,14 @@ bool WaitableEvent::TimedWait(const TimeDelta& wait_delta) { ...@@ -88,13 +89,14 @@ bool WaitableEvent::TimedWait(const TimeDelta& wait_delta) {
} }
// TimeTicks takes care of overflow but we special case is_max() nonetheless // TimeTicks takes care of overflow but we special case is_max() nonetheless
// to avoid invoking Now() unnecessarily. // to avoid invoking TimeTicksNowIgnoringOverride() unnecessarily.
// WaitForSingleObject(handle_.Get(), INFINITE) doesn't spuriously wakeup so // WaitForSingleObject(handle_.Get(), INFINITE) doesn't spuriously wakeup so
// we don't need to worry about is_max() for the increment phase of the loop. // we don't need to worry about is_max() for the increment phase of the loop.
const TimeTicks end_time = const TimeTicks end_time =
wait_delta.is_max() ? TimeTicks::Max() : TimeTicks::Now() + wait_delta; wait_delta.is_max() ? TimeTicks::Max()
: subtle::TimeTicksNowIgnoringOverride() + wait_delta;
for (TimeDelta remaining = wait_delta; remaining > TimeDelta(); for (TimeDelta remaining = wait_delta; remaining > TimeDelta();
remaining = end_time - TimeTicks::Now()) { remaining = end_time - subtle::TimeTicksNowIgnoringOverride()) {
// Truncate the timeout to milliseconds, rounded up to avoid spinning // Truncate the timeout to milliseconds, rounded up to avoid spinning
// (either by returning too early or because a < 1ms timeout on Windows // (either by returning too early or because a < 1ms timeout on Windows
// tends to return immediately). // tends to return immediately).
......
...@@ -87,27 +87,26 @@ class ScopedTaskEnvironment { ...@@ -87,27 +87,26 @@ class ScopedTaskEnvironment {
// Delayed tasks and Time/TimeTicks::Now() use the real-time system clock. // Delayed tasks and Time/TimeTicks::Now() use the real-time system clock.
SYSTEM_TIME, SYSTEM_TIME,
// Delayed tasks use a mock clock which only advances (in increments to the // Delayed tasks use a mock clock which only advances when reaching "idle"
// soonest delay) when reaching idle during a FastForward*() call to this // during RunLoop::Run() call on the main thread or a FastForward*() call to
// ScopedTaskEnvironment. Or when RunLoop::Run() goes idle on the main // this ScopedTaskEnvironment. "idle" is defined as the main thread and
// thread with no tasks remaining in the thread pool. // thread pool being out of ready tasks. When idle, time advances to the
// Note: this does not affect threads outside this ScopedTaskEnvironment's // soonest delay -- between main thread and thread pool delayed tasks --
// purview (notably: independent base::Thread's). // according to the semantics of the current Run*() or FastForward*().
MOCK_TIME,
// Mock Time/TimeTicks::Now() with the same mock clock used for delayed
// tasks. This is useful when a delayed task under test needs to check the
// amount of time that has passed since a previous sample of Now() (e.g.
// cache expiry).
// //
// Warning some platform APIs are still real-time, and don't interact with // This also mocks Time/TimeTicks::Now() with the same mock clock.
// MOCK_TIME as expected, e.g.:
// PlatformThread::Sleep
// WaitableEvent::TimedWait
// ConditionVariable::TimedWait
// //
// TODO(crbug.com/905412): Make MOCK_TIME always mock Time/TimeTicks::Now(). // Warning some platform APIs are still real-time, e.g.:
MOCK_TIME_AND_NOW, // * PlatformThread::Sleep
// * WaitableEvent::TimedWait
// * ConditionVariable::TimedWait
// * Delayed tasks on unmanaged base::Thread's and other custom task
// runners.
MOCK_TIME,
// TODO(gab): MOCK_TIME is now equivalent to MOCK_TIME_AND_NOW. Mass migrate
// users of MOCK_TIME_AND_NOW to MOCK_TIME.
MOCK_TIME_AND_NOW = MOCK_TIME,
// TODO(gab): Consider making MOCK_TIME the default mode. // TODO(gab): Consider making MOCK_TIME the default mode.
DEFAULT = SYSTEM_TIME DEFAULT = SYSTEM_TIME
......
...@@ -145,10 +145,11 @@ class BASE_EXPORT PlatformThread { ...@@ -145,10 +145,11 @@ class BASE_EXPORT PlatformThread {
// Yield the current thread so another thread can be scheduled. // Yield the current thread so another thread can be scheduled.
static void YieldCurrentThread(); static void YieldCurrentThread();
// Sleeps for the specified duration. Note: The sleep duration may be in // Sleeps for the specified duration (real-time; ignores time overrides).
// base::Time or base::TimeTicks, depending on platform. If you're looking to // Note: The sleep duration may be in base::Time or base::TimeTicks, depending
// use this in unit tests testing delayed tasks, this will be unreliable - // on platform. If you're looking to use this in unit tests testing delayed
// instead, use base::test::ScopedTaskEnvironment with MOCK_TIME mode. // tasks, this will be unreliable - instead, use
// base::test::ScopedTaskEnvironment with MOCK_TIME mode.
static void Sleep(base::TimeDelta duration); static void Sleep(base::TimeDelta duration);
// Sets the thread name visible to debuggers/tools. This will try to // Sets the thread name visible to debuggers/tools. This will try to
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "base/threading/scoped_blocking_call.h" #include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_id_name_manager.h" #include "base/threading/thread_id_name_manager.h"
#include "base/threading/thread_restrictions.h" #include "base/threading/thread_restrictions.h"
#include "base/time/time_override.h"
#include "base/win/scoped_handle.h" #include "base/win/scoped_handle.h"
#include "base/win/windows_version.h" #include "base/win/windows_version.h"
#include "build/build_config.h" #include "build/build_config.h"
...@@ -218,9 +219,13 @@ void PlatformThread::YieldCurrentThread() { ...@@ -218,9 +219,13 @@ void PlatformThread::YieldCurrentThread() {
void PlatformThread::Sleep(TimeDelta duration) { void PlatformThread::Sleep(TimeDelta duration) {
// When measured with a high resolution clock, Sleep() sometimes returns much // When measured with a high resolution clock, Sleep() sometimes returns much
// too early. We may need to call it repeatedly to get the desired duration. // too early. We may need to call it repeatedly to get the desired duration.
TimeTicks end = TimeTicks::Now() + duration; // PlatformThread::Sleep doesn't support mock-time, so this always uses
for (TimeTicks now = TimeTicks::Now(); now < end; now = TimeTicks::Now()) // real-time.
const TimeTicks end = subtle::TimeTicksNowIgnoringOverride() + duration;
for (TimeTicks now = subtle::TimeTicksNowIgnoringOverride(); now < end;
now = subtle::TimeTicksNowIgnoringOverride()) {
::Sleep(static_cast<DWORD>((end - now).InMillisecondsRoundedUp())); ::Sleep(static_cast<DWORD>((end - now).InMillisecondsRoundedUp()));
}
} }
// static // static
......
...@@ -6,6 +6,10 @@ ...@@ -6,6 +6,10 @@
namespace base { namespace base {
namespace {
bool g_mock_elapsed_timers_for_test = false;
} // namespace
ElapsedTimer::ElapsedTimer() : begin_(TimeTicks::Now()) {} ElapsedTimer::ElapsedTimer() : begin_(TimeTicks::Now()) {}
ElapsedTimer::ElapsedTimer(ElapsedTimer&& other) : begin_(other.begin_) {} ElapsedTimer::ElapsedTimer(ElapsedTimer&& other) : begin_(other.begin_) {}
...@@ -15,6 +19,8 @@ void ElapsedTimer::operator=(ElapsedTimer&& other) { ...@@ -15,6 +19,8 @@ void ElapsedTimer::operator=(ElapsedTimer&& other) {
} }
TimeDelta ElapsedTimer::Elapsed() const { TimeDelta ElapsedTimer::Elapsed() const {
if (g_mock_elapsed_timers_for_test)
return ScopedMockElapsedTimersForTest::kMockElapsedTime;
return TimeTicks::Now() - begin_; return TimeTicks::Now() - begin_;
} }
...@@ -23,7 +29,24 @@ ElapsedThreadTimer::ElapsedThreadTimer() ...@@ -23,7 +29,24 @@ ElapsedThreadTimer::ElapsedThreadTimer()
begin_(is_supported_ ? ThreadTicks::Now() : ThreadTicks()) {} begin_(is_supported_ ? ThreadTicks::Now() : ThreadTicks()) {}
TimeDelta ElapsedThreadTimer::Elapsed() const { TimeDelta ElapsedThreadTimer::Elapsed() const {
return is_supported_ ? (ThreadTicks::Now() - begin_) : TimeDelta(); if (!is_supported_)
return TimeDelta();
if (g_mock_elapsed_timers_for_test)
return ScopedMockElapsedTimersForTest::kMockElapsedTime;
return ThreadTicks::Now() - begin_;
}
// static
constexpr TimeDelta ScopedMockElapsedTimersForTest::kMockElapsedTime;
ScopedMockElapsedTimersForTest::ScopedMockElapsedTimersForTest() {
DCHECK(!g_mock_elapsed_timers_for_test);
g_mock_elapsed_timers_for_test = true;
}
ScopedMockElapsedTimersForTest::~ScopedMockElapsedTimersForTest() {
DCHECK(g_mock_elapsed_timers_for_test);
g_mock_elapsed_timers_for_test = false;
} }
} // namespace base } // namespace base
...@@ -49,6 +49,25 @@ class BASE_EXPORT ElapsedThreadTimer { ...@@ -49,6 +49,25 @@ class BASE_EXPORT ElapsedThreadTimer {
DISALLOW_COPY_AND_ASSIGN(ElapsedThreadTimer); DISALLOW_COPY_AND_ASSIGN(ElapsedThreadTimer);
}; };
// Whenever there's a ScopedMockElapsedTimersForTest in scope,
// Elapsed(Thread)Timers will always return kMockElapsedTime from Elapsed().
// This is useful, for example, in unit tests that verify that their impl
// records timing histograms. It enables such tests to observe reliable timings.
class BASE_EXPORT ScopedMockElapsedTimersForTest {
public:
static constexpr TimeDelta kMockElapsedTime =
TimeDelta::FromMilliseconds(1337);
// ScopedMockElapsedTimersForTest is not thread-safe (it must be instantiated
// in a test before other threads begin using ElapsedTimers; and it must
// conversely outlive any usage of ElapsedTimer in that test).
ScopedMockElapsedTimersForTest();
~ScopedMockElapsedTimersForTest();
private:
DISALLOW_COPY_AND_ASSIGN(ScopedMockElapsedTimersForTest);
};
} // namespace base } // namespace base
#endif // BASE_TIMER_ELAPSED_TIMER_H_ #endif // BASE_TIMER_ELAPSED_TIMER_H_
...@@ -27,6 +27,17 @@ TEST(ElapsedTimerTest, Simple) { ...@@ -27,6 +27,17 @@ TEST(ElapsedTimerTest, Simple) {
EXPECT_GE(timer.Elapsed(), 2 * kSleepDuration); EXPECT_GE(timer.Elapsed(), 2 * kSleepDuration);
} }
TEST(ElapsedTimerTest, Mocked) {
ScopedMockElapsedTimersForTest mock_elapsed_timer;
ElapsedTimer timer;
EXPECT_EQ(timer.Elapsed(), ScopedMockElapsedTimersForTest::kMockElapsedTime);
// Real-time doesn't matter.
PlatformThread::Sleep(kSleepDuration);
EXPECT_EQ(timer.Elapsed(), ScopedMockElapsedTimersForTest::kMockElapsedTime);
}
class ElapsedThreadTimerTest : public ::testing::Test { class ElapsedThreadTimerTest : public ::testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
...@@ -73,4 +84,18 @@ TEST_F(ElapsedThreadTimerTest, DoesNotCountSleep) { ...@@ -73,4 +84,18 @@ TEST_F(ElapsedThreadTimerTest, DoesNotCountSleep) {
EXPECT_LT(timer.Elapsed(), kSleepDuration); EXPECT_LT(timer.Elapsed(), kSleepDuration);
} }
TEST_F(ElapsedThreadTimerTest, Mocked) {
if (!ThreadTicks::IsSupported())
return;
ScopedMockElapsedTimersForTest mock_elapsed_timer;
ElapsedThreadTimer timer;
EXPECT_EQ(timer.Elapsed(), ScopedMockElapsedTimersForTest::kMockElapsedTime);
// Real-time doesn't matter.
PlatformThread::Sleep(kSleepDuration);
EXPECT_EQ(timer.Elapsed(), ScopedMockElapsedTimersForTest::kMockElapsedTime);
}
} // namespace base } // namespace base
...@@ -796,6 +796,7 @@ TEST_F(ParkableStringTest, OnlyOneAgingTask) { ...@@ -796,6 +796,7 @@ TEST_F(ParkableStringTest, OnlyOneAgingTask) {
} }
TEST_F(ParkableStringTest, ReportTotalUnparkingTime) { TEST_F(ParkableStringTest, ReportTotalUnparkingTime) {
base::ScopedMockElapsedTimersForTest mock_elapsed_timers;
base::HistogramTester histogram_tester; base::HistogramTester histogram_tester;
// On some platforms, initialization takes time, though it happens when // On some platforms, initialization takes time, though it happens when
...@@ -811,7 +812,8 @@ TEST_F(ParkableStringTest, ReportTotalUnparkingTime) { ...@@ -811,7 +812,8 @@ TEST_F(ParkableStringTest, ReportTotalUnparkingTime) {
ParkableString parkable(String(data.data(), data.size()).ReleaseImpl()); ParkableString parkable(String(data.data(), data.size()).ReleaseImpl());
ParkAndWait(parkable); ParkAndWait(parkable);
for (int i = 0; i < 10; ++i) { const int kNumIterations = 10;
for (int i = 0; i < kNumIterations; ++i) {
parkable.ToString(); parkable.ToString();
ASSERT_FALSE(parkable.Impl()->is_parked()); ASSERT_FALSE(parkable.Impl()->is_parked());
WaitForAging(); WaitForAging();
...@@ -822,16 +824,21 @@ TEST_F(ParkableStringTest, ReportTotalUnparkingTime) { ...@@ -822,16 +824,21 @@ TEST_F(ParkableStringTest, ReportTotalUnparkingTime) {
const size_t compressed_size = parkable.Impl()->compressed_size(); const size_t compressed_size = parkable.Impl()->compressed_size();
scoped_task_environment_.FastForwardUntilNoTasksRemain(); scoped_task_environment_.FastForwardUntilNoTasksRemain();
histogram_tester.ExpectTotalCount("Memory.ParkableString.MainThreadTime.5min",
1); // The string is unparked kNumIterations times.
histogram_tester.ExpectBucketCount( histogram_tester.ExpectUniqueSample(
"Memory.ParkableString.MainThreadTime.5min", 0, 0); "Memory.ParkableString.MainThreadTime.5min",
base::ScopedMockElapsedTimersForTest::kMockElapsedTime.InMilliseconds() *
kNumIterations,
1);
if (base::ThreadTicks::IsSupported()) { if (base::ThreadTicks::IsSupported()) {
histogram_tester.ExpectTotalCount( // The string is only compressed once despite the multiple parking/unparking
"Memory.ParkableString.ParkingThreadTime.5min", 1); // calls.
histogram_tester.ExpectBucketCount( histogram_tester.ExpectUniqueSample(
"Memory.ParkableString.ParkingThreadTime.5min", 0, 0); "Memory.ParkableString.ParkingThreadTime.5min",
base::ScopedMockElapsedTimersForTest::kMockElapsedTime.InMilliseconds(),
1);
} }
histogram_tester.ExpectUniqueSample("Memory.ParkableString.TotalSizeKb.5min", histogram_tester.ExpectUniqueSample("Memory.ParkableString.TotalSizeKb.5min",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment