fixup! [WebRTC] Add unidirectional codec support 46/324746/2
authorMichal Jurkiewicz <m.jurkiewicz@samsung.com>
Fri, 23 May 2025 11:34:52 +0000 (13:34 +0200)
committerBot Blink <blinkbot@samsung.com>
Mon, 26 May 2025 09:12:17 +0000 (09:12 +0000)
Due to concurrent development of base patch and
[M130 Migration][WebRTC] Enable unit tests patch,
regression was introduced during compilation of
tizen_blink_unittests.

Fix for not compiling rtp_transceiver_unittest.

Ported upstream patches:
* Add utility WaitUntil for testing for an eventual condition
  https://webrtc-review.googlesource.com/c/src/+/369980
* Add matchers for RTCError, rename old matcher for RTCErrorOr.
  https://webrtc-review.googlesource.com/c/src/+/374343
* Misc improvements to RtpTransceiver unit tests and test utils.
  https://webrtc-review.googlesource.com/c/src/+/374900

Bug: https://jira-eu.sec.samsung.net/browse/VDWASM-2425
Change-Id: I617251cbbe3eb622742b94202d88a27a606c2af5
Signed-off-by: Michal Jurkiewicz <m.jurkiewicz@samsung.com>
15 files changed:
third_party/webrtc/api/BUILD.gn
third_party/webrtc/api/DEPS
third_party/webrtc/api/rtc_error.h
third_party/webrtc/api/rtc_error_unittest.cc
third_party/webrtc/api/test/DEPS
third_party/webrtc/api/test/rtc_error_matchers.h [new file with mode: 0644]
third_party/webrtc/media/base/fake_media_engine.cc
third_party/webrtc/media/base/fake_media_engine.h
third_party/webrtc/media/engine/fake_webrtc_video_engine.cc
third_party/webrtc/media/engine/fake_webrtc_video_engine.h
third_party/webrtc/pc/BUILD.gn
third_party/webrtc/test/BUILD.gn
third_party/webrtc/test/wait_until.h [new file with mode: 0644]
third_party/webrtc/test/wait_until_internal.h [new file with mode: 0644]
third_party/webrtc/test/wait_until_unittest.cc [new file with mode: 0644]

index 7a59f3eea5b42e8e93ee1680f1c560a4dccf994f..402f750c252b347eb231cc72d75790b859276dcc 100644 (file)
@@ -430,10 +430,23 @@ rtc_library("rtc_error") {
     "../rtc_base:logging",
     "../rtc_base:macromagic",
     "../rtc_base/system:rtc_export",
+    "//third_party/abseil-cpp/absl/meta:type_traits",
+    "//third_party/abseil-cpp/absl/strings",
+    "//third_party/abseil-cpp/absl/strings:str_format",
     "//third_party/abseil-cpp/absl/strings:string_view",
   ]
 }
 
