Googletest export

Introduce a new matcher for unescaping Base-64 strings to gmock.

PiperOrigin-RevId: 388471904
This commit is contained in:
Abseil Team 2021-08-03 12:19:54 -04:00 committed by Andy Soffer
parent c22ce88775
commit 652ec31f9f
6 changed files with 181 additions and 10 deletions

View File

@ -88,16 +88,17 @@ The `argument` can be either a C string or a C++ string object:
| Matcher | Description |
| :---------------------- | :------------------------------------------------- |
| `ContainsRegex(string)` | `argument` matches the given regular expression. |
| `EndsWith(suffix)` | `argument` ends with string `suffix`. |
| `HasSubstr(string)` | `argument` contains `string` as a sub-string. |
| `IsEmpty()` | `argument` is an empty string. |
| `MatchesRegex(string)` | `argument` matches the given regular expression with the match starting at the first character and ending at the last character. |
| `StartsWith(prefix)` | `argument` starts with string `prefix`. |
| `StrCaseEq(string)` | `argument` is equal to `string`, ignoring case. |
| `StrCaseNe(string)` | `argument` is not equal to `string`, ignoring case. |
| `StrEq(string)` | `argument` is equal to `string`. |
| `StrNe(string)` | `argument` is not equal to `string`. |
| `ContainsRegex(string)` | `argument` matches the given regular expression. |
| `EndsWith(suffix)` | `argument` ends with string `suffix`. |
| `HasSubstr(string)` | `argument` contains `string` as a sub-string. |
| `IsEmpty()` | `argument` is an empty string. |
| `MatchesRegex(string)` | `argument` matches the given regular expression with the match starting at the first character and ending at the last character. |
| `StartsWith(prefix)` | `argument` starts with string `prefix`. |
| `StrCaseEq(string)` | `argument` is equal to `string`, ignoring case. |
| `StrCaseNe(string)` | `argument` is not equal to `string`, ignoring case. |
| `StrEq(string)` | `argument` is equal to `string`. |
| `StrNe(string)` | `argument` is not equal to `string`. |
| `WhenBase64Unescaped(m)` | `argument` is a base-64 escaped string whose unescaped string matches `m`. |
`ContainsRegex()` and `MatchesRegex()` take ownership of the `RE` object. They
use the regular expression syntax defined

View File

@ -1122,6 +1122,45 @@ class EndsWithMatcher {
const StringType suffix_;
};
// Implements the polymorphic WhenBase64Unescaped(matcher) matcher, which can be
// used as a Matcher<T> as long as T can be converted to a string.
class WhenBase64UnescapedMatcher {
public:
using is_gtest_matcher = void;
explicit WhenBase64UnescapedMatcher(
const Matcher<const std::string&>& internal_matcher)
: internal_matcher_(internal_matcher) {}
// Matches anything that can convert to std::string.
template <typename MatcheeStringType>
bool MatchAndExplain(const MatcheeStringType& s,
MatchResultListener* listener) const {
const std::string s2(s); // NOLINT (needed for working with string_view).
std::string unescaped;
if (!internal::Base64Unescape(s2, &unescaped)) {
if (listener != nullptr) {
*listener << "is not a valid base64 escaped string";
}
return false;
}
return MatchPrintAndExplain(unescaped, internal_matcher_, listener);
}
void DescribeTo(::std::ostream* os) const {
*os << "matches after Base64Unescape ";
internal_matcher_.DescribeTo(os);
}
void DescribeNegationTo(::std::ostream* os) const {
*os << "does not match after Base64Unescape ";
internal_matcher_.DescribeTo(os);
}
private:
const Matcher<const std::string&> internal_matcher_;
};
// Implements a matcher that compares the two fields of a 2-tuple
// using one of the ==, <=, <, etc, operators. The two fields being
// compared don't have to have the same type.
@ -4986,6 +5025,14 @@ inline internal::AddressMatcher<InnerMatcher> Address(
const InnerMatcher& inner_matcher) {
return internal::AddressMatcher<InnerMatcher>(inner_matcher);
}
// Matches a base64 escaped string, when the unescaped string matches the
// internal matcher.
template <typename MatcherType>
internal::WhenBase64UnescapedMatcher WhenBase64Unescaped(
const MatcherType& internal_matcher) {
return internal::WhenBase64UnescapedMatcher(internal_matcher);
}
} // namespace no_adl
// Returns a predicate that is satisfied by anything that matches the

View File

