From b3c6810be3283963db5779a50f3df957b5881f91 Mon Sep 17 00:00:00 2001 From: Taylor Brandstetter Date: Fri, 27 May 2016 14:15:43 -0700 Subject: [PATCH] Adding the ability to use a simulated clock for unit tests. This will be useful for any tests that test objects with time-dependent behavior. It will allow such tests to be written in such a way that their outcome is more repeatable (less flaky), and will also allow such tests to finish quicker. For example, a test for STUN timeout doesn't need to wait the full timeout interval in real time; it can simply advance the simulated clock. BUG=webrtc:4925 R=pthatcher@webrtc.org Review URL: https://codereview.webrtc.org/1895933003 . Cr-Commit-Position: refs/heads/master@{#12950} --- webrtc/base/base_tests.gyp | 3 + webrtc/base/fakeclock.cc | 40 +++++++ webrtc/base/fakeclock.h | 43 ++++++++ webrtc/base/messagequeue.cc | 27 +++++ webrtc/base/messagequeue.h | 11 +- webrtc/base/timedelta.h | 128 ++++++++++++++++++++++ webrtc/base/timeutils.cc | 22 ++-- webrtc/base/timeutils.h | 20 ++++ webrtc/base/timeutils_unittest.cc | 174 ++++++++++++++++++++++++++++++ webrtc/base/timing.h | 1 + 10 files changed, 460 insertions(+), 9 deletions(-) create mode 100644 webrtc/base/fakeclock.cc create mode 100644 webrtc/base/fakeclock.h create mode 100644 webrtc/base/timedelta.h diff --git a/webrtc/base/base_tests.gyp b/webrtc/base/base_tests.gyp index 063e8e164f..3c0e912110 100644 --- a/webrtc/base/base_tests.gyp +++ b/webrtc/base/base_tests.gyp @@ -64,6 +64,8 @@ 'event_tracer_unittest.cc', 'event_unittest.cc', 'exp_filter_unittest.cc', + 'fakeclock.cc', + 'fakeclock.h', 'filerotatingstream_unittest.cc', 'fileutils_unittest.cc', 'helpers_unittest.cc', @@ -113,6 +115,7 @@ 'testclient_unittest.cc', 'thread_checker_unittest.cc', 'thread_unittest.cc', + 'timedelta.h', 'timeutils_unittest.cc', 'urlencode_unittest.cc', 'versionparsing_unittest.cc', diff --git a/webrtc/base/fakeclock.cc b/webrtc/base/fakeclock.cc new file mode 100644 index 0000000000..e5aa3bc0a4 --- /dev/null +++ b/webrtc/base/fakeclock.cc @@ -0,0 +1,40 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/base/fakeclock.h" + +#include "webrtc/base/checks.h" +#include "webrtc/base/messagequeue.h" + +namespace rtc { + +uint64_t FakeClock::TimeNanos() const { + CritScope cs(&lock_); + return time_; +} + +void FakeClock::SetTimeNanos(uint64_t nanos) { + { + CritScope cs(&lock_); + RTC_DCHECK(nanos >= time_); + time_ = nanos; + } + // If message queues are waiting in a socket select() with a timeout provided + // by the OS, they should wake up to check if there are any messages ready to + // be dispatched based on the fake time. + MessageQueueManager::WakeAllMessageQueues(); +} + +void FakeClock::AdvanceTime(TimeDelta delta) { + CritScope cs(&lock_); + SetTimeNanos(time_ + delta.ToNanoseconds()); +} + +} // namespace rtc diff --git a/webrtc/base/fakeclock.h b/webrtc/base/fakeclock.h new file mode 100644 index 0000000000..2b3afdde05 --- /dev/null +++ b/webrtc/base/fakeclock.h @@ -0,0 +1,43 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_FAKECLOCK_H_ +#define WEBRTC_BASE_FAKECLOCK_H_ + +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/timedelta.h" +#include "webrtc/base/timeutils.h" + +namespace rtc { + +// Fake clock for use with unit tests, which does not tick on its own. +// Starts at time 0. +class FakeClock : public ClockInterface { + public: + ~FakeClock() override {} + + // ClockInterface implementation. + uint64_t TimeNanos() const override; + + // Methods that can be used by the test to control the time. + + // Should only be used to set a time in the future. + void SetTimeNanos(uint64_t nanos); + + void AdvanceTime(TimeDelta delta); + + private: + CriticalSection lock_; + uint64_t time_ GUARDED_BY(lock_) = 0u; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_FAKECLOCK_H_ diff --git a/webrtc/base/messagequeue.cc b/webrtc/base/messagequeue.cc index 84fdaf1a95..da50e2304f 100644 --- a/webrtc/base/messagequeue.cc +++ b/webrtc/base/messagequeue.cc @@ -15,6 +15,11 @@ #include "webrtc/base/messagequeue.h" #include "webrtc/base/trace_event.h" +namespace { + +enum { MSG_WAKE_MESSAGE_QUEUE = 1 }; +} + namespace rtc { const int kMaxMsgLatency = 150; // 150 ms @@ -103,6 +108,28 @@ void MessageQueueManager::ClearInternal(MessageHandler *handler) { (*iter)->Clear(handler); } +void MessageQueueManager::WakeAllMessageQueues() { + if (!instance_) { + return; + } + return Instance()->WakeAllMessageQueuesInternal(); +} + +void MessageQueueManager::WakeAllMessageQueuesInternal() { +#if CS_DEBUG_CHECKS // CurrentThreadIsOwner returns true by default. + ASSERT(!crit_.CurrentThreadIsOwner()); // See note above. +#endif + CritScope cs(&crit_); + for (MessageQueue* queue : message_queues_) { + // Posting an arbitrary message will force the message queue to wake up. + queue->Post(this, MSG_WAKE_MESSAGE_QUEUE); + } +} + +void MessageQueueManager::OnMessage(Message* pmsg) { + RTC_DCHECK(pmsg->message_id == MSG_WAKE_MESSAGE_QUEUE); +} + //------------------------------------------------------------------ // MessageQueue MessageQueue::MessageQueue(SocketServer* ss, bool init_queue) diff --git a/webrtc/base/messagequeue.h b/webrtc/base/messagequeue.h index 03dfc98e13..3a5226cd0a 100644 --- a/webrtc/base/messagequeue.h +++ b/webrtc/base/messagequeue.h @@ -37,7 +37,7 @@ class MessageQueue; // MessageQueueManager does cleanup of of message queues -class MessageQueueManager { +class MessageQueueManager : public MessageHandler { public: static void Add(MessageQueue *message_queue); static void Remove(MessageQueue *message_queue); @@ -49,15 +49,22 @@ class MessageQueueManager { // MessageQueueManager instance when necessary. static bool IsInitialized(); + // Mainly for testing purposes, for use with a simulated clock. + // Posts a no-op event on all message queues so they will wake from the + // socket server select() and process messages again. + static void WakeAllMessageQueues(); + private: static MessageQueueManager* Instance(); MessageQueueManager(); - ~MessageQueueManager(); + ~MessageQueueManager() override; void AddInternal(MessageQueue *message_queue); void RemoveInternal(MessageQueue *message_queue); void ClearInternal(MessageHandler *handler); + void WakeAllMessageQueuesInternal(); + void OnMessage(Message* pmsg) override; static MessageQueueManager* instance_; // This list contains all live MessageQueues. diff --git a/webrtc/base/timedelta.h b/webrtc/base/timedelta.h new file mode 100644 index 0000000000..fe8e6aae2a --- /dev/null +++ b/webrtc/base/timedelta.h @@ -0,0 +1,128 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_TIMEDELTA_H_ +#define WEBRTC_BASE_TIMEDELTA_H_ + +#include "webrtc/base/basictypes.h" +#include "webrtc/base/timeutils.h" + +// Convenience class to convert between different units of relative time. +// Stores time to precision of nanoseconds, as int64_t internally. +// Doesn't check for overflow/underflow. +// +// Based on TimeDelta in: +// https://code.google.com/p/chromium/codesearch#chromium/src/base/time/time.h +namespace rtc { + +class TimeDelta { + public: + TimeDelta() : delta_(0) {} + + // Converts units of time to TimeDeltas. + static constexpr TimeDelta FromSeconds(int64_t secs) { + return TimeDelta(secs * kNumNanosecsPerSec); + } + static constexpr TimeDelta FromMilliseconds(int64_t ms) { + return TimeDelta(ms * kNumNanosecsPerMillisec); + } + static constexpr TimeDelta FromMicroseconds(int64_t us) { + return TimeDelta(us * kNumNanosecsPerMicrosec); + } + static constexpr TimeDelta FromNanoseconds(int64_t ns) { + return TimeDelta(ns); + } + + // Returns true if the time delta is zero. + bool is_zero() const { return delta_ == 0; } + + // Converts TimeDelta to units of time. + int64_t ToSeconds() const { return delta_ / kNumNanosecsPerSec; } + int64_t ToMilliseconds() const { return delta_ / kNumNanosecsPerMillisec; } + int64_t ToMicroseconds() const { return delta_ / kNumNanosecsPerMicrosec; } + int64_t ToNanoseconds() const { return delta_; } + + TimeDelta& operator=(TimeDelta other) { + delta_ = other.delta_; + return *this; + } + + // Computations with other deltas. + TimeDelta operator+(TimeDelta other) const { + return TimeDelta(delta_ + other.delta_); + } + TimeDelta operator-(TimeDelta other) const { + return TimeDelta(delta_ + other.delta_); + } + + TimeDelta& operator+=(TimeDelta other) { return *this = (*this + other); } + TimeDelta& operator-=(TimeDelta other) { return *this = (*this - other); } + TimeDelta operator-() const { return TimeDelta(-delta_); } + + // Computations with numeric types. + template + TimeDelta operator*(T a) const { + return TimeDelta(delta_ * a); + } + template + TimeDelta operator/(T a) const { + return TimeDelta(delta_ / a); + } + template + TimeDelta& operator*=(T a) { + return *this = (*this * a); + } + template + TimeDelta& operator/=(T a) { + return *this = (*this / a); + } + + TimeDelta operator%(TimeDelta a) const { + return TimeDelta(delta_ % a.delta_); + } + + // Comparison operators. + constexpr bool operator==(TimeDelta other) const { + return delta_ == other.delta_; + } + constexpr bool operator!=(TimeDelta other) const { + return delta_ != other.delta_; + } + constexpr bool operator<(TimeDelta other) const { + return delta_ < other.delta_; + } + constexpr bool operator<=(TimeDelta other) const { + return delta_ <= other.delta_; + } + constexpr bool operator>(TimeDelta other) const { + return delta_ > other.delta_; + } + constexpr bool operator>=(TimeDelta other) const { + return delta_ >= other.delta_; + } + + private: + // Constructs a delta given the duration in nanoseconds. This is private + // to avoid confusion by callers with an integer constructor. Use + // FromSeconds, FromMilliseconds, etc. instead. + constexpr explicit TimeDelta(int64_t delta_ns) : delta_(delta_ns) {} + + // Delta in nanoseconds. + int64_t delta_; +}; + +template +inline TimeDelta operator*(T a, TimeDelta td) { + return td * a; +} + +} // namespace rtc + +#endif // WEBRTC_BASE_TIMEDELTA_H_ diff --git a/webrtc/base/timeutils.cc b/webrtc/base/timeutils.cc index a9fe49d607..ecd0911a51 100644 --- a/webrtc/base/timeutils.cc +++ b/webrtc/base/timeutils.cc @@ -30,8 +30,17 @@ namespace rtc { +ClockInterface* g_clock = nullptr; + +void SetClock(ClockInterface* clock) { + g_clock = clock; +} + uint64_t TimeNanos() { - int64_t ticks = 0; + if (g_clock) { + return g_clock->TimeNanos(); + } + int64_t ticks; #if defined(WEBRTC_MAC) static mach_timebase_info_data_t timebase; if (timebase.denom == 0) { @@ -45,8 +54,8 @@ uint64_t TimeNanos() { ticks = mach_absolute_time() * timebase.numer / timebase.denom; #elif defined(WEBRTC_POSIX) struct timespec ts; - // TODO: Do we need to handle the case when CLOCK_MONOTONIC - // is not supported? + // TODO(deadbeef): Do we need to handle the case when CLOCK_MONOTONIC is not + // supported? clock_gettime(CLOCK_MONOTONIC, &ts); ticks = kNumNanosecsPerSec * static_cast(ts.tv_sec) + static_cast(ts.tv_nsec); @@ -58,8 +67,7 @@ uint64_t TimeNanos() { // Atomically update the last gotten time DWORD old = InterlockedExchange(last_timegettime_ptr, now); if (now < old) { - // If now is earlier than old, there may have been a race between - // threads. + // If now is earlier than old, there may have been a race between threads. // 0x0fffffff ~3.1 days, the code will not take that long to execute // so it must have been a wrap around. if (old > 0xf0000000 && now < 0x0fffffff) { @@ -67,8 +75,8 @@ uint64_t TimeNanos() { } } ticks = now + (num_wrap_timegettime << 32); - // TODO: Calculate with nanosecond precision. Otherwise, we're just - // wasting a multiply and divide when doing Time() on Windows. + // TODO(deadbeef): Calculate with nanosecond precision. Otherwise, we're + // just wasting a multiply and divide when doing Time() on Windows. ticks = ticks * kNumNanosecsPerMillisec; #else #error Unsupported platform. diff --git a/webrtc/base/timeutils.h b/webrtc/base/timeutils.h index 222d5c26e4..78ebacee38 100644 --- a/webrtc/base/timeutils.h +++ b/webrtc/base/timeutils.h @@ -31,6 +31,26 @@ static const int64_t kNumNanosecsPerMicrosec = // TODO(honghaiz): Define a type for the time value specifically. +class ClockInterface { + public: + virtual ~ClockInterface() {} + virtual uint64_t TimeNanos() const = 0; +}; + +// Sets the global source of time. This is useful mainly for unit tests. +// +// Does not transfer ownership of the clock. +// SetClock(nullptr) should be called before the ClockInterface is deleted. +// +// This method is not thread-safe; it should only be used when no other thread +// is running (for example, at the start/end of a unit test, or start/end of +// main()). +// +// TODO(deadbeef): Instead of having functions that access this global +// ClockInterface, we may want to pass the ClockInterface into everything +// that uses it, eliminating the need for a global variable and this function. +void SetClock(ClockInterface* clock); + // Returns the current time in milliseconds in 32 bits. uint32_t Time32(); diff --git a/webrtc/base/timeutils_unittest.cc b/webrtc/base/timeutils_unittest.cc index 0971c037fa..f183684e6c 100644 --- a/webrtc/base/timeutils_unittest.cc +++ b/webrtc/base/timeutils_unittest.cc @@ -9,6 +9,8 @@ */ #include "webrtc/base/common.h" +#include "webrtc/base/event.h" +#include "webrtc/base/fakeclock.h" #include "webrtc/base/gunit.h" #include "webrtc/base/helpers.h" #include "webrtc/base/thread.h" @@ -205,4 +207,176 @@ TEST_F(TmToSeconds, TestTmToSeconds) { TestTmToSeconds(100000); } +TEST(TimeDelta, FromAndTo) { + EXPECT_TRUE(TimeDelta::FromSeconds(2) == TimeDelta::FromMilliseconds(2000)); + EXPECT_TRUE(TimeDelta::FromMilliseconds(3) == + TimeDelta::FromMicroseconds(3000)); + EXPECT_TRUE(TimeDelta::FromMicroseconds(4) == + TimeDelta::FromNanoseconds(4000)); + EXPECT_EQ(13, TimeDelta::FromSeconds(13).ToSeconds()); + EXPECT_EQ(13, TimeDelta::FromMilliseconds(13).ToMilliseconds()); + EXPECT_EQ(13, TimeDelta::FromMicroseconds(13).ToMicroseconds()); + EXPECT_EQ(13, TimeDelta::FromNanoseconds(13).ToNanoseconds()); +} + +TEST(TimeDelta, ComparisonOperators) { + EXPECT_LT(TimeDelta::FromSeconds(1), TimeDelta::FromSeconds(2)); + EXPECT_EQ(TimeDelta::FromSeconds(3), TimeDelta::FromSeconds(3)); + EXPECT_GT(TimeDelta::FromSeconds(5), TimeDelta::FromSeconds(4)); +} + +TEST(TimeDelta, NumericOperators) { + double d = 0.5; + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + TimeDelta::FromMilliseconds(1000) * d); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + TimeDelta::FromMilliseconds(1000) / d); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + TimeDelta::FromMilliseconds(1000) *= d); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + TimeDelta::FromMilliseconds(1000) /= d); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + d * TimeDelta::FromMilliseconds(1000)); + + float f = 0.5; + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + TimeDelta::FromMilliseconds(1000) * f); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + TimeDelta::FromMilliseconds(1000) / f); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + TimeDelta::FromMilliseconds(1000) *= f); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + TimeDelta::FromMilliseconds(1000) /= f); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + f * TimeDelta::FromMilliseconds(1000)); + + int i = 2; + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + TimeDelta::FromMilliseconds(1000) * i); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + TimeDelta::FromMilliseconds(1000) / i); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + TimeDelta::FromMilliseconds(1000) *= i); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + TimeDelta::FromMilliseconds(1000) /= i); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + i * TimeDelta::FromMilliseconds(1000)); + + int64_t i64 = 2; + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + TimeDelta::FromMilliseconds(1000) * i64); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + TimeDelta::FromMilliseconds(1000) / i64); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + TimeDelta::FromMilliseconds(1000) *= i64); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + TimeDelta::FromMilliseconds(1000) /= i64); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + i64 * TimeDelta::FromMilliseconds(1000)); + + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + TimeDelta::FromMilliseconds(1000) * 0.5); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + TimeDelta::FromMilliseconds(1000) / 0.5); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + TimeDelta::FromMilliseconds(1000) *= 0.5); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + TimeDelta::FromMilliseconds(1000) /= 0.5); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + 0.5 * TimeDelta::FromMilliseconds(1000)); + + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + TimeDelta::FromMilliseconds(1000) * 2); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + TimeDelta::FromMilliseconds(1000) / 2); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + TimeDelta::FromMilliseconds(1000) *= 2); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), + TimeDelta::FromMilliseconds(1000) /= 2); + EXPECT_EQ(TimeDelta::FromMilliseconds(2000), + 2 * TimeDelta::FromMilliseconds(1000)); +} + +// Test that all the time functions exposed by TimeUtils get time from the +// fake clock when it's set. +TEST(FakeClock, TimeFunctionsUseFakeClock) { + FakeClock clock; + SetClock(&clock); + + clock.SetTimeNanos(987654321u); + EXPECT_EQ(987u, Time32()); + EXPECT_EQ(987, TimeMillis()); + EXPECT_EQ(987654u, TimeMicros()); + EXPECT_EQ(987654321u, TimeNanos()); + EXPECT_EQ(1000u, TimeAfter(13)); + + SetClock(nullptr); + // After it's unset, we should get a normal time. + EXPECT_NE(987, TimeMillis()); +} + +TEST(FakeClock, InitialTime) { + FakeClock clock; + EXPECT_EQ(0u, clock.TimeNanos()); +} + +TEST(FakeClock, SetTimeNanos) { + FakeClock clock; + clock.SetTimeNanos(123u); + EXPECT_EQ(123u, clock.TimeNanos()); + clock.SetTimeNanos(456u); + EXPECT_EQ(456u, clock.TimeNanos()); +} + +TEST(FakeClock, AdvanceTime) { + FakeClock clock; + clock.AdvanceTime(TimeDelta::FromNanoseconds(1111u)); + EXPECT_EQ(1111u, clock.TimeNanos()); + clock.AdvanceTime(TimeDelta::FromMicroseconds(2222u)); + EXPECT_EQ(2223111u, clock.TimeNanos()); + clock.AdvanceTime(TimeDelta::FromMilliseconds(3333u)); + EXPECT_EQ(3335223111u, clock.TimeNanos()); + clock.AdvanceTime(TimeDelta::FromSeconds(4444u)); + EXPECT_EQ(4447335223111u, clock.TimeNanos()); +} + +// When the clock is advanced, threads that are waiting in a socket select +// should wake up and look at the new time. This allows tests using the +// fake clock to run much faster, if the test is bound by time constraints +// (such as a test for a STUN ping timeout). +TEST(FakeClock, SettingTimeWakesThreads) { + int64_t real_start_time_ms = TimeMillis(); + + FakeClock clock; + SetClock(&clock); + + Thread worker; + worker.Start(); + + // Post an event that won't be executed for 10 seconds. + Event message_handler_dispatched(false, false); + auto functor = [&message_handler_dispatched] { + message_handler_dispatched.Set(); + }; + FunctorMessageHandler handler(functor); + worker.PostDelayed(10000, &handler); + + // Wait for a bit for the worker thread to be started and enter its socket + // select(). + Thread::Current()->SleepMs(1000); + + // Advance the fake clock, expecting the worker thread to wake up + // and dispatch the message quickly. + clock.AdvanceTime(TimeDelta::FromSeconds(10u)); + message_handler_dispatched.Wait(Event::kForever); + worker.Stop(); + + SetClock(nullptr); + + // The message should have been dispatched long before the 10 seconds fully + // elapsed. + int64_t real_end_time_ms = TimeMillis(); + EXPECT_LT(real_end_time_ms - real_start_time_ms, 2000); +} + } // namespace rtc diff --git a/webrtc/base/timing.h b/webrtc/base/timing.h index e709f883f3..a73c57d986 100644 --- a/webrtc/base/timing.h +++ b/webrtc/base/timing.h @@ -13,6 +13,7 @@ namespace rtc { +// TODO(deadbeef): Remove this and use ClockInterface instead. class Timing { public: Timing();