+rtc_source_set("rtc_error_matchers") {
+  testonly = true
+  sources = [ "test/rtc_error_matchers.h" ]
+  deps = [
+    ":rtc_error",
+    "../test:test_support",
+    "//third_party/abseil-cpp/absl/strings",
+  ]
+}
+
 rtc_source_set("packet_socket_factory") {
   visibility = [ "*" ]
   sources = [ "packet_socket_factory.h" ]
index a03410297b838c7a8547770c9e9c1ce1433a2901..e4c17a3a39e40197aa3e11c80bc368a51656e809 100644 (file)
@@ -135,6 +135,8 @@ specific_include_rules = {
 
   "rtc_error\.h": [
     "+rtc_base/logging.h",
+    "+absl/strings/has_absl_stringify.h",
+    "+absl/strings/str_format.h",
   ],
   "rtc_event_log_output_file.h": [
     # For private member and constructor.
index 9de3b1c649f67c52b2ab1e3616d67c2327905bfd..775f6de0387095d4b3f5e734890b0b05e59495fd 100644 (file)
 
 #include <optional>
 #include <string>
+#include <type_traits>
 #include <utility>  // For std::move.
 
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
@@ -95,6 +97,14 @@ enum class RTCErrorDetailType {
   HARDWARE_ENCODER_ERROR,
 };
 
+// Outputs the error as a friendly string. Update this method when adding a new
+// error type.
+//
+// Only intended to be used for logging/diagnostics. The returned char* points
+// to literal strings that live for the whole duration of the program.
+RTC_EXPORT absl::string_view ToString(RTCErrorType error);
+RTC_EXPORT absl::string_view ToString(RTCErrorDetailType error);
+
 // Roughly corresponds to RTCError in the web api. Holds an error type, a
 // message, and possibly additional information specific to that error.
 //
@@ -146,6 +156,16 @@ class RTC_EXPORT RTCError {
   // error occurred.
   bool ok() const { return type_ == RTCErrorType::NONE; }
 
+  template <typename Sink>
+  friend void AbslStringify(Sink& sink, const RTCError& error) {
+    sink.Append(ToString(error.type_));
+    if (!error.message_.empty()) {
+      sink.Append(" with message: \"");
+      sink.Append(error.message_);
+      sink.Append("\"");
+    }
+  }
+
  private:
   RTCErrorType type_ = RTCErrorType::NONE;
   std::string message_;
@@ -153,14 +173,6 @@ class RTC_EXPORT RTCError {
   std::optional<uint16_t> sctp_cause_code_;
 };
 
-// Outputs the error as a friendly string. Update this method when adding a new
-// error type.
-//
-// Only intended to be used for logging/diagnostics. The returned char* points
-// to literal string that lives for the whole duration of the program.
-RTC_EXPORT absl::string_view ToString(RTCErrorType error);
-RTC_EXPORT absl::string_view ToString(RTCErrorDetailType error);
-
 // Helper macro that can be used by implementations to create an error with a
 // message and log it. `message` should be a string literal or movable
 // std::string.
@@ -307,6 +319,19 @@ class RTCErrorOr {
     return std::move(*value_);
   }
 
+  template <typename Sink>
+  friend void AbslStringify(Sink& sink, const RTCErrorOr<T>& error_or) {
+    if (error_or.ok()) {
+      sink.Append("OK");
+      if constexpr (std::is_convertible_v<T, absl::AlphaNum>) {
+        sink.Append(" with value: ");
+        sink.Append(absl::StrCat(error_or.value()));
+      }
+    } else {
+      sink.Append(absl::StrCat(error_or.error()));
+    }
+  }
+
  private:
   RTCError error_;
   std::optional<T> value_;
index 68ca2f5a51d7a6c9bb3280ef8a35b41e536b49dc..860f3ead053285f5bae38497430ff75a9b95c16e 100644 (file)
@@ -13,6 +13,7 @@
 #include <string>
 #include <utility>
 
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "rtc_base/checks.h"
 #include "test/gtest.h"
@@ -147,6 +148,11 @@ TEST(RTCErrorTest, SetMessage) {
   EXPECT_STREQ(e.message(), "string");
 }
 
+TEST(RTCErrorTest, Stringify) {
+  RTCError e(RTCErrorType::INVALID_PARAMETER, "foo");
+  EXPECT_EQ(absl::StrCat(e), "INVALID_PARAMETER with message: \"foo\"");
+}
+
 // Test that the default constructor creates an "INTERNAL_ERROR".
 TEST(RTCErrorOrTest, DefaultConstructor) {
   RTCErrorOr<MoveOnlyInt> e;
@@ -212,6 +218,26 @@ TEST(RTCErrorOrTest, MoveValue) {
   EXPECT_EQ(value.value, 88);
 }
 
+TEST(RTCErrorOrTest, StringifyWithUnprintableValue) {
+  RTCErrorOr<MoveOnlyInt> e(MoveOnlyInt(1337));
+  EXPECT_EQ(absl::StrCat(e), "OK");
+}
+
+TEST(RTCErrorOrTest, StringifyWithStringValue) {
+  RTCErrorOr<absl::string_view> e("foo");
+  EXPECT_EQ(absl::StrCat(e), "OK with value: foo");
+}
+
+TEST(RTCErrorOrTest, StringifyWithPrintableValue) {
+  RTCErrorOr<int> e(1337);
+  EXPECT_EQ(absl::StrCat(e), "OK with value: 1337");
+}
+
+TEST(RTCErrorOrTest, StringifyWithError) {
+  RTCErrorOr<int> e({RTCErrorType::SYNTAX_ERROR, "message"});
+  EXPECT_EQ(absl::StrCat(e), "SYNTAX_ERROR with message: \"message\"");
+}
+
 // Death tests.
 // Disabled on Android because death tests misbehave on Android, see
 // base/test/gtest_util.h.
index 5506d6c76afa16a87562cca88260b35efda904ea..4a51584c86089d4b0a6ff190026cfb866f1a12b9 100644 (file)
@@ -56,4 +56,7 @@ specific_include_rules = {
   "videocodec_test_fixture\.h": [
     "+modules/video_coding/codecs/h264/include/h264_globals.h",
   ],
+  "rtc_error_matchers\.h": [
+    "+test/gmock.h",
+  ],
 }
diff --git a/third_party/webrtc/api/test/rtc_error_matchers.h b/third_party/webrtc/api/test/rtc_error_matchers.h
new file mode 100644 (file)
index 0000000..2abc569
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ *  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.
+ */
+
+#ifndef API_TEST_RTC_ERROR_MATCHERS_H_
+#define API_TEST_RTC_ERROR_MATCHERS_H_
+
+#include <string>
+
+#include "absl/strings/str_cat.h"
+#include "api/rtc_error.h"
+#include "test/gmock.h"
+
+namespace webrtc {
+
+MATCHER(IsRtcOk, "") {
+  if (!arg.ok()) {
+    *result_listener << "Expected OK, got " << absl::StrCat(arg);
+    return false;
+  }
+  return true;
+}
+
+MATCHER_P(IsRtcOkAndHolds,
+          matcher,
+          "RtcErrorOr that is holding an OK status and ") {
+  if (!arg.ok()) {
+    *result_listener << "Expected OK, got " << absl::StrCat(arg);
+    return false;
+  }
+  return testing::ExplainMatchResult(matcher, arg.value(), result_listener);
+}
+
+MATCHER_P(IsRtcErrorWithType, error_type, ToString(error_type)) {
+  if (arg.ok()) {
+    *result_listener << "Expected " << ToString(error_type) << ", got OK.";
+    return false;
+  }
+  if (arg.type() != error_type) {
+    *result_listener << "Expected " << ToString(error_type) << ", got "
+                     << ToString(arg.type());
+    return false;
+  }
+  return true;
+}
+
+MATCHER_P2(IsRtcErrorWithTypeAndMessage,
+           error_type,
+           message,
+           ToString(error_type)) {
+  if (arg.ok()) {
+    *result_listener << "Expected " << ToString(error_type) << ", got OK.";
+    return false;
+  }
+  if (arg.type() != error_type) {
+    *result_listener << "Expected " << ToString(error_type) << ", got "
+                     << ToString(arg.type());
+    return false;
+  }
+  if (std::string(arg.message()) != message) {
+    *result_listener << "Expected message \"" << message << "\", got \""
+                     << arg.message() << "\"";
+    return false;
+  }
+  return true;
+}
+
+MATCHER_P2(IsRtcErrorOrWithMessage,
+           error_matcher,
+           message_matcher,
+           "RtcErrorOr that is holding an error that " +
+               testing::DescribeMatcher<RTCError>(error_matcher, negation) +
+               (negation ? " or " : " and ") + " with a message that " +
+               testing::DescribeMatcher<std::string>(message_matcher,
+                                                     negation)) {
+  if (arg.ok()) {
+    *result_listener << "Expected error, got " << absl::StrCat(arg);
+    return false;
+  }
+  return testing::ExplainMatchResult(error_matcher, arg.error(),
+                                     result_listener) &&
+         testing::ExplainMatchResult(message_matcher, arg.error().message(),
+                                     result_listener);
+}
+
+}  // namespace webrtc
+
+#endif  // API_TEST_RTC_ERROR_MATCHERS_H_
index 61c0ed701762519406d4f6580a44170d0c89eeb5..2989eec512ceb308aa65237e8216d26aebc5a2ac 100644 (file)
@@ -698,6 +698,13 @@ void FakeMediaEngine::SetVideoCodecs(const std::vector<Codec>& codecs) {
   video_->SetSendCodecs(codecs);
   video_->SetRecvCodecs(codecs);
 }
+void FakeMediaEngine::SetVideoRecvCodecs(const std::vector<Codec>& codecs) {
+  video_->SetRecvCodecs(codecs);
+}
+void FakeMediaEngine::SetVideoSendCodecs(const std::vector<Codec>& codecs) {
+  video_->SetSendCodecs(codecs);
+}
+
 void FakeMediaEngine::set_fail_create_channel(bool fail) {
   voice_->fail_create_channel_ = fail;
   video_->fail_create_channel_ = fail;
index b1e046ef579e4a0f6b946d6c281301d8c0ee9823..58b4eabe1cc47a74bd478f6d94c6a5958c797824 100644 (file)
@@ -857,6 +857,8 @@ class FakeMediaEngine : public CompositeMediaEngine {
   void SetAudioRecvCodecs(const std::vector<Codec>& codecs);
   void SetAudioSendCodecs(const std::vector<Codec>& codecs);
   void SetVideoCodecs(const std::vector<Codec>& codecs);
+  void SetVideoRecvCodecs(const std::vector<Codec>& codecs);
+  void SetVideoSendCodecs(const std::vector<Codec>& codecs);
 
   void set_fail_create_channel(bool fail);
 
index a701a9565c26d64bc4cd9b444eef79e0ddacf9e6..7cf12c92e0c37d741f8b343708d511b18b07a99f 100644 (file)
@@ -62,8 +62,7 @@ bool FakeWebRtcVideoDecoder::Configure(const Settings& settings) {
   return true;
 }
 
-int32_t FakeWebRtcVideoDecoder::Decode(const webrtc::EncodedImage&,
-                                       int64_t) {
+int32_t FakeWebRtcVideoDecoder::Decode(const webrtc::EncodedImage&, int64_t) {
   num_frames_received_++;
   return WEBRTC_VIDEO_CODEC_OK;
 }
@@ -118,6 +117,11 @@ void FakeWebRtcVideoDecoderFactory::DecoderDestroyed(
                   decoders_.end());
 }
 
+void FakeWebRtcVideoDecoderFactory::AddSupportedVideoCodec(
+    const webrtc::SdpVideoFormat& format) {
+  supported_codec_formats_.push_back(format);
+}
+
 void FakeWebRtcVideoDecoderFactory::AddSupportedVideoCodecType(
     const std::string& name) {
   // This is to match the default H264 params of cricket::Codec.
index 237085a5dd2da02b04e6db3cfe4895b964e4ce40..8d78e86dc5a3a7b5ac77ab5c9919dc7623e6f06c 100644 (file)
@@ -69,6 +69,7 @@ class FakeWebRtcVideoDecoderFactory : public webrtc::VideoDecoderFactory {
       const webrtc::SdpVideoFormat& format) override;
 
   void DecoderDestroyed(FakeWebRtcVideoDecoder* decoder);
+  void AddSupportedVideoCodec(const webrtc::SdpVideoFormat& format);
   void AddSupportedVideoCodecType(const std::string& name);
   int GetNumCreatedDecoders();
   const std::vector<FakeWebRtcVideoDecoder*>& decoders();
index 12940c7fbe6b73889f9cd730845c870cc3c3d6c7..1ff66f09d097244d6d013bc62fa3dcf3dba0b456 100644 (file)
@@ -2096,9 +2096,7 @@ if (rtc_include_tests && (!build_with_chromium || tizen_rtc_unittests)) {
     }
 
     if (tizen_rtc_unittests) {
-      deps -= [
-        "../system_wrappers:metrics",
-      ]
+      deps -= [ "../system_wrappers:metrics" ]
 
       deps += [
         "../rtc_base/synchronization:yield_policy",
@@ -2338,6 +2336,7 @@ if (rtc_include_tests && (!build_with_chromium || tizen_rtc_unittests)) {
       "../api:packet_socket_factory",
       "../api:priority",
       "../api:rtc_error",
+      "../api:rtc_error_matchers",
       "../api:rtp_sender_interface",
       "../api:rtp_transceiver_direction",
       "../api:scoped_refptr",
@@ -2432,6 +2431,7 @@ if (rtc_include_tests && (!build_with_chromium || tizen_rtc_unittests)) {
       "../test:rtc_expect_death",
       "../test:run_loop",
       "../test:scoped_key_value_config",
+      "../test:wait_until",
       "../test/pc/sctp:fake_sctp_transport",
       "//testing/gtest",
       "//third_party/abseil-cpp/absl/algorithm:container",
@@ -2440,9 +2440,7 @@ if (rtc_include_tests && (!build_with_chromium || tizen_rtc_unittests)) {
     ]
 
     if (tizen_rtc_unittests) {
-      deps -= [
-        "../system_wrappers:metrics",
-      ]
+      deps -= [ "../system_wrappers:metrics" ]
 
       deps += [
         "../rtc_base/synchronization:yield_policy",
index 2f895786817b43bda2d3d087b252355dae891763..3f6fccba572b6d79857db0505eb6c25bb0071767 100644 (file)
@@ -709,6 +709,7 @@ if (rtc_include_tests) {
         ":video_frame_writer",
         ":video_test_common",
         ":video_test_support",
+        ":wait_until",
         ":y4m_frame_generator",
         "../api:array_view",
         "../api:create_frame_generator",
@@ -717,6 +718,8 @@ if (rtc_include_tests) {
         "../api:mock_video_codec_factory",
         "../api:mock_video_decoder",
         "../api:mock_video_encoder",
+        "../api:rtc_error",
+        "../api:rtc_error_matchers",
         "../api:scoped_refptr",
         "../api:simulcast_test_fixture_api",
         "../api/environment",
@@ -729,6 +732,7 @@ if (rtc_include_tests) {
         "../api/units:data_size",
         "../api/units:frequency",
         "../api/units:time_delta",
+        "../api/units:timestamp",
         "../api/video:encoded_image",
         "../api/video:video_frame",
         "../api/video_codecs:builtin_video_decoder_factory",
@@ -749,8 +753,10 @@ if (rtc_include_tests) {
         "../modules/video_coding/svc:scalability_mode_util",
         "../rtc_base:criticalsection",
         "../rtc_base:rtc_event",
+        "../rtc_base:threading",
         "../rtc_base/synchronization:mutex",
         "../rtc_base/system:file_wrapper",
+        "../system_wrappers",
         "jitter:jitter_unittests",
         "pc/e2e:e2e_unittests",
         "pc/e2e/analyzer/video:video_analyzer_unittests",
@@ -782,6 +788,7 @@ if (rtc_include_tests) {
         "testsupport/yuv_frame_reader_unittest.cc",
         "testsupport/yuv_frame_writer_unittest.cc",
         "video_codec_tester_unittest.cc",
+        "wait_until_unittest.cc",
       ]
 
       if (rtc_enable_protobuf) {
@@ -1383,3 +1390,22 @@ rtc_library("video_codec_tester") {
     "//third_party/libyuv",
   ]
 }
+
+rtc_source_set("wait_until") {
+  testonly = true
+  sources = [
+    "wait_until.h",
+    "wait_until_internal.h",
+  ]
+  deps = [
+    ":test_support",
+    "../api:rtc_error",
+    "../api/units:time_delta",
+    "../api/units:timestamp",
+    "../rtc_base:checks",
+    "../rtc_base:threading",
+    "../system_wrappers",
+    "//third_party/abseil-cpp/absl/base:nullability",
+    "//third_party/abseil-cpp/absl/strings:string_view",
+  ]
+}
diff --git a/third_party/webrtc/test/wait_until.h b/third_party/webrtc/test/wait_until.h
new file mode 100644 (file)
index 0000000..a8f7ac2
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ *  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.
+ */
+
+#ifndef TEST_WAIT_UNTIL_H_
+#define TEST_WAIT_UNTIL_H_
+
+#include <string>
+
+#include "absl/base/nullability.h"
+#include "api/rtc_error.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/thread.h"
+#include "system_wrappers/include/clock.h"
+#include "test/gmock.h"
+#include "test/wait_until_internal.h"  // IWYU pragma: private
+
+namespace webrtc {
+
+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<SimulatedClock*> clock = nullptr;
+
+  // Name of the result to be used in the error message.
+  std::string result_name = "result";
+};
+
+// Runs a function `fn`, which returns a result, until `matcher` matches the
+// result.
+//
+// The function is called repeatedly until the result matches the matcher or the
+// timeout is reached. If the matcher matches the result, the result is
+// returned. Otherwise, an error is returned.
+//
+// Example:
+//
+//   int counter = 0;
+//   RTCErrorOr<int> result = Waituntil([&] { return ++counter; }, Eq(3))
+//   EXPECT_THAT(result, IsOkAndHolds(3));
+template <typename Fn, typename Matcher>
+auto WaitUntil(const Fn& fn, Matcher matcher, WaitUntilSettings settings = {})
+    -> RTCErrorOr<decltype(fn())> {
+  if (!settings.clock) {
+    RTC_CHECK(rtc::Thread::Current()) << "A current thread is required. An "
+                                         "rtc::AutoThread can work for tests.";
+  }
+
+  absl::Nonnull<Clock*> clock =
+      settings.clock ? settings.clock : Clock::GetRealTimeClock();
+
+  Timestamp start = clock->CurrentTime();
+
+  do {
+    auto result = fn();
+    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);
+
+  // 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;
+  }
+
+  return RTCError(RTCErrorType::INTERNAL_ERROR, listener.str());
+}
+
+}  // namespace webrtc
+
+#endif  // TEST_WAIT_UNTIL_H_
diff --git a/third_party/webrtc/test/wait_until_internal.h b/third_party/webrtc/test/wait_until_internal.h
new file mode 100644 (file)
index 0000000..5065e96
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ *  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.
+ */
+
+#ifndef TEST_WAIT_UNTIL_INTERNAL_H_
+#define TEST_WAIT_UNTIL_INTERNAL_H_
+
+#include <string>
+
+#include "absl/base/nullability.h"
+#include "absl/strings/string_view.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace wait_until_internal {
+
+// Explains the match result of `matcher` against `value` to `listener`.
+// `value_name` is the name of the value to be used in the error message.
+// This is inspired by testing::ExplainMatchResult and
+// testing::internal::MatchPrintAndExplain.
+template <typename T, typename M>
+bool ExplainMatchResult(
+    const M& matcher,
+    const T& value,
+    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<const T&> safe_matcher =
+      testing::SafeMatcherCast<const T&>(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)) {
+    return true;
+  }
+  *ss << testing::PrintToString(value);
+  if (const std::string& inner_message = inner_listener.str();
+      !inner_message.empty()) {
+    *ss << ", " << inner_message;
+  }
+  return false;
+}
+
+}  // namespace wait_until_internal
+}  // namespace webrtc
+
+#endif  // TEST_WAIT_UNTIL_INTERNAL_H_
diff --git a/third_party/webrtc/test/wait_until_unittest.cc b/third_party/webrtc/test/wait_until_unittest.cc
new file mode 100644 (file)
index 0000000..c519e9a
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ *  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 "api/rtc_error.h"
+#include "api/test/rtc_error_matchers.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+#include "rtc_base/thread.h"
+#include "system_wrappers/include/clock.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace {
+
+using testing::_;
+using testing::AllOf;
+using testing::Eq;
+using testing::Ge;
+using testing::Gt;
+using testing::Lt;
+using testing::MatchesRegex;
+
+TEST(WaitUntilTest, ReturnsWhenConditionIsMet) {
+  rtc::AutoThread thread;
+
+  int counter = 0;
+  RTCErrorOr<int> result = WaitUntil([&] { return ++counter; }, Eq(3));
+  EXPECT_THAT(result, IsRtcOkAndHolds(3));
+}
+
+TEST(WaitUntilTest, ReturnsErrorWhenTimeoutIsReached) {
+  rtc::AutoThread thread;
+  int counter = 0;
+  RTCErrorOr<int> result =
+      WaitUntil([&] { return --counter; }, Eq(1),
+                {.timeout = TimeDelta::Millis(10), .result_name = "counter"});
+  // Only returns the last error. Note we only are checking that the error
+  // message ends with a negative number rather than a specific number to avoid
+  // flakiness.
+  EXPECT_THAT(
+      result,
+      IsRtcErrorOrWithMessage(
+          _, MatchesRegex(
+                 "Value of: counter\nExpected: is equal to 1\nActual: -\\d+")));
+}
+
+TEST(WaitUntilTest, ErrorContainsMatcherExplanation) {
+  rtc::AutoThread thread;
+  int counter = 0;
+  auto matcher = AllOf(Gt(0), Lt(10));
+  RTCErrorOr<int> result =
+      WaitUntil([&] { return --counter; }, matcher,
+                {.timeout = TimeDelta::Millis(10), .result_name = "counter"});
+  // Only returns the last error. Note we only are checking that the error
+  // message ends with a negative number rather than a specific number to avoid
+  // flakiness.
+  EXPECT_THAT(
+      result,
+      IsRtcErrorOrWithMessage(
+          _, MatchesRegex("Value of: counter\nExpected: \\(is > 0\\) and "
+                          "\\(is < 10\\)\nActual: -\\d+, which doesn't match "
+                          "\\(is > 0\\)")));
+}
+
+TEST(WaitUntilTest, ReturnsWhenConditionIsMetWithSimulatedClock) {
+  SimulatedClock fake_clock = SimulatedClock(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)));
+}
+
+}  // namespace
+}  // namespace webrtc
\ No newline at end of file