blob: fad36a07932dfc5839989cf2d9e4ec2340778ac5 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TEST_GMOCK_EXPECTED_SUPPORT_H_
#define BASE_TEST_GMOCK_EXPECTED_SUPPORT_H_
#include <ostream>
#include <string>
#include <type_traits>
#include <utility>
#include "base/strings/strcat.h"
#include "base/strings/to_string.h"
#include "base/types/expected.h"
#include "base/types/expected_internal.h"
#include "base/types/expected_macros.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base::test {
namespace internal {
// `HasVoidValueType<T>` is true iff `T` satisfies
// `base::internal::IsExpected<T>` and `T`'s `value_type` is `void`.
template <typename T>
concept HasVoidValueType =
base::internal::IsExpected<T> &&
std::is_void_v<typename std::remove_cvref_t<T>::value_type>;
// Implementation for matcher `HasValue`.
class HasValueMatcher {
public:
HasValueMatcher() = default;
template <typename T>
operator ::testing::Matcher<T>() const { // NOLINT
return ::testing::Matcher<T>(new Impl<const T&>());
}
private:
template <typename T>
requires(base::internal::IsExpected<T>)
class Impl : public ::testing::MatcherInterface<T> {
public:
Impl() = default;
void DescribeTo(std::ostream* os) const override {
*os << "is an 'expected' type with a value";
}
void DescribeNegationTo(std::ostream* os) const override {
*os << "is an 'expected' type with an error";
}
bool MatchAndExplain(
T actual_value,
::testing::MatchResultListener* listener) const override {
if (!actual_value.has_value()) {
*listener << "which has the error " << ToString(actual_value.error());
}
return actual_value.has_value();
}
};
};
// Implementation for matcher `ValueIs`.
template <typename T>
class ValueIsMatcher {
public:
explicit ValueIsMatcher(T matcher) : matcher_(std::move(matcher)) {}
template <typename U>
operator ::testing::Matcher<U>() const { // NOLINT
return ::testing::Matcher<U>(new Impl<const U&>(matcher_));
}
private:
template <typename U>
requires(base::internal::IsExpected<U> && !HasVoidValueType<U>)
class Impl : public ::testing::MatcherInterface<U> {
public:
explicit Impl(const T& matcher)
: matcher_(::testing::SafeMatcherCast<const V&>(matcher)) {}
void DescribeTo(std::ostream* os) const override {
*os << "is an 'expected' type with a value which ";
matcher_.DescribeTo(os);
}
void DescribeNegationTo(std::ostream* os) const override {
*os << "is an 'expected' type with an error or a value which ";
matcher_.DescribeNegationTo(os);
}
bool MatchAndExplain(
U actual_value,
::testing::MatchResultListener* listener) const override {
if (!actual_value.has_value()) {
*listener << "which has the error " << ToString(actual_value.error());
return false;
}
::testing::StringMatchResultListener inner_listener;
const bool match =
matcher_.MatchAndExplain(actual_value.value(), &inner_listener);
const std::string explanation = inner_listener.str();
if (!explanation.empty()) {
*listener << "which has the value " << ToString(actual_value.value())
<< ", " << explanation;
}
return match;
}
private:
using V = typename std::remove_cvref_t<U>::value_type;
const ::testing::Matcher<const V&> matcher_;
};
const T matcher_;
};
// Implementation for matcher `ErrorIs`.
template <typename T>
class ErrorIsMatcher {
public:
explicit ErrorIsMatcher(T matcher) : matcher_(std::move(matcher)) {}
template <typename U>
operator ::testing::Matcher<U>() const { // NOLINT
return ::testing::Matcher<U>(new Impl<const U&>(matcher_));
}
private:
template <typename U>
requires(base::internal::IsExpected<U>)
class Impl : public ::testing::MatcherInterface<U> {
public:
explicit Impl(const T& matcher)
: matcher_(::testing::SafeMatcherCast<const E&>(matcher)) {}
void DescribeTo(std::ostream* os) const override {
*os << "is an 'expected' type with an error which ";
matcher_.DescribeTo(os);
}
void DescribeNegationTo(std::ostream* os) const override {
*os << "is an 'expected' type with a value or an error which ";
matcher_.DescribeNegationTo(os);
}
bool MatchAndExplain(
U actual_value,
::testing::MatchResultListener* listener) const override {
if (actual_value.has_value()) {
if constexpr (HasVoidValueType<U>) {
*listener << "which has a value";
} else {
*listener << "which has the value " << ToString(actual_value.value());
}
return false;
}
::testing::StringMatchResultListener inner_listener;
const bool match =
matcher_.MatchAndExplain(actual_value.error(), &inner_listener);
const std::string explanation = inner_listener.str();
if (!explanation.empty()) {
*listener << "which has the error " << ToString(actual_value.error())
<< ", " << explanation;
}
return match;
}
private:
using E = typename std::remove_cvref_t<U>::error_type;
const ::testing::Matcher<const E&> matcher_;
};
private:
const T matcher_;
};
} // namespace internal
// Returns a gMock matcher that matches an `expected<T, E>` which has a value.
inline internal::HasValueMatcher HasValue() {
return internal::HasValueMatcher();
}
// Returns a gMock matcher that matches an `expected<T, E>` which has a non-void
// value which matches the inner matcher.
template <typename T>
inline internal::ValueIsMatcher<typename std::decay_t<T>> ValueIs(T&& matcher) {
return internal::ValueIsMatcher<typename std::decay_t<T>>(
std::forward<T>(matcher));
}
// Returns a gMock matcher that matches an `expected<T, E>` which has an error
// which matches the inner matcher.
template <typename T>
inline internal::ErrorIsMatcher<typename std::decay_t<T>> ErrorIs(T&& matcher) {
return internal::ErrorIsMatcher<typename std::decay_t<T>>(
std::forward<T>(matcher));
}
} // namespace base::test
// Executes an expression that returns an `expected<T, E>` or
// `std::optional<T>`, and assigns the contained `T` to `lhs` if the result is a
// value. If the result is an error, generates a test failure and returns from
// the current function, which must have a `void` return type. For more usage
// examples and caveats, see the documentation for `ASSIGN_OR_RETURN`.
//
// Example: Declaring and initializing a new value:
// ASSERT_OK_AND_ASSIGN(ValueType value, MaybeGetValue(arg));
//
// Example: Assigning to an existing value:
// ValueType value;
// ASSERT_OK_AND_ASSIGN(value, MaybeGetValue(arg));
#define ASSERT_OK_AND_ASSIGN(lhs, rexpr) \
ASSIGN_OR_RETURN(lhs, rexpr, []<typename... Ts>(const Ts&... e) { \
std::string message; \
if constexpr (sizeof...(Ts) > 0) { \
message = base::StrCat( \
{#rexpr, " returned error: ", (..., base::ToString(e))}); \
} else { \
message = base::StrCat({#rexpr, " returned nullopt"}); \
} \
return GTEST_MESSAGE_(message.c_str(), \
::testing::TestPartResult::kFatalFailure); \
})
namespace base {
template <typename T, typename E>
void PrintTo(const expected<T, E>& expected, ::std::ostream* os) {
*os << expected.ToString();
}
template <typename T>
void PrintTo(const ok<T>& a, ::std::ostream* os) {
*os << a.ToString();
}
template <typename T>
void PrintTo(const unexpected<T>& a, ::std::ostream* os) {
*os << a.ToString();
}
} // namespace base
#endif // BASE_TEST_GMOCK_EXPECTED_SUPPORT_H_