From c4f865413a52a2048cfd7495c02607aecf9c4cf7 Mon Sep 17 00:00:00 2001 From: Bjorn A Mellem Date: Thu, 21 Nov 2019 10:37:18 -0800 Subject: [PATCH] Add TimeController to api/test/ and add a CreateTimeController API. Creates an abstraction for an "alarm clock" which can schedule time-controller callbacks and exposes a time controller driven by an external alarm. Bug: webrtc:9719 Change-Id: I08c2aa9dba25603043bfba48f55c925716a55bae Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/158969 Reviewed-by: Mirko Bonadei Reviewed-by: Per Kjellander Reviewed-by: Artem Titov Reviewed-by: Steve Anton Commit-Queue: Bjorn Mellem Cr-Commit-Position: refs/heads/master@{#29879} --- api/BUILD.gn | 46 ++++ api/test/DEPS | 5 + api/test/create_time_controller.cc | 24 ++ api/test/create_time_controller.h | 24 ++ api/test/create_time_controller_unittest.cc | 76 ++++++ .../test}/time_controller.h | 34 ++- test/network/BUILD.gn | 1 + test/network/network_emulation_manager.h | 2 +- test/scenario/BUILD.gn | 2 + test/scenario/call_client.h | 2 +- test/scenario/scenario.h | 2 +- test/time_controller/BUILD.gn | 5 +- .../external_time_controller.cc | 226 ++++++++++++++++++ .../external_time_controller.h | 70 ++++++ .../external_time_controller_unittest.cc | 181 ++++++++++++++ test/time_controller/real_time_controller.cc | 4 + test/time_controller/real_time_controller.h | 3 +- .../simulated_time_controller.cc | 4 + .../simulated_time_controller.h | 3 +- 19 files changed, 705 insertions(+), 9 deletions(-) create mode 100644 api/test/create_time_controller.cc create mode 100644 api/test/create_time_controller.h create mode 100644 api/test/create_time_controller_unittest.cc rename {test/time_controller => api/test}/time_controller.h (59%) create mode 100644 test/time_controller/external_time_controller.cc create mode 100644 test/time_controller/external_time_controller.h create mode 100644 test/time_controller/external_time_controller_unittest.cc diff --git a/api/BUILD.gn b/api/BUILD.gn index 6847a12905..1e53f3caab 100644 --- a/api/BUILD.gn +++ b/api/BUILD.gn @@ -946,6 +946,45 @@ if (rtc_include_tests) { ] } + rtc_library("time_controller") { + visibility = [ "*" ] + testonly = true + sources = [ + "test/time_controller.h", + ] + + deps = [ + "../modules:module_api", + "../modules/utility:utility", + "../rtc_base", + "../rtc_base:rtc_base_tests_utils", + "../rtc_base:rtc_event", + "../rtc_base/synchronization:sequence_checker", + "../rtc_base/synchronization:yield_policy", + "../rtc_base/task_utils:to_queued_task", + "../system_wrappers", + "task_queue", + "task_queue:default_task_queue_factory", + "units:time_delta", + "units:timestamp", + "//third_party/abseil-cpp/absl/strings", + ] + } + + rtc_library("create_time_controller") { + visibility = [ "*" ] + testonly = true + sources = [ + "test/create_time_controller.cc", + "test/create_time_controller.h", + ] + + deps = [ + ":time_controller", + "../test/time_controller", + ] + } + rtc_library("rtc_api_unittests") { testonly = true @@ -958,11 +997,13 @@ if (rtc_include_tests) { "rtp_packet_infos_unittest.cc", "rtp_parameters_unittest.cc", "scoped_refptr_unittest.cc", + "test/create_time_controller_unittest.cc", "test/loopback_media_transport_unittest.cc", ] deps = [ ":array_view", + ":create_time_controller", ":function_view", ":libjingle_peerconnection_api", ":loopback_media_transport", @@ -971,12 +1012,17 @@ if (rtc_include_tests) { ":rtp_packet_info", ":rtp_parameters", ":scoped_refptr", + ":time_controller", "../rtc_base:checks", "../rtc_base:gunit_helpers", "../rtc_base:rtc_base_approved", + "../rtc_base:rtc_task_queue", + "../rtc_base/task_utils:repeating_task", "../test:fileutils", "../test:test_support", "task_queue:task_queue_default_factory_unittests", + "units:time_delta", + "units:timestamp", "units:units_unittests", "video:video_unittests", ] diff --git a/api/test/DEPS b/api/test/DEPS index ec6085d253..1fced5d066 100644 --- a/api/test/DEPS +++ b/api/test/DEPS @@ -32,4 +32,9 @@ specific_include_rules = { "+rtc_base/thread.h", "+media/base/media_constants.h", ], + "time_controller\.h": [ + "+modules/utility/include/process_thread.h", + "+rtc_base/synchronization/yield_policy.h", + "+system_wrappers/include/clock.h", + ], } diff --git a/api/test/create_time_controller.cc b/api/test/create_time_controller.cc new file mode 100644 index 0000000000..1d6f24ebc6 --- /dev/null +++ b/api/test/create_time_controller.cc @@ -0,0 +1,24 @@ +/* + * Copyright 2019 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 "api/test/create_time_controller.h" + +#include + +#include "test/time_controller/external_time_controller.h" + +namespace webrtc { + +std::unique_ptr CreateTimeController( + ControlledAlarmClock* alarm) { + return std::make_unique(alarm); +} + +} // namespace webrtc diff --git a/api/test/create_time_controller.h b/api/test/create_time_controller.h new file mode 100644 index 0000000000..9c5b19bd41 --- /dev/null +++ b/api/test/create_time_controller.h @@ -0,0 +1,24 @@ +/* + * Copyright 2019 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 API_TEST_CREATE_TIME_CONTROLLER_H_ +#define API_TEST_CREATE_TIME_CONTROLLER_H_ + +#include + +#include "api/test/time_controller.h" + +namespace webrtc { + +std::unique_ptr CreateTimeController( + ControlledAlarmClock* alarm); + +} // namespace webrtc + +#endif // API_TEST_CREATE_TIME_CONTROLLER_H_ diff --git a/api/test/create_time_controller_unittest.cc b/api/test/create_time_controller_unittest.cc new file mode 100644 index 0000000000..e39a453da0 --- /dev/null +++ b/api/test/create_time_controller_unittest.cc @@ -0,0 +1,76 @@ +/* + * Copyright 2019 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 "api/test/create_time_controller.h" + +#include "api/test/time_controller.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +class FakeAlarm : public ControlledAlarmClock { + public: + explicit FakeAlarm(Timestamp start_time); + + Clock* GetClock() override; + bool ScheduleAlarmAt(Timestamp deadline) override; + void SetCallback(std::function callback) override; + void Sleep(TimeDelta duration) override; + + private: + SimulatedClock clock_; + Timestamp deadline_; + std::function callback_; +}; + +FakeAlarm::FakeAlarm(Timestamp start_time) + : clock_(start_time), + deadline_(Timestamp::PlusInfinity()), + callback_([] {}) {} + +Clock* FakeAlarm::GetClock() { + return &clock_; +} + +bool FakeAlarm::ScheduleAlarmAt(Timestamp deadline) { + if (deadline < deadline_) { + deadline_ = deadline; + return true; + } + return false; +} + +void FakeAlarm::SetCallback(std::function callback) { + callback_ = callback; +} + +void FakeAlarm::Sleep(TimeDelta duration) { + Timestamp end_time = clock_.CurrentTime() + duration; + + while (deadline_ <= end_time) { + clock_.AdvanceTime(deadline_ - clock_.CurrentTime()); + deadline_ = Timestamp::PlusInfinity(); + callback_(); + } + + clock_.AdvanceTime(end_time - clock_.CurrentTime()); +} + +TEST(CreateTimeControllerTest, CreatesNonNullController) { + FakeAlarm alarm(Timestamp::ms(100)); + EXPECT_NE(CreateTimeController(&alarm), nullptr); +} + +} // namespace +} // namespace webrtc diff --git a/test/time_controller/time_controller.h b/api/test/time_controller.h similarity index 59% rename from test/time_controller/time_controller.h rename to api/test/time_controller.h index 5d97e27ed2..4723716313 100644 --- a/test/time_controller/time_controller.h +++ b/api/test/time_controller.h @@ -7,15 +7,17 @@ * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ -#ifndef TEST_TIME_CONTROLLER_TIME_CONTROLLER_H_ -#define TEST_TIME_CONTROLLER_TIME_CONTROLLER_H_ +#ifndef API_TEST_TIME_CONTROLLER_H_ +#define API_TEST_TIME_CONTROLLER_H_ #include #include #include "api/task_queue/task_queue_factory.h" #include "api/units/time_delta.h" +#include "api/units/timestamp.h" #include "modules/utility/include/process_thread.h" +#include "rtc_base/synchronization/yield_policy.h" #include "system_wrappers/include/clock.h" namespace webrtc { @@ -42,6 +44,32 @@ class TimeController { // might yield to execute other tasks. This allows doing blocking waits on // tasks on other task queues froma a task queue without deadlocking. virtual void InvokeWithControlledYield(std::function closure) = 0; + // Returns a YieldInterface which can be installed as a ScopedYieldPolicy. + virtual rtc::YieldInterface* YieldInterface() = 0; }; + +// Interface for telling time, scheduling an event to fire at a particular time, +// and waiting for time to pass. +class ControlledAlarmClock { + public: + virtual ~ControlledAlarmClock() = default; + + // Gets a clock that tells the alarm clock's notion of time. + virtual Clock* GetClock() = 0; + + // Schedules the alarm to fire at |deadline|. + // An alarm clock only supports one deadline. Calls to |ScheduleAlarmAt| with + // an earlier deadline will reset the alarm to fire earlier.Calls to + // |ScheduleAlarmAt| with a later deadline are ignored. Returns true if the + // deadline changed, false otherwise. + virtual bool ScheduleAlarmAt(Timestamp deadline) = 0; + + // Sets the callback that should be run when the alarm fires. + virtual void SetCallback(std::function callback) = 0; + + // Waits for |duration| to pass, according to the alarm clock. + virtual void Sleep(TimeDelta duration) = 0; +}; + } // namespace webrtc -#endif // TEST_TIME_CONTROLLER_TIME_CONTROLLER_H_ +#endif // API_TEST_TIME_CONTROLLER_H_ diff --git a/test/network/BUILD.gn b/test/network/BUILD.gn index 19b8d94507..5cb70b1a8f 100644 --- a/test/network/BUILD.gn +++ b/test/network/BUILD.gn @@ -41,6 +41,7 @@ rtc_library("emulated_network") { deps = [ "../../api:network_emulation_manager_api", "../../api:simulated_network_api", + "../../api:time_controller", "../../api/units:data_rate", "../../api/units:data_size", "../../api/units:time_delta", diff --git a/test/network/network_emulation_manager.h b/test/network/network_emulation_manager.h index da5b29553b..f19ded8ee3 100644 --- a/test/network/network_emulation_manager.h +++ b/test/network/network_emulation_manager.h @@ -19,6 +19,7 @@ #include "api/test/network_emulation_manager.h" #include "api/test/simulated_network.h" +#include "api/test/time_controller.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "rtc_base/logging.h" @@ -33,7 +34,6 @@ #include "test/network/network_emulation.h" #include "test/network/simulated_network_node.h" #include "test/network/traffic_route.h" -#include "test/time_controller/time_controller.h" namespace webrtc { namespace test { diff --git a/test/scenario/BUILD.gn b/test/scenario/BUILD.gn index e26df6bbc4..5614c0bbee 100644 --- a/test/scenario/BUILD.gn +++ b/test/scenario/BUILD.gn @@ -84,6 +84,8 @@ if (rtc_include_tests) { "../../api:libjingle_peerconnection_api", "../../api:rtc_event_log_output_file", "../../api:rtp_parameters", + "../../api:time_controller", + "../../api:time_controller", "../../api:transport_api", "../../api/audio_codecs:builtin_audio_decoder_factory", "../../api/audio_codecs:builtin_audio_encoder_factory", diff --git a/test/scenario/call_client.h b/test/scenario/call_client.h index 19cafe8f7c..77c598609e 100644 --- a/test/scenario/call_client.h +++ b/test/scenario/call_client.h @@ -17,6 +17,7 @@ #include #include "api/rtc_event_log/rtc_event_log.h" +#include "api/test/time_controller.h" #include "call/call.h" #include "modules/audio_device/include/test_audio_device.h" #include "modules/congestion_controller/goog_cc/test/goog_cc_printer.h" @@ -28,7 +29,6 @@ #include "test/scenario/column_printer.h" #include "test/scenario/network_node.h" #include "test/scenario/scenario_config.h" -#include "test/time_controller/time_controller.h" namespace webrtc { diff --git a/test/scenario/scenario.h b/test/scenario/scenario.h index 33cf029715..b8b56d8a54 100644 --- a/test/scenario/scenario.h +++ b/test/scenario/scenario.h @@ -14,6 +14,7 @@ #include #include +#include "api/test/time_controller.h" #include "rtc_base/constructor_magic.h" #include "rtc_base/fake_clock.h" #include "rtc_base/task_queue.h" @@ -26,7 +27,6 @@ #include "test/scenario/network_node.h" #include "test/scenario/scenario_config.h" #include "test/scenario/video_stream.h" -#include "test/time_controller/time_controller.h" namespace webrtc { namespace test { diff --git a/test/time_controller/BUILD.gn b/test/time_controller/BUILD.gn index 469683d61e..14c11f3403 100644 --- a/test/time_controller/BUILD.gn +++ b/test/time_controller/BUILD.gn @@ -12,14 +12,16 @@ if (rtc_include_tests) { rtc_library("time_controller") { testonly = true sources = [ + "external_time_controller.cc", + "external_time_controller.h", "real_time_controller.cc", "real_time_controller.h", "simulated_time_controller.cc", "simulated_time_controller.h", - "time_controller.h", ] deps = [ + "../../api:time_controller", "../../api/task_queue", "../../api/task_queue:default_task_queue_factory", "../../api/units:time_delta", @@ -39,6 +41,7 @@ if (rtc_include_tests) { rtc_library("time_controller_unittests") { testonly = true sources = [ + "external_time_controller_unittest.cc", "simulated_time_controller_unittest.cc", ] deps = [ diff --git a/test/time_controller/external_time_controller.cc b/test/time_controller/external_time_controller.cc new file mode 100644 index 0000000000..543b4e09e1 --- /dev/null +++ b/test/time_controller/external_time_controller.cc @@ -0,0 +1,226 @@ +/* + * Copyright 2019 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 "test/time_controller/external_time_controller.h" + +#include +#include +#include +#include + +#include "api/task_queue/queued_task.h" +#include "api/task_queue/task_queue_base.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "modules/include/module.h" +#include "modules/utility/include/process_thread.h" +#include "rtc_base/synchronization/yield_policy.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +// Wraps a ProcessThread so that it can reschedule the time controller whenever +// an external call changes the ProcessThread's state. For example, when a new +// module is registered, the ProcessThread may need to be called sooner than the +// time controller's currently-scheduled deadline. +class ExternalTimeController::ProcessThreadWrapper : public ProcessThread { + public: + ProcessThreadWrapper(ExternalTimeController* parent, + std::unique_ptr thread) + : parent_(parent), thread_(std::move(thread)) {} + + void Start() override { + parent_->UpdateTime(); + thread_->Start(); + parent_->ScheduleNext(); + } + + void Stop() override { + parent_->UpdateTime(); + thread_->Stop(); + parent_->ScheduleNext(); + } + + void WakeUp(Module* module) override { + parent_->UpdateTime(); + thread_->WakeUp(GetWrapper(module)); + parent_->ScheduleNext(); + } + + void PostTask(std::unique_ptr task) override { + parent_->UpdateTime(); + thread_->PostTask(std::move(task)); + parent_->ScheduleNext(); + } + + void RegisterModule(Module* module, const rtc::Location& from) override { + parent_->UpdateTime(); + module_wrappers_.emplace(module, new ModuleWrapper(module, this)); + thread_->RegisterModule(GetWrapper(module), from); + parent_->ScheduleNext(); + } + + void DeRegisterModule(Module* module) override { + parent_->UpdateTime(); + thread_->DeRegisterModule(GetWrapper(module)); + parent_->ScheduleNext(); + module_wrappers_.erase(module); + } + + private: + class ModuleWrapper : public Module { + public: + ModuleWrapper(Module* module, ProcessThreadWrapper* thread) + : module_(module), thread_(thread) {} + + int64_t TimeUntilNextProcess() override { + return module_->TimeUntilNextProcess(); + } + + void Process() override { module_->Process(); } + + void ProcessThreadAttached(ProcessThread* process_thread) override { + if (process_thread) { + module_->ProcessThreadAttached(thread_); + } else { + module_->ProcessThreadAttached(nullptr); + } + } + + private: + Module* module_; + ProcessThreadWrapper* thread_; + }; + + ModuleWrapper* GetWrapper(Module* module) { + auto it = module_wrappers_.find(module); + RTC_DCHECK(it != module_wrappers_.end()); + return it->second.get(); + } + + ExternalTimeController* const parent_; + std::unique_ptr thread_; + std::map> module_wrappers_; +}; + +// Wraps a TaskQueue so that it can reschedule the time controller whenever +// an external call schedules a new task. +class ExternalTimeController::TaskQueueWrapper : public TaskQueueBase { + public: + TaskQueueWrapper(ExternalTimeController* parent, + std::unique_ptr base) + : parent_(parent), base_(std::move(base)) {} + + void PostTask(std::unique_ptr task) override { + parent_->UpdateTime(); + base_->PostTask(std::make_unique(std::move(task), this)); + parent_->ScheduleNext(); + } + + void PostDelayedTask(std::unique_ptr task, uint32_t ms) override { + parent_->UpdateTime(); + base_->PostDelayedTask(std::make_unique(std::move(task), this), + ms); + parent_->ScheduleNext(); + } + + void Delete() override { delete this; } + + private: + class TaskWrapper : public QueuedTask { + public: + TaskWrapper(std::unique_ptr task, TaskQueueWrapper* queue) + : task_(std::move(task)), queue_(queue) {} + + bool Run() override { + CurrentTaskQueueSetter current(queue_); + if (!task_->Run()) { + task_.release(); + } + // The wrapper should always be deleted, even if it releases the inner + // task, in order to avoid leaking wrappers. + return true; + } + + private: + std::unique_ptr task_; + TaskQueueWrapper* queue_; + }; + + ExternalTimeController* const parent_; + std::unique_ptr base_; +}; + +ExternalTimeController::ExternalTimeController(ControlledAlarmClock* alarm) + : alarm_(alarm), impl_(alarm_->GetClock()->CurrentTime()) { + global_clock_.SetTime(alarm_->GetClock()->CurrentTime()); + alarm_->SetCallback([this] { Run(); }); +} + +Clock* ExternalTimeController::GetClock() { + return alarm_->GetClock(); +} + +TaskQueueFactory* ExternalTimeController::GetTaskQueueFactory() { + return this; +} + +std::unique_ptr ExternalTimeController::CreateProcessThread( + const char* thread_name) { + return std::make_unique( + this, impl_.CreateProcessThread(thread_name)); +} + +void ExternalTimeController::Sleep(TimeDelta duration) { + alarm_->Sleep(duration); +} + +void ExternalTimeController::InvokeWithControlledYield( + std::function closure) { + rtc::ScopedYieldPolicy policy(YieldInterface()); + closure(); +} + +rtc::YieldInterface* ExternalTimeController::YieldInterface() { + return &impl_; +} + +std::unique_ptr +ExternalTimeController::CreateTaskQueue( + absl::string_view name, + TaskQueueFactory::Priority priority) const { + return std::unique_ptr( + new TaskQueueWrapper(const_cast(this), + impl_.CreateTaskQueue(name, priority))); +} + +void ExternalTimeController::Run() { + rtc::ScopedYieldPolicy yield_policy(&impl_); + UpdateTime(); + impl_.RunReadyRunners(); + ScheduleNext(); +} + +void ExternalTimeController::UpdateTime() { + Timestamp now = alarm_->GetClock()->CurrentTime(); + impl_.AdvanceTime(now); + global_clock_.SetTime(now); +} + +void ExternalTimeController::ScheduleNext() { + RTC_DCHECK_EQ(impl_.CurrentTime(), alarm_->GetClock()->CurrentTime()); + TimeDelta delay = + std::max(impl_.NextRunTime() - impl_.CurrentTime(), TimeDelta::Zero()); + if (delay.IsFinite()) { + alarm_->ScheduleAlarmAt(alarm_->GetClock()->CurrentTime() + delay); + } +} + +} // namespace webrtc diff --git a/test/time_controller/external_time_controller.h b/test/time_controller/external_time_controller.h new file mode 100644 index 0000000000..c9b1287197 --- /dev/null +++ b/test/time_controller/external_time_controller.h @@ -0,0 +1,70 @@ +/* + * Copyright 2019 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 TEST_TIME_CONTROLLER_EXTERNAL_TIME_CONTROLLER_H_ +#define TEST_TIME_CONTROLLER_EXTERNAL_TIME_CONTROLLER_H_ + +#include +#include + +#include "absl/strings/string_view.h" +#include "api/task_queue/task_queue_base.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/test/time_controller.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "modules/utility/include/process_thread.h" +#include "system_wrappers/include/clock.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +// TimeController implementation built on an external controlled alarm. +// This implementation is used to delegate scheduling and execution to an +// external run loop. +class ExternalTimeController : public TimeController, public TaskQueueFactory { + public: + explicit ExternalTimeController(ControlledAlarmClock* alarm); + + // Implementation of TimeController. + Clock* GetClock() override; + TaskQueueFactory* GetTaskQueueFactory() override; + std::unique_ptr CreateProcessThread( + const char* thread_name) override; + void Sleep(TimeDelta duration) override; + void InvokeWithControlledYield(std::function closure) override; + rtc::YieldInterface* YieldInterface() override; + + // Implementation of TaskQueueFactory. + std::unique_ptr CreateTaskQueue( + absl::string_view name, + TaskQueueFactory::Priority priority) const override; + + private: + class ProcessThreadWrapper; + class TaskQueueWrapper; + + // Executes any tasks scheduled at or before the current time. May call + // |ScheduleNext| to schedule the next call to |Run|. + void Run(); + + void UpdateTime(); + void ScheduleNext(); + + ControlledAlarmClock* alarm_; + sim_time_impl::SimulatedTimeControllerImpl impl_; + + // Overrides the global rtc::Clock to ensure that it reports the same times as + // the time controller. + rtc::ScopedBaseFakeClock global_clock_; +}; + +} // namespace webrtc + +#endif // TEST_TIME_CONTROLLER_EXTERNAL_TIME_CONTROLLER_H_ diff --git a/test/time_controller/external_time_controller_unittest.cc b/test/time_controller/external_time_controller_unittest.cc new file mode 100644 index 0000000000..95a07d9248 --- /dev/null +++ b/test/time_controller/external_time_controller_unittest.cc @@ -0,0 +1,181 @@ +/* + * Copyright 2019 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 "test/time_controller/external_time_controller.h" + +#include +#include +#include + +#include "rtc_base/event.h" +#include "rtc_base/task_queue.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "test/gmock.h" +#include "test/gtest.h" + +// NOTE: Since these tests rely on real time behavior, they will be flaky +// if run on heavily loaded systems. +namespace webrtc { +namespace { +using ::testing::AtLeast; +using ::testing::Invoke; +using ::testing::MockFunction; +using ::testing::NiceMock; +using ::testing::Return; +constexpr Timestamp kStartTime = Timestamp::Seconds<1000>(); + +class FakeAlarm : public ControlledAlarmClock { + public: + explicit FakeAlarm(Timestamp start_time); + + Clock* GetClock() override; + bool ScheduleAlarmAt(Timestamp deadline) override; + void SetCallback(std::function callback) override; + void Sleep(TimeDelta duration) override; + + private: + SimulatedClock clock_; + Timestamp deadline_; + std::function callback_; +}; + +FakeAlarm::FakeAlarm(Timestamp start_time) + : clock_(start_time), + deadline_(Timestamp::PlusInfinity()), + callback_([] {}) {} + +Clock* FakeAlarm::GetClock() { + return &clock_; +} + +bool FakeAlarm::ScheduleAlarmAt(Timestamp deadline) { + if (deadline < deadline_) { + deadline_ = deadline; + return true; + } + return false; +} + +void FakeAlarm::SetCallback(std::function callback) { + callback_ = callback; +} + +void FakeAlarm::Sleep(TimeDelta duration) { + Timestamp end_time = clock_.CurrentTime() + duration; + + while (deadline_ <= end_time) { + clock_.AdvanceTime(deadline_ - clock_.CurrentTime()); + deadline_ = Timestamp::PlusInfinity(); + callback_(); + } + + clock_.AdvanceTime(end_time - clock_.CurrentTime()); +} + +} // namespace + +TEST(ExternalTimeControllerTest, TaskIsStoppedOnStop) { + const TimeDelta kShortInterval = TimeDelta::ms(5); + const TimeDelta kLongInterval = TimeDelta::ms(20); + const int kShortIntervalCount = 4; + const int kMargin = 1; + FakeAlarm alarm(kStartTime); + ExternalTimeController time_simulation(&alarm); + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + std::atomic_int counter(0); + auto handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] { + if (++counter >= kShortIntervalCount) + return kLongInterval; + return kShortInterval; + }); + // Sleep long enough to go through the initial phase. + time_simulation.Sleep(kShortInterval * (kShortIntervalCount + kMargin)); + EXPECT_EQ(counter.load(), kShortIntervalCount); + + task_queue.PostTask( + [handle = std::move(handle)]() mutable { handle.Stop(); }); + + // Sleep long enough that the task would run at least once more if not + // stopped. + time_simulation.Sleep(kLongInterval * 2); + EXPECT_EQ(counter.load(), kShortIntervalCount); +} + +TEST(ExternalTimeControllerTest, TaskCanStopItself) { + std::atomic_int counter(0); + FakeAlarm alarm(kStartTime); + ExternalTimeController time_simulation(&alarm); + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + + RepeatingTaskHandle handle; + task_queue.PostTask([&] { + handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] { + ++counter; + handle.Stop(); + return TimeDelta::ms(2); + }); + }); + time_simulation.Sleep(TimeDelta::ms(10)); + EXPECT_EQ(counter.load(), 1); +} + +TEST(ExternalTimeControllerTest, YieldForTask) { + FakeAlarm alarm(kStartTime); + ExternalTimeController time_simulation(&alarm); + + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + + time_simulation.InvokeWithControlledYield([&] { + rtc::Event event; + task_queue.PostTask([&] { event.Set(); }); + EXPECT_TRUE(event.Wait(200)); + }); +} + +TEST(ExternalTimeControllerTest, TasksYieldToEachOther) { + FakeAlarm alarm(kStartTime); + ExternalTimeController time_simulation(&alarm); + + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + rtc::TaskQueue other_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "OtherQueue", TaskQueueFactory::Priority::NORMAL)); + + task_queue.PostTask([&] { + rtc::Event event; + other_queue.PostTask([&] { event.Set(); }); + EXPECT_TRUE(event.Wait(200)); + }); + + time_simulation.Sleep(TimeDelta::ms(300)); +} + +TEST(ExternalTimeControllerTest, CurrentTaskQueue) { + FakeAlarm alarm(kStartTime); + ExternalTimeController time_simulation(&alarm); + + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + + task_queue.PostTask([&] { EXPECT_TRUE(task_queue.IsCurrent()); }); + + time_simulation.Sleep(TimeDelta::ms(10)); +} + +} // namespace webrtc diff --git a/test/time_controller/real_time_controller.cc b/test/time_controller/real_time_controller.cc index 5e0044ead2..f9948eb422 100644 --- a/test/time_controller/real_time_controller.cc +++ b/test/time_controller/real_time_controller.cc @@ -39,6 +39,10 @@ void RealTimeController::InvokeWithControlledYield( closure(); } +rtc::YieldInterface* RealTimeController::YieldInterface() { + return nullptr; +} + RealTimeController* GlobalRealTimeController() { static RealTimeController* time_controller = new RealTimeController(); return time_controller; diff --git a/test/time_controller/real_time_controller.h b/test/time_controller/real_time_controller.h index f2d73bef37..20e6ff36fa 100644 --- a/test/time_controller/real_time_controller.h +++ b/test/time_controller/real_time_controller.h @@ -14,10 +14,10 @@ #include #include "api/task_queue/task_queue_factory.h" +#include "api/test/time_controller.h" #include "api/units/time_delta.h" #include "modules/utility/include/process_thread.h" #include "system_wrappers/include/clock.h" -#include "test/time_controller/time_controller.h" namespace webrtc { class RealTimeController : public TimeController { @@ -30,6 +30,7 @@ class RealTimeController : public TimeController { const char* thread_name) override; void Sleep(TimeDelta duration) override; void InvokeWithControlledYield(std::function closure) override; + rtc::YieldInterface* YieldInterface() override; private: std::unique_ptr task_queue_factory_; diff --git a/test/time_controller/simulated_time_controller.cc b/test/time_controller/simulated_time_controller.cc index 05f86b4e97..5064501024 100644 --- a/test/time_controller/simulated_time_controller.cc +++ b/test/time_controller/simulated_time_controller.cc @@ -440,6 +440,10 @@ void GlobalSimulatedTimeController::InvokeWithControlledYield( closure(); } +rtc::YieldInterface* GlobalSimulatedTimeController::YieldInterface() { + return &impl_; +} + // namespace sim_time_impl } // namespace webrtc diff --git a/test/time_controller/simulated_time_controller.h b/test/time_controller/simulated_time_controller.h index 214a150c57..a5802028bc 100644 --- a/test/time_controller/simulated_time_controller.h +++ b/test/time_controller/simulated_time_controller.h @@ -16,6 +16,7 @@ #include #include +#include "api/test/time_controller.h" #include "api/units/timestamp.h" #include "modules/include/module.h" #include "modules/utility/include/process_thread.h" @@ -24,7 +25,6 @@ #include "rtc_base/platform_thread_types.h" #include "rtc_base/synchronization/yield_policy.h" #include "rtc_base/thread_checker.h" -#include "test/time_controller/time_controller.h" namespace webrtc { @@ -92,6 +92,7 @@ class GlobalSimulatedTimeController : public TimeController { const char* thread_name) override; void Sleep(TimeDelta duration) override; void InvokeWithControlledYield(std::function closure) override; + rtc::YieldInterface* YieldInterface() override; private: rtc::ScopedBaseFakeClock global_clock_;