diff options
Diffstat (limited to 'pw_unit_test/public/pw_unit_test/googletest_test_matchers.h')
-rw-r--r-- | pw_unit_test/public/pw_unit_test/googletest_test_matchers.h | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/pw_unit_test/public/pw_unit_test/googletest_test_matchers.h b/pw_unit_test/public/pw_unit_test/googletest_test_matchers.h new file mode 100644 index 000000000..e7965aaa5 --- /dev/null +++ b/pw_unit_test/public/pw_unit_test/googletest_test_matchers.h @@ -0,0 +1,256 @@ +// Copyright 2023 The Pigweed 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. +#pragma once + +#include <type_traits> +#include <utility> + +#include "gmock/gmock-matchers.h" +#include "gtest/gtest.h" +#include "pw_result/result.h" +#include "pw_status/status.h" +#include "pw_status/status_with_size.h" + +namespace pw::unit_test { +namespace internal { +// Gets the pw::Status of different types of objects with a pw::Status for +// Matchers that check the status. +inline constexpr Status GetStatus(Status status) { return status; } + +inline constexpr Status GetStatus(StatusWithSize status_with_size) { + return status_with_size.status(); +} + +template <typename T> +inline constexpr Status GetStatus(const Result<T>& result) { + return result.status(); +} + +// Gets the value of an object whose value is guarded by a ``pw::OkStatus()``. +// Used by Matchers. +constexpr size_t GetValue(StatusWithSize status_with_size) { + return status_with_size.size(); +} + +template <typename V> +constexpr const V& GetValue(const Result<V>& result) { + return result.value(); +} + +template <typename V> +constexpr V GetValue(Result<V>&& result) { + return std::move(result).value(); +} + +// Implements IsOk(). +class IsOkMatcher { + public: + using is_gtest_matcher = void; + + void DescribeTo(std::ostream* os) const { *os << "is OK"; } + + void DescribeNegationTo(std::ostream* os) const { *os << "isn't OK"; } + + template <typename T> + bool MatchAndExplain(T&& actual_value, + ::testing::MatchResultListener* listener) const { + const auto status = GetStatus(actual_value); + if (!status.ok()) { + *listener << "which has status " << pw_StatusString(status); + return false; + } + return true; + } +}; + +// Implements IsOkAndHolds(m) as a monomorphic matcher. +template <typename StatusType> +class IsOkAndHoldsMatcherImpl { + public: + using is_gtest_matcher = void; + using ValueType = decltype(GetValue(std::declval<StatusType>())); + + // NOLINTBEGIN(bugprone-forwarding-reference-overload) + template <typename InnerMatcher> + explicit IsOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher) + : inner_matcher_(::testing::SafeMatcherCast<const ValueType&>( + std::forward<InnerMatcher>(inner_matcher))) {} + // NOLINTEND(bugprone-forwarding-reference-overload) + + void DescribeTo(std::ostream* os) const { + *os << "is OK and has a value that "; + inner_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const { + *os << "isn't OK or has a value that "; + inner_matcher_.DescribeNegationTo(os); + } + + bool MatchAndExplain(const StatusType& actual_value, + ::testing::MatchResultListener* listener) const { + const auto& status = GetStatus(actual_value); + if (!status.ok()) { + *listener << "which has status " << pw_StatusString(status); + return false; + } + + const auto& value = GetValue(actual_value); + *listener << "which contains value " << ::testing::PrintToString(value); + + ::testing::StringMatchResultListener inner_listener; + const bool matches = inner_matcher_.MatchAndExplain(value, &inner_listener); + const std::string inner_explanation = inner_listener.str(); + if (!inner_explanation.empty()) { + *listener << ", " << inner_explanation; + } + + return matches; + } + + private: + const ::testing::Matcher<const ValueType&> inner_matcher_; +}; + +// Implements IsOkAndHolds(m) as a polymorphic matcher. +// +// We have to manually create it as a class instead of using the +// `::testing::MakePolymorphicMatcher()` helper because of the custom conversion +// to Matcher<T>. +template <typename InnerMatcher> +class IsOkAndHoldsMatcher { + public: + explicit IsOkAndHoldsMatcher(InnerMatcher inner_matcher) + : inner_matcher_(std::move(inner_matcher)) {} + + // NOLINTBEGIN(google-explicit-constructor) + template <typename StatusType> + operator ::testing::Matcher<StatusType>() const { + return ::testing::Matcher<StatusType>( + internal::IsOkAndHoldsMatcherImpl<const StatusType&>(inner_matcher_)); + } + // NOLINTEND(google-explicit-constructor) + + private: + const InnerMatcher inner_matcher_; +}; + +// Implements StatusIs(). +class StatusIsMatcher { + public: + explicit StatusIsMatcher(Status expected_status) + : expected_status_(expected_status) {} + + void DescribeTo(std::ostream* os) const { + *os << "has status " << pw_StatusString(expected_status_); + } + + void DescribeNegationTo(std::ostream* os) const { + *os << "does not have status " << pw_StatusString(expected_status_); + } + + template <typename T> + bool MatchAndExplain(T&& actual_value, + ::testing::MatchResultListener* listener) const { + const auto status = GetStatus(actual_value); + if (status != expected_status_) { + *listener << "which has status " << pw_StatusString(status); + return false; + } + return true; + } + + private: + const Status expected_status_; +}; + +} // namespace internal + +/// Macros for testing the results of functions that return ``pw::Status``, +/// ``pw::StatusWithSize``, or ``pw::Result<T>`` (for any T). +#define EXPECT_OK(expression) EXPECT_THAT(expression, ::pw::unit_test::IsOk()) +#define ASSERT_OK(expression) ASSERT_THAT(expression, ::pw::unit_test::IsOk()) + +/// Returns a gMock matcher that matches a `pw::Status`, `pw::StatusWithSize`, +/// or `pw::Result<T>` (for any T) which is OK. +inline internal::IsOkMatcher IsOk() { return {}; } + +/// Returns a gMock matcher that matches a `pw::Status`, `pw::StatusWithSize`, +/// or `pw::Result<T>` (for any T) which has the given status. +inline auto StatusIs(Status expected_status) { + return ::testing::MakePolymorphicMatcher( + internal::StatusIsMatcher(expected_status)); +} + +/// Returns a gMock matcher that matches a `pw::StatusWithSize` or +/// `pw::Result<T>` (for any T) which is OK and holds a value matching the inner +/// matcher. +template <typename InnerMatcher> +inline internal::IsOkAndHoldsMatcher<InnerMatcher> IsOkAndHolds( + InnerMatcher&& inner_matcher) { + return internal::IsOkAndHoldsMatcher<InnerMatcher>( + std::forward<InnerMatcher>(inner_matcher)); +} + +/// Executes an expression that returns a `pw::Result` or `pw::StatusWithSize` +/// and assigns or moves that value to lhs if the error code is OK. If the +// status is non-OK, generates a test failure and returns from the current +/// function, which must have a void return type. +/// +/// The MOVE variant moves the content out of the `pw::Result` and into lhs. +/// This variant is required for move-only types. +// +/// Example: Declaring and initializing a new value. E.g.: +/// ASSERT_OK_AND_ASSIGN(auto value, MaybeGetValue(arg)); +/// ASSERT_OK_AND_ASSIGN(const ValueType& value, MaybeGetValue(arg)); +/// ASSERT_OK_AND_MOVE(auto ptr, MaybeGetUniquePtr(arg)) +/// +/// Example: Assigning to an existing value +/// ValueType value; +/// ASSERT_OK_AND_ASSIGN(value, MaybeGetValue(arg)); +/// +/// The value assignment example would expand into something like: +/// auto status_or_value = MaybeGetValue(arg); +/// ASSERT_OK(status_or_value.status()); +/// value = status_or_value.ValueOrDie(); +/// +/// WARNING: ASSERT_OK_AND_ASSIGN (and the move variant) expand into multiple +/// statements; it cannot be used in a single statement (e.g. as the body of +/// an if statement without {})! +#define ASSERT_OK_AND_ASSIGN(lhs, rexpr) \ + ASSERT_OK_AND_ASSIGN_DETAIL(UNIQUE_IDENTIFIER_DETAIL(__LINE__), lhs, rexpr) +#define ASSERT_OK_AND_MOVE(lhs, rexpr) \ + ASSERT_OK_AND_MOVE_DETAIL(UNIQUE_IDENTIFIER_DETAIL(__LINE__), lhs, rexpr) + +// NOLINTBEGIN(bugprone-macro-parentheses) +// The suggestion would produce bad code. +#define ASSERT_OK_AND_ASSIGN_DETAIL(result, lhs, rexpr) \ + const auto& result = (rexpr); \ + if (!result.ok()) { \ + FAIL() << #rexpr << " is not OK."; \ + } \ + lhs = ::pw::unit_test::internal::GetValue(result); +#define ASSERT_OK_AND_MOVE_DETAIL(result, lhs, rexpr) \ + auto&& result = (rexpr); \ + if (!result.ok()) { \ + FAIL() << #rexpr << " is not OK."; \ + } \ + lhs = ::pw::unit_test::internal::GetValue(std::move(result)); +// NOLINTEND(bugprone-macro-parentheses) + +#define UNIQUE_IDENTIFIER_DETAIL(line) UNIQUE_IDENTIFIER_EXPANDED_DETAIL(line) +#define UNIQUE_IDENTIFIER_EXPANDED_DETAIL(line) \ + _assert_ok_and_assign_unique_name_##line + +} // namespace pw::unit_test |