Commit cba31a95 by Zie Weaver Committed by Copybara-Service

Add GoogleTest matchers for absl::Status

PiperOrigin-RevId: 630072639
Change-Id: Ibbb166cc3c0479617c8e48abe8134b59a67a578f
parent d94c7aef
...@@ -609,6 +609,9 @@ set(ABSL_INTERNAL_TEST_DLL_FILES ...@@ -609,6 +609,9 @@ set(ABSL_INTERNAL_TEST_DLL_FILES
"random/internal/mock_overload_set.h" "random/internal/mock_overload_set.h"
"random/mocking_bit_gen.h" "random/mocking_bit_gen.h"
"random/mock_distributions.h" "random/mock_distributions.h"
"status/status_matchers.h"
"status/internal/status_matchers.cc"
"status/internal/status_matchers.h"
"strings/cordz_test_helpers.h" "strings/cordz_test_helpers.h"
"strings/cord_test_helpers.h" "strings/cord_test_helpers.h"
) )
...@@ -621,6 +624,7 @@ set(ABSL_INTERNAL_TEST_DLL_TARGETS ...@@ -621,6 +624,7 @@ set(ABSL_INTERNAL_TEST_DLL_TARGETS
"random_internal_distribution_test_util" "random_internal_distribution_test_util"
"random_internal_mock_overload_set" "random_internal_mock_overload_set"
"scoped_mock_log" "scoped_mock_log"
"status_matchers"
) )
include(CheckCXXSourceCompiles) include(CheckCXXSourceCompiles)
......
...@@ -129,3 +129,38 @@ cc_test( ...@@ -129,3 +129,38 @@ cc_test(
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",
], ],
) )
cc_library(
name = "status_matchers",
testonly = 1,
srcs = [
"internal/status_matchers.cc",
"internal/status_matchers.h",
],
hdrs = ["status_matchers.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":status",
":statusor",
"//absl/base:config",
"//absl/strings:string_view",
"@com_google_googletest//:gtest",
],
)
cc_test(
name = "status_matchers_test",
size = "small",
srcs = ["status_matchers_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":status",
":status_matchers",
":statusor",
"//absl/strings",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
...@@ -102,3 +102,40 @@ absl_cc_test( ...@@ -102,3 +102,40 @@ absl_cc_test(
absl::strings absl::strings
GTest::gmock_main GTest::gmock_main
) )
absl_cc_library(
NAME
status_matchers
HDRS
"status_matchers.h"
SRCS
"internal/status_matchers.h"
"internal/status_matchers.cc"
COPTS
${ABSL_DEFAULT_COPTS}
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
absl::base
absl::status
absl::statusor
absl::strings
GTest::gmock
GTest::gtest
PUBLIC
TESTONLY
)
absl_cc_test(
NAME
status_matchers_test
SRCS
"status_matchers_test.cc"
COPTS
${ABSL_TEST_COPTS}
DEPS
absl::status
absl::statusor
absl::status_matchers
GTest::gmock_main
)
// Copyright 2024 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// -----------------------------------------------------------------------------
// File: status_matchers.cc
// -----------------------------------------------------------------------------
#include "absl/status/internal/status_matchers.h"
#include <ostream>
#include <string>
#include "gmock/gmock.h"
#include "absl/base/config.h"
#include "absl/status/status.h"
namespace absl_testing {
ABSL_NAMESPACE_BEGIN
namespace status_internal {
void StatusIsMatcherCommonImpl::DescribeTo(std::ostream* os) const {
*os << ", has a status code that ";
code_matcher_.DescribeTo(os);
*os << ", and has an error message that ";
message_matcher_.DescribeTo(os);
}
void StatusIsMatcherCommonImpl::DescribeNegationTo(std::ostream* os) const {
*os << ", or has a status code that ";
code_matcher_.DescribeNegationTo(os);
*os << ", or has an error message that ";
message_matcher_.DescribeNegationTo(os);
}
bool StatusIsMatcherCommonImpl::MatchAndExplain(
const ::absl::Status& status,
::testing::MatchResultListener* result_listener) const {
::testing::StringMatchResultListener inner_listener;
if (!code_matcher_.MatchAndExplain(status.code(), &inner_listener)) {
*result_listener << (inner_listener.str().empty()
? "whose status code is wrong"
: "which has a status code " +
inner_listener.str());
return false;
}
if (!message_matcher_.Matches(std::string(status.message()))) {
*result_listener << "whose error message is wrong";
return false;
}
return true;
}
} // namespace status_internal
ABSL_NAMESPACE_END
} // namespace absl_testing
// Copyright 2024 The Abseil Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef ABSL_STATUS_INTERNAL_STATUS_MATCHERS_H_
#define ABSL_STATUS_INTERNAL_STATUS_MATCHERS_H_
#include <ostream> // NOLINT
#include <string>
#include <type_traits>
#include <utility>
#include "gmock/gmock.h"
#include "absl/base/config.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
namespace absl_testing {
ABSL_NAMESPACE_BEGIN
namespace status_internal {
inline const absl::Status& GetStatus(const absl::Status& status) {
return status;
}
template <typename T>
inline const absl::Status& GetStatus(const absl::StatusOr<T>& status) {
return status.status();
}
////////////////////////////////////////////////////////////
// Implementation of IsOkAndHolds().
// Monomorphic implementation of matcher IsOkAndHolds(m). StatusOrType is a
// reference to StatusOr<T>.
template <typename StatusOrType>
class IsOkAndHoldsMatcherImpl
: public ::testing::MatcherInterface<StatusOrType> {
public:
typedef
typename std::remove_reference<StatusOrType>::type::value_type value_type;
template <typename InnerMatcher>
explicit IsOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher)
: inner_matcher_(::testing::SafeMatcherCast<const value_type&>(
std::forward<InnerMatcher>(inner_matcher))) {}
void DescribeTo(std::ostream* os) const override {
*os << "is OK and has a value that ";
inner_matcher_.DescribeTo(os);
}
void DescribeNegationTo(std::ostream* os) const override {
*os << "isn't OK or has a value that ";
inner_matcher_.DescribeNegationTo(os);
}
bool MatchAndExplain(
StatusOrType actual_value,
::testing::MatchResultListener* result_listener) const override {
if (!actual_value.ok()) {
*result_listener << "which has status " << actual_value.status();
return false;
}
// Call through to the inner matcher.
return inner_matcher_.MatchAndExplain(*actual_value, result_listener);
}
private:
const ::testing::Matcher<const value_type&> inner_matcher_;
};
// Implements IsOkAndHolds(m) as a polymorphic matcher.
template <typename InnerMatcher>
class IsOkAndHoldsMatcher {
public:
explicit IsOkAndHoldsMatcher(InnerMatcher inner_matcher)
: inner_matcher_(std::forward<InnerMatcher>(inner_matcher)) {}
// Converts this polymorphic matcher to a monomorphic matcher of the
// given type. StatusOrType can be either StatusOr<T> or a
// reference to StatusOr<T>.
template <typename StatusOrType>
operator ::testing::Matcher<StatusOrType>() const { // NOLINT
return ::testing::Matcher<StatusOrType>(
new IsOkAndHoldsMatcherImpl<const StatusOrType&>(inner_matcher_));
}
private:
const InnerMatcher inner_matcher_;
};
////////////////////////////////////////////////////////////
// Implementation of StatusIs().
// `StatusCode` is implicitly convertible from `int`, `absl::StatusCode`, and
// is explicitly convertible to these types as well.
//
// We need this class because `absl::StatusCode` (as a scoped enum) is not
// implicitly convertible to `int`. In order to handle use cases like
// ```
// StatusIs(Anyof(absl::StatusCode::kUnknown, absl::StatusCode::kCancelled))
// ```
// which uses polymorphic matchers, we need to unify the interfaces into
// `Matcher<StatusCode>`.
class StatusCode {
public:
/*implicit*/ StatusCode(int code) // NOLINT
: code_(static_cast<::absl::StatusCode>(code)) {}
/*implicit*/ StatusCode(::absl::StatusCode code) : code_(code) {} // NOLINT
explicit operator int() const { return static_cast<int>(code_); }
friend inline void PrintTo(const StatusCode& code, std::ostream* os) {
// TODO(b/321095377): Change this to print the status code as a string.
*os << static_cast<int>(code);
}
private:
::absl::StatusCode code_;
};
// Relational operators to handle matchers like Eq, Lt, etc..
inline bool operator==(const StatusCode& lhs, const StatusCode& rhs) {
return static_cast<int>(lhs) == static_cast<int>(rhs);
}
inline bool operator!=(const StatusCode& lhs, const StatusCode& rhs) {
return static_cast<int>(lhs) != static_cast<int>(rhs);
}
// StatusIs() is a polymorphic matcher. This class is the common
// implementation of it shared by all types T where StatusIs() can be
// used as a Matcher<T>.
class StatusIsMatcherCommonImpl {
public:
StatusIsMatcherCommonImpl(
::testing::Matcher<StatusCode> code_matcher,
::testing::Matcher<absl::string_view> message_matcher)
: code_matcher_(std::move(code_matcher)),
message_matcher_(std::move(message_matcher)) {}
void DescribeTo(std::ostream* os) const;
void DescribeNegationTo(std::ostream* os) const;
bool MatchAndExplain(const absl::Status& status,
::testing::MatchResultListener* result_listener) const;
private:
const ::testing::Matcher<StatusCode> code_matcher_;
const ::testing::Matcher<absl::string_view> message_matcher_;
};
// Monomorphic implementation of matcher StatusIs() for a given type
// T. T can be Status, StatusOr<>, or a reference to either of them.
template <typename T>
class MonoStatusIsMatcherImpl : public ::testing::MatcherInterface<T> {
public:
explicit MonoStatusIsMatcherImpl(StatusIsMatcherCommonImpl common_impl)
: common_impl_(std::move(common_impl)) {}
void DescribeTo(std::ostream* os) const override {
common_impl_.DescribeTo(os);
}
void DescribeNegationTo(std::ostream* os) const override {
common_impl_.DescribeNegationTo(os);
}
bool MatchAndExplain(
T actual_value,
::testing::MatchResultListener* result_listener) const override {
return common_impl_.MatchAndExplain(GetStatus(actual_value),
result_listener);
}
private:
StatusIsMatcherCommonImpl common_impl_;
};
// Implements StatusIs() as a polymorphic matcher.
class StatusIsMatcher {
public:
template <typename StatusCodeMatcher, typename StatusMessageMatcher>
StatusIsMatcher(StatusCodeMatcher&& code_matcher,
StatusMessageMatcher&& message_matcher)
: common_impl_(::testing::MatcherCast<StatusCode>(
std::forward<StatusCodeMatcher>(code_matcher)),
::testing::MatcherCast<absl::string_view>(
std::forward<StatusMessageMatcher>(message_matcher))) {
}
// Converts this polymorphic matcher to a monomorphic matcher of the
// given type. T can be StatusOr<>, Status, or a reference to
// either of them.
template <typename T>
/*implicit*/ operator ::testing::Matcher<T>() const { // NOLINT
return ::testing::Matcher<T>(
new MonoStatusIsMatcherImpl<const T&>(common_impl_));
}
private:
const StatusIsMatcherCommonImpl common_impl_;
};
// Monomorphic implementation of matcher IsOk() for a given type T.
// T can be Status, StatusOr<>, or a reference to either of them.
template <typename T>
class MonoIsOkMatcherImpl : public ::testing::MatcherInterface<T> {
public:
void DescribeTo(std::ostream* os) const override { *os << "is OK"; }
void DescribeNegationTo(std::ostream* os) const override {
*os << "is not OK";
}
bool MatchAndExplain(T actual_value,
::testing::MatchResultListener*) const override {
return GetStatus(actual_value).ok();
}
};
// Implements IsOk() as a polymorphic matcher.
class IsOkMatcher {
public:
template <typename T>
/*implicit*/ operator ::testing::Matcher<T>() const { // NOLINT
return ::testing::Matcher<T>(new MonoIsOkMatcherImpl<const T&>());
}
};
} // namespace status_internal
ABSL_NAMESPACE_END
} // namespace absl_testing
#endif // ABSL_STATUS_INTERNAL_STATUS_MATCHERS_H_
// Copyright 2024 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// -----------------------------------------------------------------------------
// File: status_matchers.h
// -----------------------------------------------------------------------------
//
// Testing utilities for working with `absl::Status` and `absl::StatusOr`.
//
// Defines the following utilities:
//
// ===============
// `IsOkAndHolds(m)`
// ===============
//
// This gMock matcher matches a StatusOr<T> value whose status is OK
// and whose inner value matches matcher m. Example:
//
// ```
// using ::testing::MatchesRegex;
// using ::absl_testing::IsOkAndHolds;
// ...
// absl::StatusOr<string> maybe_name = ...;
// EXPECT_THAT(maybe_name, IsOkAndHolds(MatchesRegex("John .*")));
// ```
//
// ===============================
// `StatusIs(status_code_matcher)`
// ===============================
//
// This is a shorthand for
// `StatusIs(status_code_matcher, ::testing::_)`
// In other words, it's like the two-argument `StatusIs()`, except that it
// ignores error message.
//
// ===============
// `IsOk()`
// ===============
//
// Matches an `absl::Status` or `absl::StatusOr<T>` value whose status value
// is `absl::StatusCode::kOk.`
//
// Equivalent to 'StatusIs(absl::StatusCode::kOk)'.
// Example:
// ```
// using ::absl_testing::IsOk;
// ...
// absl::StatusOr<string> maybe_name = ...;
// EXPECT_THAT(maybe_name, IsOk());
// Status s = ...;
// EXPECT_THAT(s, IsOk());
// ```
#ifndef ABSL_STATUS_STATUS_MATCHERS_H_
#define ABSL_STATUS_STATUS_MATCHERS_H_
#include <ostream> // NOLINT
#include <type_traits>
#include <utility>
#include "gmock/gmock.h"
#include "absl/base/config.h"
#include "absl/status/internal/status_matchers.h"
namespace absl_testing {
ABSL_NAMESPACE_BEGIN
// Returns a gMock matcher that matches a StatusOr<> whose status is
// OK and whose value matches the inner matcher.
template <typename InnerMatcherT>
status_internal::IsOkAndHoldsMatcher<typename std::decay<InnerMatcherT>::type>
IsOkAndHolds(InnerMatcherT&& inner_matcher) {
return status_internal::IsOkAndHoldsMatcher<
typename std::decay<InnerMatcherT>::type>(
std::forward<InnerMatcherT>(inner_matcher));
}
// Returns a gMock matcher that matches a Status or StatusOr<> whose status code
// matches code_matcher and whose error message matches message_matcher.
// Typically, code_matcher will be an absl::StatusCode, e.g.
//
// StatusIs(absl::StatusCode::kInvalidArgument, "...")
template <typename StatusCodeMatcherT, typename StatusMessageMatcherT>
status_internal::StatusIsMatcher StatusIs(
StatusCodeMatcherT&& code_matcher,
StatusMessageMatcherT&& message_matcher) {
return status_internal::StatusIsMatcher(
std::forward<StatusCodeMatcherT>(code_matcher),
std::forward<StatusMessageMatcherT>(message_matcher));
}
// Returns a gMock matcher that matches a Status or StatusOr<> and whose status
// code matches code_matcher. See above for details.
template <typename StatusCodeMatcherT>
status_internal::StatusIsMatcher StatusIs(StatusCodeMatcherT&& code_matcher) {
return StatusIs(std::forward<StatusCodeMatcherT>(code_matcher), ::testing::_);
}
// Returns a gMock matcher that matches a Status or StatusOr<> which is OK.
inline status_internal::IsOkMatcher IsOk() {
return status_internal::IsOkMatcher();
}
ABSL_NAMESPACE_END
} // namespace absl_testing
#endif // ABSL_STATUS_STATUS_MATCHERS_H_
// Copyright 2024 The Abseil Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// -----------------------------------------------------------------------------
// File: status_matchers_test.cc
// -----------------------------------------------------------------------------
#include "absl/status/status_matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest-spi.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
namespace {
using ::absl_testing::IsOk;
using ::absl_testing::IsOkAndHolds;
using ::absl_testing::StatusIs;
using ::testing::Gt;
TEST(StatusMatcherTest, StatusIsOk) { EXPECT_THAT(absl::OkStatus(), IsOk()); }
TEST(StatusMatcherTest, StatusOrIsOk) {
absl::StatusOr<int> ok_int = {0};
EXPECT_THAT(ok_int, IsOk());
}
TEST(StatusMatcherTest, StatusIsNotOk) {
absl::Status error = absl::UnknownError("Smigla");
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(error, IsOk()), "Smigla");
}
TEST(StatusMatcherTest, StatusOrIsNotOk) {
absl::StatusOr<int> error = absl::UnknownError("Smigla");
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(error, IsOk()), "Smigla");
}
TEST(StatusMatcherTest, IsOkAndHolds) {
absl::StatusOr<int> ok_int = {4};
absl::StatusOr<absl::string_view> ok_str = {"text"};
EXPECT_THAT(ok_int, IsOkAndHolds(4));
EXPECT_THAT(ok_int, IsOkAndHolds(Gt(0)));
EXPECT_THAT(ok_str, IsOkAndHolds("text"));
}
TEST(StatusMatcherTest, IsOkAndHoldsFailure) {
absl::StatusOr<int> ok_int = {502};
absl::StatusOr<int> error = absl::UnknownError("Smigla");
absl::StatusOr<absl::string_view> ok_str = {"actual"};
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(ok_int, IsOkAndHolds(0)), "502");
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(error, IsOkAndHolds(0)), "Smigla");
EXPECT_NONFATAL_FAILURE(EXPECT_THAT(ok_str, IsOkAndHolds("expected")),
"actual");
}
TEST(StatusMatcherTest, StatusIs) {
absl::Status unknown = absl::UnknownError("unbekannt");
absl::Status invalid = absl::InvalidArgumentError("ungueltig");
EXPECT_THAT(absl::OkStatus(), StatusIs(absl::StatusCode::kOk));
EXPECT_THAT(absl::OkStatus(), StatusIs(0));
EXPECT_THAT(unknown, StatusIs(absl::StatusCode::kUnknown));
EXPECT_THAT(unknown, StatusIs(2));
EXPECT_THAT(unknown, StatusIs(absl::StatusCode::kUnknown, "unbekannt"));
EXPECT_THAT(invalid, StatusIs(absl::StatusCode::kInvalidArgument));
EXPECT_THAT(invalid, StatusIs(3));
EXPECT_THAT(invalid,
StatusIs(absl::StatusCode::kInvalidArgument, "ungueltig"));
}
TEST(StatusMatcherTest, StatusOrIs) {
absl::StatusOr<int> ok = {42};
absl::StatusOr<int> unknown = absl::UnknownError("unbekannt");
absl::StatusOr<absl::string_view> invalid =
absl::InvalidArgumentError("ungueltig");
EXPECT_THAT(ok, StatusIs(absl::StatusCode::kOk));
EXPECT_THAT(ok, StatusIs(0));
EXPECT_THAT(unknown, StatusIs(absl::StatusCode::kUnknown));
EXPECT_THAT(unknown, StatusIs(2));
EXPECT_THAT(unknown, StatusIs(absl::StatusCode::kUnknown, "unbekannt"));
EXPECT_THAT(invalid, StatusIs(absl::StatusCode::kInvalidArgument));
EXPECT_THAT(invalid, StatusIs(3));
EXPECT_THAT(invalid,
StatusIs(absl::StatusCode::kInvalidArgument, "ungueltig"));
}
TEST(StatusMatcherTest, StatusIsFailure) {
absl::Status unknown = absl::UnknownError("unbekannt");
absl::Status invalid = absl::InvalidArgumentError("ungueltig");
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(absl::OkStatus(),
StatusIs(absl::StatusCode::kInvalidArgument)),
"OK");
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(unknown, StatusIs(absl::StatusCode::kCancelled)), "UNKNOWN");
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(unknown, StatusIs(absl::StatusCode::kUnknown, "inconnu")),
"unbekannt");
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(invalid, StatusIs(absl::StatusCode::kOutOfRange)), "INVALID");
EXPECT_NONFATAL_FAILURE(
EXPECT_THAT(invalid,
StatusIs(absl::StatusCode::kInvalidArgument, "invalide")),
"ungueltig");
}
} // namespace
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment