diff --git a/test/BUILD.gn b/test/BUILD.gn index bfe3cc3279..598c196bad 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -715,6 +715,7 @@ if (rtc_include_tests) { "../api:array_view", "../api:create_frame_generator", "../api:create_simulcast_test_fixture_api", + "../api:create_time_controller", "../api:frame_generator_api", "../api:mock_video_codec_factory", "../api:mock_video_decoder", @@ -723,6 +724,7 @@ if (rtc_include_tests) { "../api:rtc_error_matchers", "../api:scoped_refptr", "../api:simulcast_test_fixture_api", + "../api:time_controller", "../api/environment", "../api/environment:environment_factory", "../api/task_queue", @@ -753,6 +755,7 @@ if (rtc_include_tests) { "../modules/video_coding:webrtc_vp9", "../modules/video_coding/svc:scalability_mode_util", "../rtc_base:criticalsection", + "../rtc_base:rtc_base_tests_utils", "../rtc_base:rtc_event", "../rtc_base:threading", "../rtc_base/synchronization:mutex", @@ -1422,21 +1425,27 @@ rtc_library("video_codec_tester") { ] } -rtc_source_set("wait_until") { +rtc_library("wait_until") { testonly = true sources = [ + "wait_until.cc", "wait_until.h", "wait_until_internal.h", ] deps = [ ":test_support", "../api:rtc_error", + "../api:time_controller", "../api/units:time_delta", "../api/units:timestamp", "../rtc_base:checks", + "../rtc_base:rtc_base_tests_utils", "../rtc_base:threading", + "../rtc_base:timeutils", "../system_wrappers", "//third_party/abseil-cpp/absl/base:nullability", + "//third_party/abseil-cpp/absl/functional:overload", "//third_party/abseil-cpp/absl/strings:string_view", + "//third_party/abseil-cpp/absl/types:variant", ] } diff --git a/test/DEPS b/test/DEPS index 497c79ab9c..1b0b340d96 100644 --- a/test/DEPS +++ b/test/DEPS @@ -89,5 +89,8 @@ specific_include_rules = { ], "emulated_turn_server\.h": [ "+p2p/base/turn_server.h", - ] + ], + "wait_until\.cc": [ + "+absl/functional/overload.h", + ], } diff --git a/test/wait_until.cc b/test/wait_until.cc new file mode 100644 index 0000000000..cd9eacace1 --- /dev/null +++ b/test/wait_until.cc @@ -0,0 +1,54 @@ +/* + * Copyright 2024 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/wait_until.h" + +#include "absl/functional/overload.h" +#include "absl/types/variant.h" +#include "api/test/time_controller.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/thread.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { +namespace wait_until_internal { + +Timestamp GetTimeFromClockVariant(const ClockVariant& clock) { + return absl::visit( + absl::Overload{ + [](const absl::monostate&) { + return Timestamp::Micros(rtc::TimeMicros()); + }, + [](SimulatedClock* clock) { return clock->CurrentTime(); }, + [](TimeController* time_controller) { + return time_controller->GetClock()->CurrentTime(); + }, + [](auto* clock) { + return Timestamp::Micros(clock->TimeNanos() / 1000); + }, + }, + clock); +} + +void AdvanceTimeOnClockVariant(ClockVariant& clock, TimeDelta delta) { + absl::visit(absl::Overload{ + [&](const absl::monostate&) { + rtc::Thread::Current()->ProcessMessages(0); + rtc::Thread::Current()->SleepMs(delta.ms()); + }, + [&](auto* clock) { clock->AdvanceTime(delta); }, + }, + clock); +} + +} // namespace wait_until_internal +} // namespace webrtc diff --git a/test/wait_until.h b/test/wait_until.h index a8f7ac2a9c..f6d5729539 100644 --- a/test/wait_until.h +++ b/test/wait_until.h @@ -13,11 +13,13 @@ #include -#include "absl/base/nullability.h" +#include "absl/types/variant.h" #include "api/rtc_error.h" +#include "api/test/time_controller.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "rtc_base/checks.h" +#include "rtc_base/fake_clock.h" #include "rtc_base/thread.h" #include "system_wrappers/include/clock.h" #include "test/gmock.h" @@ -25,14 +27,24 @@ namespace webrtc { +using ClockVariant = absl::variant; + +namespace wait_until_internal { +Timestamp GetTimeFromClockVariant(const ClockVariant& clock); +void AdvanceTimeOnClockVariant(ClockVariant& clock, TimeDelta delta); +} // namespace wait_until_internal + struct WaitUntilSettings { // The maximum time to wait for the condition to be met. TimeDelta timeout = TimeDelta::Seconds(5); // The interval between polling the condition. TimeDelta polling_interval = TimeDelta::Millis(1); // The clock to use for timing. - absl::Nullable clock = nullptr; - + ClockVariant clock = absl::monostate(); // Name of the result to be used in the error message. std::string result_name = "result"; }; @@ -50,35 +62,31 @@ struct WaitUntilSettings { // RTCErrorOr result = Waituntil([&] { return ++counter; }, Eq(3)) // EXPECT_THAT(result, IsOkAndHolds(3)); template -auto WaitUntil(const Fn& fn, Matcher matcher, WaitUntilSettings settings = {}) +[[nodiscard]] auto WaitUntil(const Fn& fn, + Matcher matcher, + WaitUntilSettings settings = {}) -> RTCErrorOr { - if (!settings.clock) { + if (absl::holds_alternative(settings.clock)) { RTC_CHECK(rtc::Thread::Current()) << "A current thread is required. An " "rtc::AutoThread can work for tests."; } - absl::Nonnull clock = - settings.clock ? settings.clock : Clock::GetRealTimeClock(); - - Timestamp start = clock->CurrentTime(); - + Timestamp start = + wait_until_internal::GetTimeFromClockVariant(settings.clock); do { auto result = fn(); - if (testing::Value(result, matcher)) { + if (::testing::Value(result, matcher)) { return result; } - if (settings.clock) { - settings.clock->AdvanceTime(settings.polling_interval); - } else { - rtc::Thread::Current()->ProcessMessages(0); - rtc::Thread::Current()->SleepMs(settings.polling_interval.ms()); - } - } while (clock->CurrentTime() < start + settings.timeout); + wait_until_internal::AdvanceTimeOnClockVariant(settings.clock, + settings.polling_interval); + } while (wait_until_internal::GetTimeFromClockVariant(settings.clock) < + start + settings.timeout); // One more try after the last sleep. This failure will contain the error // message. auto result = fn(); - testing::StringMatchResultListener listener; + ::testing::StringMatchResultListener listener; if (wait_until_internal::ExplainMatchResult(matcher, result, &listener, settings.result_name)) { return result; diff --git a/test/wait_until_internal.h b/test/wait_until_internal.h index 5065e965b3..86ebd279e3 100644 --- a/test/wait_until_internal.h +++ b/test/wait_until_internal.h @@ -29,23 +29,23 @@ template bool ExplainMatchResult( const M& matcher, const T& value, - absl::Nonnull listener, + absl::Nonnull<::testing::StringMatchResultListener*> listener, absl::string_view value_name) { // SafeMatcherCast is required for matchers whose type does not match the // argument type. - testing::Matcher safe_matcher = - testing::SafeMatcherCast(matcher); + ::testing::Matcher safe_matcher = + ::testing::SafeMatcherCast(matcher); auto* ss = listener->stream(); *ss << "Value of: " << value_name << "\n"; *ss << "Expected: "; safe_matcher.DescribeTo(ss); *ss << "\nActual: "; - testing::StringMatchResultListener inner_listener; - if (testing::ExplainMatchResult(safe_matcher, value, &inner_listener)) { + ::testing::StringMatchResultListener inner_listener; + if (::testing::ExplainMatchResult(safe_matcher, value, &inner_listener)) { return true; } - *ss << testing::PrintToString(value); + *ss << ::testing::PrintToString(value); if (const std::string& inner_message = inner_listener.str(); !inner_message.empty()) { *ss << ", " << inner_message; diff --git a/test/wait_until_unittest.cc b/test/wait_until_unittest.cc index 96fec94a15..5f4ecf1696 100644 --- a/test/wait_until_unittest.cc +++ b/test/wait_until_unittest.cc @@ -10,10 +10,15 @@ #include "test/wait_until.h" +#include + #include "api/rtc_error.h" +#include "api/test/create_time_controller.h" #include "api/test/rtc_error_matchers.h" +#include "api/test/time_controller.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" +#include "rtc_base/fake_clock.h" #include "rtc_base/thread.h" #include "system_wrappers/include/clock.h" #include "test/gmock.h" @@ -73,7 +78,7 @@ TEST(WaitUntilTest, ErrorContainsMatcherExplanation) { } TEST(WaitUntilTest, ReturnsWhenConditionIsMetWithSimulatedClock) { - SimulatedClock fake_clock = SimulatedClock(Timestamp::Millis(1337)); + SimulatedClock fake_clock(Timestamp::Millis(1337)); int counter = 0; RTCErrorOr result = @@ -83,5 +88,42 @@ TEST(WaitUntilTest, ReturnsWhenConditionIsMetWithSimulatedClock) { EXPECT_THAT(fake_clock.CurrentTime(), Ge(Timestamp::Millis(1339))); } +TEST(WaitUntilTest, ReturnsWhenConditionIsMetWithThreadProcessingFakeClock) { + rtc::ScopedFakeClock fake_clock; + + int counter = 0; + RTCErrorOr result = + WaitUntil([&] { return ++counter; }, Eq(3), {.clock = &fake_clock}); + EXPECT_THAT(result, IsRtcOkAndHolds(3)); + // The fake clock should have advanced at least 2ms. + EXPECT_THAT(Timestamp::Micros(fake_clock.TimeNanos() * 1000), + Ge(Timestamp::Millis(1339))); +} + +TEST(WaitUntilTest, ReturnsWhenConditionIsMetWithFakeClock) { + rtc::FakeClock fake_clock; + + int counter = 0; + RTCErrorOr result = + WaitUntil([&] { return ++counter; }, Eq(3), {.clock = &fake_clock}); + EXPECT_THAT(result, IsRtcOkAndHolds(3)); + // The fake clock should have advanced at least 2ms. + EXPECT_THAT(Timestamp::Micros(fake_clock.TimeNanos() * 1000), + Ge(Timestamp::Millis(1339))); +} + +TEST(WaitUntilTest, ReturnsWhenConditionIsMetWithSimulatedTimeController) { + std::unique_ptr time_controller = + CreateSimulatedTimeController(); + + int counter = 0; + RTCErrorOr result = WaitUntil([&] { return ++counter; }, Eq(3), + {.clock = time_controller.get()}); + EXPECT_THAT(result, IsRtcOkAndHolds(3)); + // The fake clock should have advanced at least 2ms. + EXPECT_THAT(time_controller->GetClock()->CurrentTime(), + Ge(Timestamp::Millis(1339))); +} + } // namespace } // namespace webrtc