@ -447,6 +447,8 @@ struct Function<R(Args...)> {
template <typename R, typename... Args>
constexpr size_t Function<R(Args...)>::ArgumentCount;
bool Base64Unescape(const std::string& encoded, std::string* decoded);
#ifdef _MSC_VER
# pragma warning(pop)
#endif

View File

@ -37,8 +37,14 @@
#include "gmock/internal/gmock-internal-utils.h"
#include <ctype.h>
#include <array>
#include <cctype>
#include <cstdint>
#include <cstring>
#include <ostream> // NOLINT
#include <string>
#include "gmock/gmock.h"
#include "gmock/internal/gmock-port.h"
#include "gtest/gtest.h"
@ -196,5 +202,53 @@ GTEST_API_ void IllegalDoDefault(const char* file, int line) {
"the variable in various places.");
}
constexpr char UnBase64Impl(char c, const char* const base64, char carry) {
return *base64 == 0 ? static_cast<char>(65)
: *base64 == c ? carry
: UnBase64Impl(c, base64 + 1, carry + 1);
}
template <size_t... I>
constexpr std::array<char, 256> UnBase64Impl(IndexSequence<I...>,
const char* const base64) {
return {UnBase64Impl(I, base64, 0)...};
}
constexpr std::array<char, 256> UnBase64(const char* const base64) {
return UnBase64Impl(MakeIndexSequence<256>{}, base64);
}
static constexpr char kBase64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static constexpr std::array<char, 256> kUnBase64 = UnBase64(kBase64);
bool Base64Unescape(const std::string& encoded, std::string* decoded) {
decoded->clear();
size_t encoded_len = encoded.size();
decoded->reserve(3 * (encoded_len / 4) + (encoded_len % 4));
int bit_pos = 0;
char dst = 0;
for (int src : encoded) {
if (std::isspace(src) || src == '=') {
continue;
}
char src_bin = kUnBase64[src];
if (src_bin >= 64) {
decoded->clear();
return false;
}
if (bit_pos == 0) {
dst |= src_bin << 2;
bit_pos = 6;
} else {
dst |= static_cast<char>(src_bin >> (bit_pos - 2));
decoded->push_back(dst);
dst = static_cast<char>(src_bin << (10 - bit_pos));
bit_pos = (bit_pos + 6) % 8;
}
}
return true;
}
} // namespace internal
} // namespace testing

View File

@ -716,6 +716,46 @@ TEST(FunctionTest, LongArgumentList) {
F::MakeResultIgnoredValue>::value));
}
TEST(Base64Unescape, InvalidString) {
std::string unescaped;
EXPECT_FALSE(Base64Unescape("(invalid)", &unescaped));
}
TEST(Base64Unescape, ShortString) {
std::string unescaped;
EXPECT_TRUE(Base64Unescape("SGVsbG8gd29ybGQh", &unescaped));
EXPECT_EQ("Hello world!", unescaped);
}
TEST(Base64Unescape, ShortStringWithPadding) {
std::string unescaped;
EXPECT_TRUE(Base64Unescape("SGVsbG8gd29ybGQ=", &unescaped));
EXPECT_EQ("Hello world", unescaped);
}
TEST(Base64Unescape, ShortStringWithoutPadding) {
std::string unescaped;
EXPECT_TRUE(Base64Unescape("SGVsbG8gd29ybGQ", &unescaped));
EXPECT_EQ("Hello world", unescaped);
}
TEST(Base64Unescape, LongStringWithWhiteSpaces) {
std::string escaped =
R"(TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz
IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg
dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu
dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo
ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=)";
std::string expected =
"Man is distinguished, not only by his reason, but by this singular "
"passion from other animals, which is a lust of the mind, that by a "
"perseverance of delight in the continued and indefatigable generation "
"of knowledge, exceeds the short vehemence of any carnal pleasure.";
std::string unescaped;
EXPECT_TRUE(Base64Unescape(escaped, &unescaped));
EXPECT_EQ(expected, unescaped);
}
} // namespace
} // namespace internal
} // namespace testing

View File

@ -1866,6 +1866,33 @@ TEST(EndsWithTest, CanDescribeSelf) {
EXPECT_EQ("ends with \"Hi\"", Describe(m));
}
// Tests WhenBase64Unescaped.
TEST(WhenBase64UnescapedTest, MatchesUnescapedBase64Strings) {
const Matcher<const char*> m1 = WhenBase64Unescaped(EndsWith("!"));
EXPECT_FALSE(m1.Matches("invalid base64"));
EXPECT_FALSE(m1.Matches("aGVsbG8gd29ybGQ=")); // hello world
EXPECT_TRUE(m1.Matches("aGVsbG8gd29ybGQh")); // hello world!
const Matcher<const std::string&> m2 = WhenBase64Unescaped(EndsWith("!"));
EXPECT_FALSE(m2.Matches("invalid base64"));
EXPECT_FALSE(m2.Matches("aGVsbG8gd29ybGQ=")); // hello world
EXPECT_TRUE(m2.Matches("aGVsbG8gd29ybGQh")); // hello world!
#if GTEST_INTERNAL_HAS_STRING_VIEW
const Matcher<const internal::StringView&> m3 =
WhenBase64Unescaped(EndsWith("!"));
EXPECT_FALSE(m3.Matches("invalid base64"));
EXPECT_FALSE(m3.Matches("aGVsbG8gd29ybGQ=")); // hello world
EXPECT_TRUE(m3.Matches("aGVsbG8gd29ybGQh")); // hello world!
#endif // GTEST_INTERNAL_HAS_STRING_VIEW
}
TEST(WhenBase64UnescapedTest, CanDescribeSelf) {
const Matcher<const char*> m = WhenBase64Unescaped(EndsWith("!"));
EXPECT_EQ("matches after Base64Unescape ends with \"!\"", Describe(m));
}
// Tests MatchesRegex().
TEST(MatchesRegexTest, MatchesStringMatchingGivenRegex) {