diff options
Diffstat (limited to 'third_party/libwebrtc/test/time_controller')
14 files changed, 1646 insertions, 0 deletions
diff --git a/third_party/libwebrtc/test/time_controller/BUILD.gn b/third_party/libwebrtc/test/time_controller/BUILD.gn new file mode 100644 index 0000000000..b4b368a42a --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/BUILD.gn @@ -0,0 +1,70 @@ +# Copyright (c) 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. + +import("../../webrtc.gni") + +rtc_library("time_controller") { + testonly = true + sources = [ + "external_time_controller.cc", + "external_time_controller.h", + "real_time_controller.cc", + "real_time_controller.h", + "simulated_task_queue.cc", + "simulated_task_queue.h", + "simulated_thread.cc", + "simulated_thread.h", + "simulated_time_controller.cc", + "simulated_time_controller.h", + ] + + deps = [ + "../../api:sequence_checker", + "../../api:time_controller", + "../../api/task_queue", + "../../api/task_queue:default_task_queue_factory", + "../../api/units:time_delta", + "../../api/units:timestamp", + "../../rtc_base:checks", + "../../rtc_base:null_socket_server", + "../../rtc_base:platform_thread_types", + "../../rtc_base:rtc_base_tests_utils", + "../../rtc_base:rtc_event", + "../../rtc_base/synchronization:mutex", + "../../rtc_base/synchronization:yield_policy", + "../../system_wrappers", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/functional:any_invocable", + "//third_party/abseil-cpp/absl/strings", + ] +} + +if (rtc_include_tests) { + rtc_library("time_controller_unittests") { + testonly = true + sources = [ + "external_time_controller_unittest.cc", + "simulated_time_controller_unittest.cc", + "time_controller_conformance_test.cc", + ] + deps = [ + ":time_controller", + "../:test_support", + "../../api:time_controller", + "../../api/units:time_delta", + "../../rtc_base:macromagic", + "../../rtc_base:rtc_event", + "../../rtc_base:rtc_task_queue", + "../../rtc_base:task_queue_for_test", + "../../rtc_base:threading", + "../../rtc_base/synchronization:mutex", + "../../rtc_base/task_utils:repeating_task", + ] + } +} diff --git a/third_party/libwebrtc/test/time_controller/external_time_controller.cc b/third_party/libwebrtc/test/time_controller/external_time_controller.cc new file mode 100644 index 0000000000..41f36eaaef --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/external_time_controller.cc @@ -0,0 +1,135 @@ +/* + * 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 <algorithm> +#include <map> +#include <memory> +#include <utility> + +#include "absl/functional/any_invocable.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 "rtc_base/checks.h" +#include "rtc_base/synchronization/yield_policy.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +// 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<TaskQueueBase, TaskQueueDeleter> base) + : parent_(parent), base_(std::move(base)) {} + + void PostTaskImpl(absl::AnyInvocable<void() &&> task, + const PostTaskTraits& traits, + const Location& location) override { + parent_->UpdateTime(); + base_->PostTask(TaskWrapper(std::move(task))); + parent_->ScheduleNext(); + } + + void PostDelayedTaskImpl(absl::AnyInvocable<void() &&> task, + TimeDelta delay, + const PostDelayedTaskTraits& traits, + const Location& location) override { + parent_->UpdateTime(); + if (traits.high_precision) { + base_->PostDelayedHighPrecisionTask(TaskWrapper(std::move(task)), delay); + } else { + base_->PostDelayedTask(TaskWrapper(std::move(task)), delay); + } + parent_->ScheduleNext(); + } + + void Delete() override { delete this; } + + private: + absl::AnyInvocable<void() &&> TaskWrapper( + absl::AnyInvocable<void() &&> task) { + return [task = std::move(task), this]() mutable { + CurrentTaskQueueSetter current(this); + std::move(task)(); + }; + } + + ExternalTimeController* const parent_; + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> base_; +}; + +ExternalTimeController::ExternalTimeController(ControlledAlarmClock* alarm) + : alarm_(alarm), + impl_(alarm_->GetClock()->CurrentTime()), + yield_policy_(&impl_) { + global_clock_.SetTime(alarm_->GetClock()->CurrentTime()); + alarm_->SetCallback([this] { Run(); }); +} + +Clock* ExternalTimeController::GetClock() { + return alarm_->GetClock(); +} + +TaskQueueFactory* ExternalTimeController::GetTaskQueueFactory() { + return this; +} + +void ExternalTimeController::AdvanceTime(TimeDelta duration) { + alarm_->Sleep(duration); +} + +std::unique_ptr<rtc::Thread> ExternalTimeController::CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) { + RTC_DCHECK_NOTREACHED(); + return nullptr; +} + +rtc::Thread* ExternalTimeController::GetMainThread() { + RTC_DCHECK_NOTREACHED(); + return nullptr; +} + +std::unique_ptr<TaskQueueBase, TaskQueueDeleter> +ExternalTimeController::CreateTaskQueue( + absl::string_view name, + TaskQueueFactory::Priority priority) const { + return std::unique_ptr<TaskQueueBase, TaskQueueDeleter>( + new TaskQueueWrapper(const_cast<ExternalTimeController*>(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/third_party/libwebrtc/test/time_controller/external_time_controller.h b/third_party/libwebrtc/test/time_controller/external_time_controller.h new file mode 100644 index 0000000000..a67f2557b4 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/external_time_controller.h @@ -0,0 +1,69 @@ +/* + * 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 <functional> +#include <memory> + +#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 "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; + void AdvanceTime(TimeDelta duration) override; + std::unique_ptr<rtc::Thread> CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) override; + rtc::Thread* GetMainThread() override; + + // Implementation of TaskQueueFactory. + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> CreateTaskQueue( + absl::string_view name, + TaskQueueFactory::Priority priority) const override; + + private: + 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_; + rtc::ScopedYieldPolicy yield_policy_; + + // 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/third_party/libwebrtc/test/time_controller/external_time_controller_unittest.cc b/third_party/libwebrtc/test/time_controller/external_time_controller_unittest.cc new file mode 100644 index 0000000000..13d63fe8ed --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/external_time_controller_unittest.cc @@ -0,0 +1,179 @@ +/* + * 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 <atomic> +#include <memory> +#include <utility> + +#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<void()> callback) override; + void Sleep(TimeDelta duration) override; + + private: + SimulatedClock clock_; + Timestamp deadline_; + std::function<void()> 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<void()> 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::Millis(5); + const TimeDelta kLongInterval = TimeDelta::Millis(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.AdvanceTime(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.AdvanceTime(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::Millis(2); + }); + }); + time_simulation.AdvanceTime(TimeDelta::Millis(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)); + + rtc::Event event; + task_queue.PostTask([&] { event.Set(); }); + EXPECT_TRUE(event.Wait(TimeDelta::Millis(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(TimeDelta::Millis(200))); + }); + + time_simulation.AdvanceTime(TimeDelta::Millis(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.AdvanceTime(TimeDelta::Millis(10)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/time_controller/real_time_controller.cc b/third_party/libwebrtc/test/time_controller/real_time_controller.cc new file mode 100644 index 0000000000..7cc750d6d4 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/real_time_controller.cc @@ -0,0 +1,66 @@ +/* + * 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/real_time_controller.h" + +#include "api/task_queue/default_task_queue_factory.h" +#include "rtc_base/null_socket_server.h" + +namespace webrtc { +namespace { +class MainThread : public rtc::Thread { + public: + MainThread() + : Thread(std::make_unique<rtc::NullSocketServer>(), false), + current_setter_(this) { + DoInit(); + } + ~MainThread() { + Stop(); + DoDestroy(); + } + + private: + CurrentThreadSetter current_setter_; +}; +} // namespace +RealTimeController::RealTimeController() + : task_queue_factory_(CreateDefaultTaskQueueFactory()), + main_thread_(std::make_unique<MainThread>()) { + main_thread_->SetName("Main", this); +} + +Clock* RealTimeController::GetClock() { + return Clock::GetRealTimeClock(); +} + +TaskQueueFactory* RealTimeController::GetTaskQueueFactory() { + return task_queue_factory_.get(); +} + +std::unique_ptr<rtc::Thread> RealTimeController::CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) { + if (!socket_server) + socket_server = std::make_unique<rtc::NullSocketServer>(); + auto res = std::make_unique<rtc::Thread>(std::move(socket_server)); + res->SetName(name, nullptr); + res->Start(); + return res; +} + +rtc::Thread* RealTimeController::GetMainThread() { + return main_thread_.get(); +} + +void RealTimeController::AdvanceTime(TimeDelta duration) { + main_thread_->ProcessMessages(duration.ms()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/time_controller/real_time_controller.h b/third_party/libwebrtc/test/time_controller/real_time_controller.h new file mode 100644 index 0000000000..5f02eaf85f --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/real_time_controller.h @@ -0,0 +1,41 @@ +/* + * 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_REAL_TIME_CONTROLLER_H_ +#define TEST_TIME_CONTROLLER_REAL_TIME_CONTROLLER_H_ + +#include <functional> +#include <memory> + +#include "api/task_queue/task_queue_factory.h" +#include "api/test/time_controller.h" +#include "api/units/time_delta.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { +class RealTimeController : public TimeController { + public: + RealTimeController(); + + Clock* GetClock() override; + TaskQueueFactory* GetTaskQueueFactory() override; + std::unique_ptr<rtc::Thread> CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) override; + rtc::Thread* GetMainThread() override; + void AdvanceTime(TimeDelta duration) override; + + private: + const std::unique_ptr<TaskQueueFactory> task_queue_factory_; + const std::unique_ptr<rtc::Thread> main_thread_; +}; + +} // namespace webrtc + +#endif // TEST_TIME_CONTROLLER_REAL_TIME_CONTROLLER_H_ diff --git a/third_party/libwebrtc/test/time_controller/simulated_task_queue.cc b/third_party/libwebrtc/test/time_controller/simulated_task_queue.cc new file mode 100644 index 0000000000..66b3fd8087 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_task_queue.cc @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020 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/simulated_task_queue.h" + +#include <algorithm> +#include <utility> + +namespace webrtc { + +SimulatedTaskQueue::SimulatedTaskQueue( + sim_time_impl::SimulatedTimeControllerImpl* handler, + absl::string_view name) + : handler_(handler), name_(new char[name.size()]) { + std::copy_n(name.begin(), name.size(), name_); +} + +SimulatedTaskQueue::~SimulatedTaskQueue() { + handler_->Unregister(this); + delete[] name_; +} + +void SimulatedTaskQueue::Delete() { + // Need to destroy the tasks outside of the lock because task destruction + // can lead to re-entry in SimulatedTaskQueue via custom destructors. + std::deque<absl::AnyInvocable<void() &&>> ready_tasks; + std::map<Timestamp, std::vector<absl::AnyInvocable<void() &&>>> delayed_tasks; + { + MutexLock lock(&lock_); + ready_tasks_.swap(ready_tasks); + delayed_tasks_.swap(delayed_tasks); + } + ready_tasks.clear(); + delayed_tasks.clear(); + delete this; +} + +void SimulatedTaskQueue::RunReady(Timestamp at_time) { + MutexLock lock(&lock_); + for (auto it = delayed_tasks_.begin(); + it != delayed_tasks_.end() && it->first <= at_time; + it = delayed_tasks_.erase(it)) { + for (auto& task : it->second) { + ready_tasks_.push_back(std::move(task)); + } + } + CurrentTaskQueueSetter set_current(this); + while (!ready_tasks_.empty()) { + absl::AnyInvocable<void()&&> ready = std::move(ready_tasks_.front()); + ready_tasks_.pop_front(); + lock_.Unlock(); + std::move(ready)(); + ready = nullptr; + lock_.Lock(); + } + if (!delayed_tasks_.empty()) { + next_run_time_ = delayed_tasks_.begin()->first; + } else { + next_run_time_ = Timestamp::PlusInfinity(); + } +} + +void SimulatedTaskQueue::PostTaskImpl(absl::AnyInvocable<void() &&> task, + const PostTaskTraits& /*traits*/, + const Location& /*location*/) { + MutexLock lock(&lock_); + ready_tasks_.push_back(std::move(task)); + next_run_time_ = Timestamp::MinusInfinity(); +} + +void SimulatedTaskQueue::PostDelayedTaskImpl( + absl::AnyInvocable<void() &&> task, + TimeDelta delay, + const PostDelayedTaskTraits& /*traits*/, + const Location& /*location*/) { + MutexLock lock(&lock_); + Timestamp target_time = handler_->CurrentTime() + delay; + delayed_tasks_[target_time].push_back(std::move(task)); + next_run_time_ = std::min(next_run_time_, target_time); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/time_controller/simulated_task_queue.h b/third_party/libwebrtc/test/time_controller/simulated_task_queue.h new file mode 100644 index 0000000000..6ca46327b2 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_task_queue.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020 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_SIMULATED_TASK_QUEUE_H_ +#define TEST_TIME_CONTROLLER_SIMULATED_TASK_QUEUE_H_ + +#include <deque> +#include <map> +#include <memory> +#include <vector> + +#include "absl/functional/any_invocable.h" +#include "api/units/time_delta.h" +#include "rtc_base/synchronization/mutex.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +class SimulatedTaskQueue : public TaskQueueBase, + public sim_time_impl::SimulatedSequenceRunner { + public: + SimulatedTaskQueue(sim_time_impl::SimulatedTimeControllerImpl* handler, + absl::string_view name); + + ~SimulatedTaskQueue(); + + void RunReady(Timestamp at_time) override; + + Timestamp GetNextRunTime() const override { + MutexLock lock(&lock_); + return next_run_time_; + } + TaskQueueBase* GetAsTaskQueue() override { return this; } + + // TaskQueueBase interface + void Delete() override; + void PostTaskImpl(absl::AnyInvocable<void() &&> task, + const PostTaskTraits& traits, + const Location& location) override; + void PostDelayedTaskImpl(absl::AnyInvocable<void() &&> task, + TimeDelta delay, + const PostDelayedTaskTraits& traits, + const Location& location) override; + + private: + sim_time_impl::SimulatedTimeControllerImpl* const handler_; + // Using char* to be debugger friendly. + char* name_; + + mutable Mutex lock_; + + std::deque<absl::AnyInvocable<void() &&>> ready_tasks_ RTC_GUARDED_BY(lock_); + std::map<Timestamp, std::vector<absl::AnyInvocable<void() &&>>> delayed_tasks_ + RTC_GUARDED_BY(lock_); + + Timestamp next_run_time_ RTC_GUARDED_BY(lock_) = Timestamp::PlusInfinity(); +}; + +} // namespace webrtc + +#endif // TEST_TIME_CONTROLLER_SIMULATED_TASK_QUEUE_H_ diff --git a/third_party/libwebrtc/test/time_controller/simulated_thread.cc b/third_party/libwebrtc/test/time_controller/simulated_thread.cc new file mode 100644 index 0000000000..e8a5a22a71 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_thread.cc @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2020 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/simulated_thread.h" + +#include <algorithm> +#include <utility> + +namespace webrtc { +namespace { + +// A socket server that does nothing. It's different from NullSocketServer in +// that it does allow sleep/wakeup. This avoids usage of an Event instance which +// otherwise would cause issues with the simulated Yeild behavior. +class DummySocketServer : public rtc::SocketServer { + public: + rtc::Socket* CreateSocket(int family, int type) override { + RTC_DCHECK_NOTREACHED(); + return nullptr; + } + bool Wait(TimeDelta max_wait_duration, bool process_io) override { + RTC_CHECK(max_wait_duration.IsZero()); + return true; + } + void WakeUp() override {} +}; + +} // namespace + +SimulatedThread::SimulatedThread( + sim_time_impl::SimulatedTimeControllerImpl* handler, + absl::string_view name, + std::unique_ptr<rtc::SocketServer> socket_server) + : rtc::Thread(socket_server ? std::move(socket_server) + : std::make_unique<DummySocketServer>()), + handler_(handler), + name_(new char[name.size()]) { + std::copy_n(name.begin(), name.size(), name_); +} + +SimulatedThread::~SimulatedThread() { + handler_->Unregister(this); + delete[] name_; +} + +void SimulatedThread::RunReady(Timestamp at_time) { + CurrentThreadSetter set_current(this); + ProcessMessages(0); + int delay_ms = GetDelay(); + MutexLock lock(&lock_); + if (delay_ms == kForever) { + next_run_time_ = Timestamp::PlusInfinity(); + } else { + next_run_time_ = at_time + TimeDelta::Millis(delay_ms); + } +} + +void SimulatedThread::BlockingCallImpl(rtc::FunctionView<void()> functor, + const Location& /*location*/) { + if (IsQuitting()) + return; + + if (IsCurrent()) { + functor(); + } else { + TaskQueueBase* yielding_from = TaskQueueBase::Current(); + handler_->StartYield(yielding_from); + RunReady(Timestamp::MinusInfinity()); + CurrentThreadSetter set_current(this); + functor(); + handler_->StopYield(yielding_from); + } +} + +void SimulatedThread::PostTaskImpl(absl::AnyInvocable<void() &&> task, + const PostTaskTraits& traits, + const Location& location) { + rtc::Thread::PostTaskImpl(std::move(task), traits, location); + MutexLock lock(&lock_); + next_run_time_ = Timestamp::MinusInfinity(); +} + +void SimulatedThread::PostDelayedTaskImpl(absl::AnyInvocable<void() &&> task, + TimeDelta delay, + const PostDelayedTaskTraits& traits, + const Location& location) { + rtc::Thread::PostDelayedTaskImpl(std::move(task), delay, traits, location); + MutexLock lock(&lock_); + next_run_time_ = + std::min(next_run_time_, Timestamp::Millis(rtc::TimeMillis()) + delay); +} + +void SimulatedThread::Stop() { + Thread::Quit(); +} + +SimulatedMainThread::SimulatedMainThread( + sim_time_impl::SimulatedTimeControllerImpl* handler) + : SimulatedThread(handler, "main", nullptr), current_setter_(this) {} + +SimulatedMainThread::~SimulatedMainThread() { + // Removes pending tasks in case they keep shared pointer references to + // objects whose destructor expects to run before the Thread destructor. + Stop(); + DoDestroy(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/time_controller/simulated_thread.h b/third_party/libwebrtc/test/time_controller/simulated_thread.h new file mode 100644 index 0000000000..8c6c728a48 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_thread.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020 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_SIMULATED_THREAD_H_ +#define TEST_TIME_CONTROLLER_SIMULATED_THREAD_H_ + +#include <memory> + +#include "rtc_base/synchronization/mutex.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +class SimulatedThread : public rtc::Thread, + public sim_time_impl::SimulatedSequenceRunner { + public: + using CurrentThreadSetter = CurrentThreadSetter; + SimulatedThread(sim_time_impl::SimulatedTimeControllerImpl* handler, + absl::string_view name, + std::unique_ptr<rtc::SocketServer> socket_server); + ~SimulatedThread() override; + + void RunReady(Timestamp at_time) override; + + Timestamp GetNextRunTime() const override { + MutexLock lock(&lock_); + return next_run_time_; + } + + TaskQueueBase* GetAsTaskQueue() override { return this; } + + // Thread interface + void BlockingCallImpl(rtc::FunctionView<void()> functor, + const Location& location) override; + void PostTaskImpl(absl::AnyInvocable<void() &&> task, + const PostTaskTraits& traits, + const Location& location) override; + void PostDelayedTaskImpl(absl::AnyInvocable<void() &&> task, + TimeDelta delay, + const PostDelayedTaskTraits& traits, + const Location& location) override; + + void Stop() override; + + private: + sim_time_impl::SimulatedTimeControllerImpl* const handler_; + // Using char* to be debugger friendly. + char* name_; + mutable Mutex lock_; + Timestamp next_run_time_ RTC_GUARDED_BY(lock_) = Timestamp::PlusInfinity(); +}; + +class SimulatedMainThread : public SimulatedThread { + public: + explicit SimulatedMainThread( + sim_time_impl::SimulatedTimeControllerImpl* handler); + ~SimulatedMainThread(); + + private: + CurrentThreadSetter current_setter_; +}; +} // namespace webrtc +#endif // TEST_TIME_CONTROLLER_SIMULATED_THREAD_H_ diff --git a/third_party/libwebrtc/test/time_controller/simulated_time_controller.cc b/third_party/libwebrtc/test/time_controller/simulated_time_controller.cc new file mode 100644 index 0000000000..dbb36fdfcc --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_time_controller.cc @@ -0,0 +1,236 @@ +/* + * 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/simulated_time_controller.h" + +#include <algorithm> +#include <deque> +#include <list> +#include <memory> +#include <string> +#include <thread> +#include <vector> + +#include "absl/strings/string_view.h" +#include "test/time_controller/simulated_task_queue.h" +#include "test/time_controller/simulated_thread.h" + +namespace webrtc { +namespace { +// Helper function to remove from a std container by value. +template <class C> +bool RemoveByValue(C* vec, typename C::value_type val) { + auto it = std::find(vec->begin(), vec->end(), val); + if (it == vec->end()) + return false; + vec->erase(it); + return true; +} +} // namespace + +namespace sim_time_impl { + +SimulatedTimeControllerImpl::SimulatedTimeControllerImpl(Timestamp start_time) + : thread_id_(rtc::CurrentThreadId()), current_time_(start_time) {} + +SimulatedTimeControllerImpl::~SimulatedTimeControllerImpl() = default; + +std::unique_ptr<TaskQueueBase, TaskQueueDeleter> +SimulatedTimeControllerImpl::CreateTaskQueue( + absl::string_view name, + TaskQueueFactory::Priority priority) const { + // TODO(srte): Remove the const cast when the interface is made mutable. + auto mutable_this = const_cast<SimulatedTimeControllerImpl*>(this); + auto task_queue = std::unique_ptr<SimulatedTaskQueue, TaskQueueDeleter>( + new SimulatedTaskQueue(mutable_this, name)); + mutable_this->Register(task_queue.get()); + return task_queue; +} + +std::unique_ptr<rtc::Thread> SimulatedTimeControllerImpl::CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) { + auto thread = + std::make_unique<SimulatedThread>(this, name, std::move(socket_server)); + Register(thread.get()); + return thread; +} + +void SimulatedTimeControllerImpl::YieldExecution() { + if (rtc::CurrentThreadId() == thread_id_) { + TaskQueueBase* yielding_from = TaskQueueBase::Current(); + // Since we might continue execution on a process thread, we should reset + // the thread local task queue reference. This ensures that thread checkers + // won't think we are executing on the yielding task queue. It also ensure + // that TaskQueueBase::Current() won't return the yielding task queue. + TokenTaskQueue::CurrentTaskQueueSetter reset_queue(nullptr); + // When we yield, we don't want to risk executing further tasks on the + // currently executing task queue. If there's a ready task that also yields, + // it's added to this set as well and only tasks on the remaining task + // queues are executed. + auto inserted = yielded_.insert(yielding_from); + RTC_DCHECK(inserted.second); + RunReadyRunners(); + yielded_.erase(inserted.first); + } +} + +void SimulatedTimeControllerImpl::RunReadyRunners() { + // Using a dummy thread rather than nullptr to avoid implicit thread creation + // by Thread::Current(). + SimulatedThread::CurrentThreadSetter set_current(dummy_thread_.get()); + MutexLock lock(&lock_); + RTC_DCHECK_EQ(rtc::CurrentThreadId(), thread_id_); + Timestamp current_time = CurrentTime(); + // Clearing `ready_runners_` in case this is a recursive call: + // RunReadyRunners -> Run -> Event::Wait -> Yield ->RunReadyRunners + ready_runners_.clear(); + + // We repeat until we have no ready left to handle tasks posted by ready + // runners. + while (true) { + for (auto* runner : runners_) { + if (yielded_.find(runner->GetAsTaskQueue()) == yielded_.end() && + runner->GetNextRunTime() <= current_time) { + ready_runners_.push_back(runner); + } + } + if (ready_runners_.empty()) + break; + while (!ready_runners_.empty()) { + auto* runner = ready_runners_.front(); + ready_runners_.pop_front(); + lock_.Unlock(); + // Note that the RunReady function might indirectly cause a call to + // Unregister() which will grab `lock_` again to remove items from + // `ready_runners_`. + runner->RunReady(current_time); + lock_.Lock(); + } + } +} + +Timestamp SimulatedTimeControllerImpl::CurrentTime() const { + MutexLock lock(&time_lock_); + return current_time_; +} + +Timestamp SimulatedTimeControllerImpl::NextRunTime() const { + Timestamp current_time = CurrentTime(); + Timestamp next_time = Timestamp::PlusInfinity(); + MutexLock lock(&lock_); + for (auto* runner : runners_) { + Timestamp next_run_time = runner->GetNextRunTime(); + if (next_run_time <= current_time) + return current_time; + next_time = std::min(next_time, next_run_time); + } + return next_time; +} + +void SimulatedTimeControllerImpl::AdvanceTime(Timestamp target_time) { + MutexLock time_lock(&time_lock_); + RTC_DCHECK_GE(target_time, current_time_); + current_time_ = target_time; +} + +void SimulatedTimeControllerImpl::Register(SimulatedSequenceRunner* runner) { + MutexLock lock(&lock_); + runners_.push_back(runner); +} + +void SimulatedTimeControllerImpl::Unregister(SimulatedSequenceRunner* runner) { + MutexLock lock(&lock_); + bool removed = RemoveByValue(&runners_, runner); + RTC_CHECK(removed); + RemoveByValue(&ready_runners_, runner); +} + +void SimulatedTimeControllerImpl::StartYield(TaskQueueBase* yielding_from) { + auto inserted = yielded_.insert(yielding_from); + RTC_DCHECK(inserted.second); +} + +void SimulatedTimeControllerImpl::StopYield(TaskQueueBase* yielding_from) { + yielded_.erase(yielding_from); +} + +} // namespace sim_time_impl + +GlobalSimulatedTimeController::GlobalSimulatedTimeController( + Timestamp start_time) + : sim_clock_(start_time.us()), impl_(start_time), yield_policy_(&impl_) { + global_clock_.SetTime(start_time); + auto main_thread = std::make_unique<SimulatedMainThread>(&impl_); + impl_.Register(main_thread.get()); + main_thread_ = std::move(main_thread); +} + +GlobalSimulatedTimeController::~GlobalSimulatedTimeController() = default; + +Clock* GlobalSimulatedTimeController::GetClock() { + return &sim_clock_; +} + +TaskQueueFactory* GlobalSimulatedTimeController::GetTaskQueueFactory() { + return &impl_; +} + +std::unique_ptr<rtc::Thread> GlobalSimulatedTimeController::CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) { + return impl_.CreateThread(name, std::move(socket_server)); +} + +rtc::Thread* GlobalSimulatedTimeController::GetMainThread() { + return main_thread_.get(); +} + +void GlobalSimulatedTimeController::AdvanceTime(TimeDelta duration) { + rtc::ScopedYieldPolicy yield_policy(&impl_); + Timestamp current_time = impl_.CurrentTime(); + Timestamp target_time = current_time + duration; + RTC_DCHECK_EQ(current_time.us(), rtc::TimeMicros()); + while (current_time < target_time) { + impl_.RunReadyRunners(); + Timestamp next_time = std::min(impl_.NextRunTime(), target_time); + impl_.AdvanceTime(next_time); + auto delta = next_time - current_time; + current_time = next_time; + sim_clock_.AdvanceTimeMicroseconds(delta.us()); + global_clock_.AdvanceTime(delta); + } + // After time has been simulated up until `target_time` we also need to run + // tasks meant to be executed at `target_time`. + impl_.RunReadyRunners(); +} + +void GlobalSimulatedTimeController::SkipForwardBy(TimeDelta duration) { + rtc::ScopedYieldPolicy yield_policy(&impl_); + Timestamp current_time = impl_.CurrentTime(); + Timestamp target_time = current_time + duration; + impl_.AdvanceTime(target_time); + sim_clock_.AdvanceTimeMicroseconds(duration.us()); + global_clock_.AdvanceTime(duration); + + // Run tasks that were pending during the skip. + impl_.RunReadyRunners(); +} + +void GlobalSimulatedTimeController::Register( + sim_time_impl::SimulatedSequenceRunner* runner) { + impl_.Register(runner); +} + +void GlobalSimulatedTimeController::Unregister( + sim_time_impl::SimulatedSequenceRunner* runner) { + impl_.Unregister(runner); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/time_controller/simulated_time_controller.h b/third_party/libwebrtc/test/time_controller/simulated_time_controller.h new file mode 100644 index 0000000000..f3f0da9274 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_time_controller.h @@ -0,0 +1,167 @@ +/* + * 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_SIMULATED_TIME_CONTROLLER_H_ +#define TEST_TIME_CONTROLLER_SIMULATED_TIME_CONTROLLER_H_ + +#include <list> +#include <memory> +#include <unordered_set> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/sequence_checker.h" +#include "api/test/time_controller.h" +#include "api/units/timestamp.h" +#include "rtc_base/fake_clock.h" +#include "rtc_base/platform_thread_types.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/synchronization/yield_policy.h" + +namespace webrtc { +namespace sim_time_impl { +class SimulatedSequenceRunner { + public: + virtual ~SimulatedSequenceRunner() = default; + // Provides next run time. + virtual Timestamp GetNextRunTime() const = 0; + // Runs all ready tasks and modules and updates next run time. + virtual void RunReady(Timestamp at_time) = 0; + + // All implementations also implements TaskQueueBase in some form, but if we'd + // inherit from it in this interface we'd run into issues with double + // inheritance. Therefore we simply allow the implementations to provide a + // casted pointer to themself. + virtual TaskQueueBase* GetAsTaskQueue() = 0; +}; + +class SimulatedTimeControllerImpl : public TaskQueueFactory, + public rtc::YieldInterface { + public: + explicit SimulatedTimeControllerImpl(Timestamp start_time); + ~SimulatedTimeControllerImpl() override; + + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> CreateTaskQueue( + absl::string_view name, + Priority priority) const RTC_LOCKS_EXCLUDED(time_lock_) override; + + // Implements the YieldInterface by running ready tasks on all task queues, + // except that if this method is called from a task, the task queue running + // that task is skipped. + void YieldExecution() RTC_LOCKS_EXCLUDED(time_lock_, lock_) override; + + // Create thread using provided `socket_server`. + std::unique_ptr<rtc::Thread> CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) + RTC_LOCKS_EXCLUDED(time_lock_, lock_); + + // Runs all runners in `runners_` that has tasks or modules ready for + // execution. + void RunReadyRunners() RTC_LOCKS_EXCLUDED(time_lock_, lock_); + // Return `current_time_`. + Timestamp CurrentTime() const RTC_LOCKS_EXCLUDED(time_lock_); + // Return min of runner->GetNextRunTime() for runner in `runners_`. + Timestamp NextRunTime() const RTC_LOCKS_EXCLUDED(lock_); + // Set `current_time_` to `target_time`. + void AdvanceTime(Timestamp target_time) RTC_LOCKS_EXCLUDED(time_lock_); + // Adds `runner` to `runners_`. + void Register(SimulatedSequenceRunner* runner) RTC_LOCKS_EXCLUDED(lock_); + // Removes `runner` from `runners_`. + void Unregister(SimulatedSequenceRunner* runner) RTC_LOCKS_EXCLUDED(lock_); + + // Indicates that `yielding_from` is not ready to run. + void StartYield(TaskQueueBase* yielding_from); + // Indicates that processing can be continued on `yielding_from`. + void StopYield(TaskQueueBase* yielding_from); + + private: + const rtc::PlatformThreadId thread_id_; + const std::unique_ptr<rtc::Thread> dummy_thread_ = rtc::Thread::Create(); + mutable Mutex time_lock_; + Timestamp current_time_ RTC_GUARDED_BY(time_lock_); + mutable Mutex lock_; + std::vector<SimulatedSequenceRunner*> runners_ RTC_GUARDED_BY(lock_); + // Used in RunReadyRunners() to keep track of ready runners that are to be + // processed in a round robin fashion. the reason it's a member is so that + // runners can removed from here by Unregister(). + std::list<SimulatedSequenceRunner*> ready_runners_ RTC_GUARDED_BY(lock_); + + // Runners on which YieldExecution has been called. + std::unordered_set<TaskQueueBase*> yielded_; +}; +} // namespace sim_time_impl + +// Used to satisfy sequence checkers for non task queue sequences. +class TokenTaskQueue : public TaskQueueBase { + public: + // Promoted to public + using CurrentTaskQueueSetter = TaskQueueBase::CurrentTaskQueueSetter; + + void Delete() override { RTC_DCHECK_NOTREACHED(); } + void PostTaskImpl(absl::AnyInvocable<void() &&> task, + const PostTaskTraits& traits, + const Location& location) override { + RTC_DCHECK_NOTREACHED(); + } + void PostDelayedTaskImpl(absl::AnyInvocable<void() &&> task, + TimeDelta delay, + const PostDelayedTaskTraits& traits, + const Location& location) override { + RTC_DCHECK_NOTREACHED(); + } +}; + +// TimeController implementation using completely simulated time. Task queues +// and process threads created by this controller will run delayed activities +// when AdvanceTime() is called. Overrides the global clock backing +// rtc::TimeMillis() and rtc::TimeMicros(). Note that this is not thread safe +// since it modifies global state. +class GlobalSimulatedTimeController : public TimeController { + public: + explicit GlobalSimulatedTimeController(Timestamp start_time); + ~GlobalSimulatedTimeController() override; + + Clock* GetClock() override; + TaskQueueFactory* GetTaskQueueFactory() override; + std::unique_ptr<rtc::Thread> CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) override; + rtc::Thread* GetMainThread() override; + + void AdvanceTime(TimeDelta duration) override; + + // Advances time by `duration`and do not run delayed tasks in the meantime. + // Runs any pending tasks at the end. + // Useful for simulating contention on destination queues. + void SkipForwardBy(TimeDelta duration); + + // Makes the simulated time controller aware of a custom + // SimulatedSequenceRunner. + // TODO(bugs.webrtc.org/11581): remove method once the ModuleRtpRtcpImpl2 unit + // test stops using it. + void Register(sim_time_impl::SimulatedSequenceRunner* runner); + // Removes a previously installed custom SimulatedSequenceRunner from the + // simulated time controller. + // TODO(bugs.webrtc.org/11581): remove method once the ModuleRtpRtcpImpl2 unit + // test stops using it. + void Unregister(sim_time_impl::SimulatedSequenceRunner* runner); + + private: + rtc::ScopedBaseFakeClock global_clock_; + // Provides simulated CurrentNtpInMilliseconds() + SimulatedClock sim_clock_; + sim_time_impl::SimulatedTimeControllerImpl impl_; + rtc::ScopedYieldPolicy yield_policy_; + std::unique_ptr<rtc::Thread> main_thread_; +}; +} // namespace webrtc + +#endif // TEST_TIME_CONTROLLER_SIMULATED_TIME_CONTROLLER_H_ diff --git a/third_party/libwebrtc/test/time_controller/simulated_time_controller_unittest.cc b/third_party/libwebrtc/test/time_controller/simulated_time_controller_unittest.cc new file mode 100644 index 0000000000..f223ffe85d --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_time_controller_unittest.cc @@ -0,0 +1,164 @@ +/* + * 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/simulated_time_controller.h" + +#include <atomic> +#include <memory> + +#include "api/units/time_delta.h" +#include "rtc_base/event.h" +#include "rtc_base/task_queue.h" +#include "rtc_base/task_queue_for_test.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); +} // namespace + +TEST(SimulatedTimeControllerTest, TaskIsStoppedOnStop) { + const TimeDelta kShortInterval = TimeDelta::Millis(5); + const TimeDelta kLongInterval = TimeDelta::Millis(20); + const int kShortIntervalCount = 4; + const int kMargin = 1; + GlobalSimulatedTimeController time_simulation(kStartTime); + 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.AdvanceTime(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.AdvanceTime(kLongInterval * 2); + EXPECT_EQ(counter.load(), kShortIntervalCount); +} + +TEST(SimulatedTimeControllerTest, TaskCanStopItself) { + std::atomic_int counter(0); + GlobalSimulatedTimeController time_simulation(kStartTime); + 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::Millis(2); + }); + }); + time_simulation.AdvanceTime(TimeDelta::Millis(10)); + EXPECT_EQ(counter.load(), 1); +} + +TEST(SimulatedTimeControllerTest, Example) { + class ObjectOnTaskQueue { + public: + void DoPeriodicTask() {} + TimeDelta TimeUntilNextRun() { return TimeDelta::Millis(100); } + void StartPeriodicTask(RepeatingTaskHandle* handle, + rtc::TaskQueue* task_queue) { + *handle = RepeatingTaskHandle::Start(task_queue->Get(), [this] { + DoPeriodicTask(); + return TimeUntilNextRun(); + }); + } + }; + GlobalSimulatedTimeController time_simulation(kStartTime); + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + auto object = std::make_unique<ObjectOnTaskQueue>(); + // Create and start the periodic task. + RepeatingTaskHandle handle; + object->StartPeriodicTask(&handle, &task_queue); + // Restart the task + task_queue.PostTask( + [handle = std::move(handle)]() mutable { handle.Stop(); }); + object->StartPeriodicTask(&handle, &task_queue); + task_queue.PostTask( + [handle = std::move(handle)]() mutable { handle.Stop(); }); + + task_queue.PostTask([object = std::move(object)] {}); +} + +TEST(SimulatedTimeControllerTest, DelayTaskRunOnTime) { + GlobalSimulatedTimeController time_simulation(kStartTime); + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue = + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL); + + bool delay_task_executed = false; + task_queue->PostDelayedTask([&] { delay_task_executed = true; }, + TimeDelta::Millis(10)); + + time_simulation.AdvanceTime(TimeDelta::Millis(10)); + EXPECT_TRUE(delay_task_executed); +} + +TEST(SimulatedTimeControllerTest, ThreadYeildsOnSynchronousCall) { + GlobalSimulatedTimeController sim(kStartTime); + auto main_thread = sim.GetMainThread(); + auto t2 = sim.CreateThread("thread", nullptr); + bool task_has_run = false; + // Posting a task to the main thread, this should not run until AdvanceTime is + // called. + main_thread->PostTask([&] { task_has_run = true; }); + SendTask(t2.get(), [] { + rtc::Event yield_event; + // Wait() triggers YieldExecution() which will runs message processing on + // all threads that are not in the yielded set. + + yield_event.Wait(TimeDelta::Zero()); + }); + // Since we are doing an invoke from the main thread, we don't expect the main + // thread message loop to be processed. + EXPECT_FALSE(task_has_run); + sim.AdvanceTime(TimeDelta::Seconds(1)); + ASSERT_TRUE(task_has_run); +} + +TEST(SimulatedTimeControllerTest, SkipsDelayedTaskForward) { + GlobalSimulatedTimeController sim(kStartTime); + auto main_thread = sim.GetMainThread(); + constexpr auto duration_during_which_nothing_runs = TimeDelta::Seconds(2); + constexpr auto shorter_duration = TimeDelta::Seconds(1); + MockFunction<void()> fun; + EXPECT_CALL(fun, Call).WillOnce(Invoke([&] { + ASSERT_EQ(sim.GetClock()->CurrentTime(), + kStartTime + duration_during_which_nothing_runs); + })); + main_thread->PostDelayedTask(fun.AsStdFunction(), shorter_duration); + sim.SkipForwardBy(duration_during_which_nothing_runs); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/time_controller/time_controller_conformance_test.cc b/third_party/libwebrtc/test/time_controller/time_controller_conformance_test.cc new file mode 100644 index 0000000000..300dd9175c --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/time_controller_conformance_test.cc @@ -0,0 +1,181 @@ +/* + * Copyright 2020 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 <memory> +#include <vector> + +#include "api/test/time_controller.h" +#include "api/units/time_delta.h" +#include "rtc_base/event.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread.h" +#include "rtc_base/thread_annotations.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/time_controller/real_time_controller.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { +namespace { + +using ::testing::ElementsAreArray; +using ::testing::TestParamInfo; +using ::testing::TestWithParam; +using ::testing::Values; + +enum class TimeMode { kRealTime, kSimulated }; + +std::unique_ptr<TimeController> CreateTimeController(TimeMode mode) { + switch (mode) { + case TimeMode::kRealTime: + return std::make_unique<RealTimeController>(); + case TimeMode::kSimulated: + // Using an offset of 100000 to get nice fixed width and readable + // timestamps in typical test scenarios. + constexpr Timestamp kSimulatedStartTime = Timestamp::Seconds(100000); + return std::make_unique<GlobalSimulatedTimeController>( + kSimulatedStartTime); + } +} + +std::string ParamsToString(const TestParamInfo<webrtc::TimeMode>& param) { + switch (param.param) { + case webrtc::TimeMode::kRealTime: + return "RealTime"; + case webrtc::TimeMode::kSimulated: + return "SimulatedTime"; + default: + RTC_DCHECK_NOTREACHED() << "Time mode not supported"; + } +} + +// Keeps order of executions. May be called from different threads. +class ExecutionOrderKeeper { + public: + void Executed(int execution_id) { + MutexLock lock(&mutex_); + order_.push_back(execution_id); + } + + std::vector<int> order() const { + MutexLock lock(&mutex_); + return order_; + } + + private: + mutable Mutex mutex_; + std::vector<int> order_ RTC_GUARDED_BY(mutex_); +}; + +// Tests conformance between real time and simulated time time controller. +class SimulatedRealTimeControllerConformanceTest + : public TestWithParam<webrtc::TimeMode> {}; + +TEST_P(SimulatedRealTimeControllerConformanceTest, ThreadPostOrderTest) { + std::unique_ptr<TimeController> time_controller = + CreateTimeController(GetParam()); + std::unique_ptr<rtc::Thread> thread = time_controller->CreateThread("thread"); + + // Tasks on thread have to be executed in order in which they were + // posted. + ExecutionOrderKeeper execution_order; + thread->PostTask([&]() { execution_order.Executed(1); }); + thread->PostTask([&]() { execution_order.Executed(2); }); + time_controller->AdvanceTime(TimeDelta::Millis(100)); + EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2})); + // Destroy `thread` before `execution_order` to be sure `execution_order` + // is not accessed on the posted task after it is destroyed. + thread = nullptr; +} + +TEST_P(SimulatedRealTimeControllerConformanceTest, ThreadPostDelayedOrderTest) { + std::unique_ptr<TimeController> time_controller = + CreateTimeController(GetParam()); + std::unique_ptr<rtc::Thread> thread = time_controller->CreateThread("thread"); + + ExecutionOrderKeeper execution_order; + thread->PostDelayedTask([&]() { execution_order.Executed(2); }, + TimeDelta::Millis(500)); + thread->PostTask([&]() { execution_order.Executed(1); }); + time_controller->AdvanceTime(TimeDelta::Millis(600)); + EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2})); + // Destroy `thread` before `execution_order` to be sure `execution_order` + // is not accessed on the posted task after it is destroyed. + thread = nullptr; +} + +TEST_P(SimulatedRealTimeControllerConformanceTest, ThreadPostInvokeOrderTest) { + std::unique_ptr<TimeController> time_controller = + CreateTimeController(GetParam()); + std::unique_ptr<rtc::Thread> thread = time_controller->CreateThread("thread"); + + // Tasks on thread have to be executed in order in which they were + // posted/invoked. + ExecutionOrderKeeper execution_order; + thread->PostTask([&]() { execution_order.Executed(1); }); + thread->BlockingCall([&]() { execution_order.Executed(2); }); + time_controller->AdvanceTime(TimeDelta::Millis(100)); + EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2})); + // Destroy `thread` before `execution_order` to be sure `execution_order` + // is not accessed on the posted task after it is destroyed. + thread = nullptr; +} + +TEST_P(SimulatedRealTimeControllerConformanceTest, + ThreadPostInvokeFromThreadOrderTest) { + std::unique_ptr<TimeController> time_controller = + CreateTimeController(GetParam()); + std::unique_ptr<rtc::Thread> thread = time_controller->CreateThread("thread"); + + // If task is invoked from thread X on thread X it has to be executed + // immediately. + ExecutionOrderKeeper execution_order; + thread->PostTask([&]() { + thread->PostTask([&]() { execution_order.Executed(2); }); + thread->BlockingCall([&]() { execution_order.Executed(1); }); + }); + time_controller->AdvanceTime(TimeDelta::Millis(100)); + EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2})); + // Destroy `thread` before `execution_order` to be sure `execution_order` + // is not accessed on the posted task after it is destroyed. + thread = nullptr; +} + +TEST_P(SimulatedRealTimeControllerConformanceTest, + TaskQueuePostEventWaitOrderTest) { + std::unique_ptr<TimeController> time_controller = + CreateTimeController(GetParam()); + auto task_queue = time_controller->GetTaskQueueFactory()->CreateTaskQueue( + "task_queue", webrtc::TaskQueueFactory::Priority::NORMAL); + + // Tasks on thread have to be executed in order in which they were + // posted/invoked. + ExecutionOrderKeeper execution_order; + rtc::Event event; + task_queue->PostTask([&]() { execution_order.Executed(1); }); + task_queue->PostTask([&]() { + execution_order.Executed(2); + event.Set(); + }); + EXPECT_TRUE(event.Wait(/*give_up_after=*/TimeDelta::Millis(100))); + time_controller->AdvanceTime(TimeDelta::Millis(100)); + EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2})); + // Destroy `task_queue` before `execution_order` to be sure `execution_order` + // is not accessed on the posted task after it is destroyed. + task_queue = nullptr; +} + +INSTANTIATE_TEST_SUITE_P(ConformanceTest, + SimulatedRealTimeControllerConformanceTest, + Values(TimeMode::kRealTime, TimeMode::kSimulated), + ParamsToString); + +} // namespace +} // namespace webrtc |