Add an ErrorsAreAndClear test helper.

Usage:

```
EXPECT_TRUE(ErrorsAreAndClear({ERR_LIB_EVP, std::nullopt},
                              {std::nullopt, ERR_R_MALLOC_FAILURE});
```

where `nullopt`s are wildcards.

Bug: 42290241
Change-Id: Id4eeb543bd9130e4ae75d185fbe7ce396a6a6964
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/99007
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Rudolf Polzer <rpolzer@google.com>
diff --git a/crypto/test/test_util.cc b/crypto/test/test_util.cc
index 28ab074..716de41 100644
--- a/crypto/test/test_util.cc
+++ b/crypto/test/test_util.cc
@@ -70,18 +70,64 @@
   return ret;
 }
 
-testing::AssertionResult ErrorEquals(uint32_t err, int lib, int reason) {
-  if (ERR_equals(err, lib, reason)) {
+testing::AssertionResult ErrorEquals(uint32_t err, std::optional<int> lib,
+                                     std::optional<int> reason) {
+  bool lib_matches = !lib.has_value() || (ERR_GET_LIB(err) == lib.value());
+  bool reason_matches =
+      !reason.has_value() || (ERR_GET_REASON(err) == reason.value());
+
+  if (lib_matches && reason_matches) {
     return testing::AssertionSuccess();
   }
 
   char buf[128], expected[128];
-  return testing::AssertionFailure()
-         << "Got \"" << ERR_error_string_n(err, buf, sizeof(buf))
-         << "\", wanted \""
-         << ERR_error_string_n(ERR_PACK(lib, reason), expected,
-                               sizeof(expected))
-         << "\"";
+  if (lib.has_value() && reason.has_value()) {
+    return testing::AssertionFailure()
+           << "Got \"" << ERR_error_string_n(err, buf, sizeof(buf))
+           << "\", wanted \""
+           << ERR_error_string_n(ERR_PACK(lib.value(), reason.value()),
+                                 expected, sizeof(expected))
+           << "\"";
+  } else if (lib.has_value()) {
+    return testing::AssertionFailure()
+           << "Got \"" << ERR_error_string_n(err, buf, sizeof(buf))
+           << "\", wanted something with library \""
+           << ERR_lib_error_string(ERR_PACK(lib.value(), 0)) << "\"";
+  } else if (reason.has_value()) {
+    return testing::AssertionFailure()
+           << "Got \"" << ERR_error_string_n(err, buf, sizeof(buf))
+           << "\", wanted something with reason \""
+           << ERR_reason_error_string(ERR_PACK(0, reason.value())) << "\"";
+  } else {
+    return testing::AssertionFailure()
+           << "Unreachable code: the always-true assertion failed";
+  }
+}
+
+testing::AssertionResult ErrorsAreAndClear(
+    std::initializer_list<std::pair<std::optional<int>, std::optional<int>>>
+        libs_and_reasons) {
+  if (libs_and_reasons.size() == 0) {
+    return testing::AssertionFailure()
+           << "ErrorsAreAndClear with empty list of errors is nonsensical - "
+              "just use ERR_clear_error directly!";
+  }
+  bool have_failures = false;
+  testing::AssertionResult all_failures = testing::AssertionFailure();
+  for (const auto &[lib, reason] : libs_and_reasons) {
+    uint32_t err = ERR_get_error();
+    testing::AssertionResult this_result = ErrorEquals(err, lib, reason);
+    if (this_result) {
+      continue;
+    }
+    all_failures << this_result.message();
+    have_failures = true;
+  }
+  if (have_failures) {
+    return all_failures;
+  }
+  ERR_clear_error();
+  return testing::AssertionSuccess();
 }
 
 bssl::UniquePtr<BIGNUM> HexToBIGNUM(const char *hex) {
diff --git a/crypto/test/test_util.h b/crypto/test/test_util.h
index af6c6ec..a5101f0 100644
--- a/crypto/test/test_util.h
+++ b/crypto/test/test_util.h
@@ -20,8 +20,11 @@
 #include <stdio.h>
 #include <string.h>
 
+#include <initializer_list>
 #include <iosfwd>
+#include <optional>
 #include <string_view>
+#include <utility>
 #include <vector>
 
 #include <gtest/gtest.h>
@@ -73,8 +76,18 @@
 std::string EncodeHex(bssl::Span<const uint8_t> in);
 
 // ErrorEquals asserts that `err` is an error with library `lib` and reason
-// `reason`.
-testing::AssertionResult ErrorEquals(uint32_t err, int lib, int reason);
+// `reason`. Pass `std::nullopt` to either of them to not assert on it.
+testing::AssertionResult ErrorEquals(uint32_t err, std::optional<int> lib,
+                                     std::optional<int> reason);
+
+// ErrorsAreAndClear asserts that the first (i.e. least recent, and thus most
+// specific) errors on the error queue are as specified, and then clears the
+// remainder of the queue. The first entry in `libs_and_reasons` shall be the
+// error first read from `ERR_get_error`. `libs_and_reasons` is not allowed to
+// be empty; instead, to just clear and assert nothing, call `ERR_clear_error`.
+testing::AssertionResult ErrorsAreAndClear(
+    std::initializer_list<std::pair<std::optional<int>, std::optional<int>>>
+        libs_and_reasons);
 
 // HexToBignum decodes `hex` as a hexadecimal, big-endian, unsigned integer and
 // returns it as a `BIGNUM`, or nullptr on error.