Add simplified WaitUntil variant to wait for a boolean condition
This allows makes test bodies cleaner for such common use case
eliminating extra IsTrue and IsRtcOk matchers.
Bug: None
Change-Id: I905143f5ccd0470e46437383e7fb5ec74e87a696
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/403160
Reviewed-by: Evan Shrubsole <eshr@webrtc.org>
Reviewed-by: Jeremy Leconte <jleconte@google.com>
Commit-Queue: Danil Chapovalov <danilchap@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#45282}
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 9edc60a..75a3fbc 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -1487,6 +1487,7 @@
]
deps = [
":test_support",
+ "../api:function_view",
"../api:rtc_error",
"../api:time_controller",
"../api/units:time_delta",
diff --git a/test/network/network_emulation_pc_unittest.cc b/test/network/network_emulation_pc_unittest.cc
index 086e230..8310f3d 100644
--- a/test/network/network_emulation_pc_unittest.cc
+++ b/test/network/network_emulation_pc_unittest.cc
@@ -174,11 +174,8 @@
ASSERT_THAT(WaitUntil([&] { return alice->signaling_state(); },
Eq(PeerConnectionInterface::kStable)),
IsRtcOk());
- ASSERT_THAT(
- WaitUntil([&] { return alice->IsIceGatheringDone(); }, IsTrue()),
- IsRtcOk());
- ASSERT_THAT(WaitUntil([&] { return bob->IsIceGatheringDone(); }, IsTrue()),
- IsRtcOk());
+ ASSERT_TRUE(WaitUntil([&] { return alice->IsIceGatheringDone(); }));
+ ASSERT_TRUE(WaitUntil([&] { return bob->IsIceGatheringDone(); }));
// Connect an ICE candidate pairs.
ASSERT_TRUE(
@@ -186,10 +183,8 @@
ASSERT_TRUE(
AddIceCandidates(alice.get(), bob->observer()->GetAllCandidates()));
// This means that ICE and DTLS are connected.
- ASSERT_THAT(WaitUntil([&] { return bob->IsIceConnected(); }, IsTrue()),
- IsRtcOk());
- ASSERT_THAT(WaitUntil([&] { return alice->IsIceConnected(); }, IsTrue()),
- IsRtcOk());
+ ASSERT_TRUE(WaitUntil([&] { return bob->IsIceConnected(); }));
+ ASSERT_TRUE(WaitUntil([&] { return alice->IsIceConnected(); }));
// Close peer connections
alice->pc()->Close();
@@ -285,11 +280,8 @@
ASSERT_THAT(WaitUntil([&] { return alice->signaling_state(); },
Eq(PeerConnectionInterface::kStable)),
IsRtcOk());
- ASSERT_THAT(
- WaitUntil([&] { return alice->IsIceGatheringDone(); }, IsTrue()),
- IsRtcOk());
- ASSERT_THAT(WaitUntil([&] { return bob->IsIceGatheringDone(); }, IsTrue()),
- IsRtcOk());
+ ASSERT_TRUE(WaitUntil([&] { return alice->IsIceGatheringDone(); }));
+ ASSERT_TRUE(WaitUntil([&] { return bob->IsIceGatheringDone(); }));
// Connect an ICE candidate pairs.
ASSERT_TRUE(
@@ -297,10 +289,8 @@
ASSERT_TRUE(
AddIceCandidates(alice.get(), bob->observer()->GetAllCandidates()));
// This means that ICE and DTLS are connected.
- ASSERT_THAT(WaitUntil([&] { return bob->IsIceConnected(); }, IsTrue()),
- IsRtcOk());
- ASSERT_THAT(WaitUntil([&] { return alice->IsIceConnected(); }, IsTrue()),
- IsRtcOk());
+ ASSERT_TRUE(WaitUntil([&] { return bob->IsIceConnected(); }));
+ ASSERT_TRUE(WaitUntil([&] { return alice->IsIceConnected(); }));
// Close peer connections
alice->pc()->Close();
diff --git a/test/wait_until.cc b/test/wait_until.cc
index b92f32c..61cfebe 100644
--- a/test/wait_until.cc
+++ b/test/wait_until.cc
@@ -13,41 +13,61 @@
#include <variant>
#include "absl/functional/overload.h"
+#include "api/function_view.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/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 std::visit(
- absl::Overload{
- [](const std::monostate&) { return Timestamp::Micros(TimeMicros()); },
- [](SimulatedClock* clock) { return clock->CurrentTime(); },
- [](TimeController* time_controller) {
- return time_controller->GetClock()->CurrentTime();
- },
- [](auto* clock) {
- return Timestamp::Micros(clock->TimeNanos() / 1000);
- },
- },
- clock);
+[[nodiscard]] bool WaitUntil(FunctionView<bool()> fn,
+ WaitUntilSettings settings) {
+ if (std::holds_alternative<std::monostate>(settings.clock)) {
+ RTC_CHECK(Thread::Current()) << "A current thread is required. An "
+ "webrtc::AutoThread can work for tests.";
+ }
+
+ auto now = [&] {
+ return std::visit(
+ absl::Overload{
+ [](const std::monostate&) {
+ return Timestamp::Micros(TimeMicros());
+ },
+ [](SimulatedClock* clock) { return clock->CurrentTime(); },
+ [](TimeController* time_controller) {
+ return time_controller->GetClock()->CurrentTime();
+ },
+ [](auto* clock) {
+ return Timestamp::Micros(clock->TimeNanos() / 1000);
+ },
+ },
+ settings.clock);
+ };
+
+ auto sleep = [&](TimeDelta delta) {
+ std::visit(absl::Overload{
+ [&](const std::monostate&) {
+ Thread::Current()->ProcessMessages(0);
+ Thread::Current()->SleepMs(delta.ms());
+ },
+ [&](auto* clock) { clock->AdvanceTime(delta); },
+ },
+ settings.clock);
+ };
+
+ Timestamp deadline = now() + settings.timeout;
+ for (;;) {
+ if (fn()) {
+ return true;
+ } else if (now() >= deadline) {
+ return false;
+ }
+ sleep(settings.polling_interval);
+ }
}
-void AdvanceTimeOnClockVariant(ClockVariant& clock, TimeDelta delta) {
- std::visit(absl::Overload{
- [&](const std::monostate&) {
- Thread::Current()->ProcessMessages(0);
- 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 1d45fdc..c5e4f8f 100644
--- a/test/wait_until.h
+++ b/test/wait_until.h
@@ -11,18 +11,21 @@
#ifndef TEST_WAIT_UNTIL_H_
#define TEST_WAIT_UNTIL_H_
+#include <optional>
#include <string>
+#include <type_traits>
+#include <utility>
#include <variant>
+#include "api/function_view.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"
+#include "test/gtest.h"
#include "test/wait_until_internal.h" // IWYU pragma: private
namespace webrtc {
@@ -33,11 +36,6 @@
ThreadProcessingFakeClock*,
TimeController*>;
-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);
@@ -49,6 +47,16 @@
std::string result_name = "result";
};
+// Runs a function `fn`, until it returns true, or timeout from `settings`.
+// Calls `fn` at least once. Returns true when `fn` return true, returns false
+// after timeout if `fn` always returned false.
+//
+// Example:
+//
+// EXPECT_TRUE(WaitUntil([&] { return client.IsConnected(); });
+[[nodiscard]] bool WaitUntil(FunctionView<bool()> fn,
+ WaitUntilSettings settings = {});
+
// Runs a function `fn`, which returns a result, until `matcher` matches the
// result.
//
@@ -59,39 +67,35 @@
// Example:
//
// int counter = 0;
-// RTCErrorOr<int> result = Waituntil([&] { return ++counter; }, Eq(3))
+// RTCErrorOr<int> result = WaitUntil([&] { return ++counter; }, Eq(3))
// EXPECT_THAT(result, IsOkAndHolds(3));
-template <typename Fn, typename Matcher>
-[[nodiscard]] auto WaitUntil(const Fn& fn,
- Matcher matcher,
- WaitUntilSettings settings = {})
- -> RTCErrorOr<decltype(fn())> {
- if (std::holds_alternative<std::monostate>(settings.clock)) {
- RTC_CHECK(Thread::Current()) << "A current thread is required. An "
- "webrtc::AutoThread can work for tests.";
+template <typename Fn>
+[[nodiscard]] RTCErrorOr<std::invoke_result_t<Fn>> WaitUntil(
+ const Fn& fn,
+ ::testing::Matcher<std::invoke_result_t<Fn>> matcher,
+ WaitUntilSettings settings = {}) {
+ // Wrap `result` into optional to support types that are not default
+ // constructable.
+ std::optional<std::invoke_result_t<Fn>> result;
+ bool ok = WaitUntil(
+ [&] {
+ // `emplace` instead of assigning to support return types that do not
+ // have an assign operator.
+ result.emplace(fn());
+ return ::testing::Value(*result, matcher);
+ },
+ settings);
+
+ // WaitUntil promise to call `fn` at least once and thus `result` is
+ // populated.
+ RTC_CHECK(result.has_value());
+ if (ok) {
+ return *std::move(result);
}
- Timestamp start =
- wait_until_internal::GetTimeFromClockVariant(settings.clock);
- do {
- auto result = fn();
- if (::testing::Value(result, matcher)) {
- return result;
- }
- 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;
- if (wait_until_internal::ExplainMatchResult(matcher, result, &listener,
- settings.result_name)) {
- return result;
- }
-
+ wait_until_internal::ExplainMatchResult(matcher, *result, &listener,
+ settings.result_name);
return RTCError(RTCErrorType::INTERNAL_ERROR, listener.str());
}
diff --git a/test/wait_until_unittest.cc b/test/wait_until_unittest.cc
index 6eff548..740ec0e 100644
--- a/test/wait_until_unittest.cc
+++ b/test/wait_until_unittest.cc
@@ -27,13 +27,24 @@
namespace webrtc {
namespace {
-using testing::_;
-using testing::AllOf;
-using testing::Eq;
-using testing::Ge;
-using testing::Gt;
-using testing::Lt;
-using testing::MatchesRegex;
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::Eq;
+using ::testing::Ge;
+using ::testing::Gt;
+using ::testing::Lt;
+using ::testing::MatchesRegex;
+using ::testing::Property;
+
+TEST(WaitUntilTest, ReturnsTrueWhenConditionIsMet) {
+ AutoThread thread;
+
+ int counter = 0;
+ EXPECT_TRUE(WaitUntil([&] { return ++counter == 3; }));
+
+ // Check that functor is not called after it returned true.
+ EXPECT_EQ(counter, 3);
+}
TEST(WaitUntilTest, ReturnsWhenConditionIsMet) {
AutoThread thread;
@@ -81,20 +92,22 @@
SimulatedClock fake_clock(Timestamp::Millis(1337));
int counter = 0;
- RTCErrorOr<int> 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(fake_clock.CurrentTime(), Ge(Timestamp::Millis(1339)));
+ EXPECT_TRUE(WaitUntil(
+ [&] { return ++counter == 3; },
+ {.polling_interval = TimeDelta::Millis(10), .clock = &fake_clock}));
+ EXPECT_EQ(counter, 3);
+ // The fake clock should have advanced at least 2 polling intervals, 20ms.
+ EXPECT_THAT(fake_clock.CurrentTime(), Ge(Timestamp::Millis(1357)));
}
TEST(WaitUntilTest, ReturnsWhenConditionIsMetWithThreadProcessingFakeClock) {
ScopedFakeClock fake_clock;
int counter = 0;
- RTCErrorOr<int> result =
- WaitUntil([&] { return ++counter; }, Eq(3), {.clock = &fake_clock});
- EXPECT_THAT(result, IsRtcOkAndHolds(3));
+ EXPECT_TRUE(WaitUntil(
+ [&] { return ++counter == 3; },
+ {.polling_interval = TimeDelta::Millis(1), .clock = &fake_clock}));
+ EXPECT_EQ(counter, 3);
// The fake clock should have advanced at least 2ms.
EXPECT_THAT(Timestamp::Micros(fake_clock.TimeNanos() * 1000),
Ge(Timestamp::Millis(1339)));
@@ -104,22 +117,49 @@
FakeClock fake_clock;
int counter = 0;
- RTCErrorOr<int> result =
- WaitUntil([&] { return ++counter; }, Eq(3), {.clock = &fake_clock});
- EXPECT_THAT(result, IsRtcOkAndHolds(3));
+ EXPECT_TRUE(WaitUntil(
+ [&] { return ++counter == 3; },
+ {.polling_interval = TimeDelta::Millis(1), .clock = &fake_clock}));
+ EXPECT_EQ(counter, 3);
// The fake clock should have advanced at least 2ms.
EXPECT_THAT(Timestamp::Micros(fake_clock.TimeNanos() * 1000),
Ge(Timestamp::Millis(1339)));
}
+// No default constuctor, not assignable, move-only type.
+class CustomType {
+ public:
+ explicit CustomType(int value) : value_(value) {}
+ CustomType(CustomType&&) = default;
+ CustomType& operator=(CustomType&&) = delete;
+ CustomType() = delete;
+
+ int value() const { return value_; }
+
+ private:
+ const int value_;
+};
+
+TEST(WaitUntilTest, RequiresOnlyMoveCopyConstructionForReturnedType) {
+ AutoThread thread;
+
+ int counter = 0;
+ RTCErrorOr<CustomType> result =
+ WaitUntil([&] { return CustomType(++counter); },
+ Property(&CustomType::value, Eq(3)));
+ EXPECT_THAT(result, IsRtcOkAndHolds(Property(&CustomType::value, Eq(3))));
+}
+
TEST(WaitUntilTest, ReturnsWhenConditionIsMetWithSimulatedTimeController) {
std::unique_ptr<TimeController> time_controller =
CreateSimulatedTimeController();
int counter = 0;
- RTCErrorOr<int> result = WaitUntil([&] { return ++counter; }, Eq(3),
- {.clock = time_controller.get()});
- EXPECT_THAT(result, IsRtcOkAndHolds(3));
+ EXPECT_TRUE(WaitUntil([&] { return ++counter == 3; },
+ {.polling_interval = TimeDelta::Millis(1),
+ .clock = time_controller.get()}));
+ EXPECT_EQ(counter, 3);
+
// The fake clock should have advanced at least 2ms.
EXPECT_THAT(time_controller->GetClock()->CurrentTime(),
Ge(Timestamp::Millis(1339)));