Skip to content

Commit

Permalink
Googletest export
Browse files Browse the repository at this point in the history
Introduce a new matcher for unescaping Base-64 strings to gmock.

PiperOrigin-RevId: 388471904
  • Loading branch information
Abseil Team authored and Andy Soffer committed Aug 4, 2021
1 parent c22ce88 commit 652ec31
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 10 deletions.
21 changes: 11 additions & 10 deletions docs/reference/matchers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 47 additions & 0 deletions googlemock/include/gmock/gmock-matchers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions googlemock/include/gmock/internal/gmock-internal-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 54 additions & 0 deletions googlemock/src/gmock-internal-utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
40 changes: 40 additions & 0 deletions googlemock/test/gmock-internal-utils_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
27 changes: 27 additions & 0 deletions googlemock/test/gmock-matchers_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 652ec31

Please sign in to comment.