/* * Copyright 2017 Google LLC * * 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 * * http://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 FCP_TESTING_TESTING_H_ #define FCP_TESTING_TESTING_H_ #include #include #include #include #include "google/protobuf/util/message_differencer.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/status/status.h" #include "absl/strings/string_view.h" #include "fcp/base/error.h" #include "fcp/base/monitoring.h" #include "fcp/base/platform.h" #include "fcp/base/result.h" #include "fcp/base/source_location.h" #include "fcp/testing/result_matchers.h" #include "fcp/testing/parse_text_proto.h" // This file defines platform dependent utilities for testing, // based on the public version of googletest. namespace fcp { // A macro for use inside a GTest test that executes the provided code as a // function returning a Result and asserts that the return value is not an // Error. // // The code provided to the macro will be much like the code one would write in // the body of a regular test, with the differences being that the code must // return Result, and only EXPECT_* statements are allowed, not ASSERT_*. // // This makes it possible to greatly simplify the test body by using FCP_TRY(), // rather than having to check in the test body that every return value of // Result type is not an error. // // Example: // // TEST(FooTest, GetFoo) { // FCP_EXPECT_NO_ERROR( // Foo foo = FCP_TRY(GetFoo()); // EXPECT_TRUE(foo.HasBar()); // return Unit{}; // ); // } #define FCP_EXPECT_NO_ERROR(test_contents) \ auto test_fn = []() -> Result test_contents; \ ASSERT_THAT(test_fn(), testing::Not(IsError())) // Convenience macros for `EXPECT_THAT(s, IsOk())`, where `s` is either // a `Status` or a `StatusOr`. // Old versions of the protobuf library define EXPECT_OK as well, so we only // conditionally define our version. #if !defined(EXPECT_OK) #define EXPECT_OK(result) EXPECT_THAT(result, fcp::IsOk()) #endif #define ASSERT_OK(result) ASSERT_THAT(result, fcp::IsOk()) /** Returns the current test's name. */ std::string TestName(); /** * Gets path to a test data file based on a path relative to project root. */ std::string GetTestDataPath(absl::string_view relative_path); /** * Creates a temporary file name with given suffix unique for the running test. */ std::string TemporaryTestFile(absl::string_view suffix); /** * Verifies a provided content against an expected stored in a baseline file. * Returns an empty string if both are identical, otherwise a diagnostic * message for error reports. * * A return status of not ok indicates an operational error which made the * comparison impossible. * * The baseline file name must be provided relative to the project root. */ StatusOr VerifyAgainstBaseline(absl::string_view baseline_file, absl::string_view content); /** * Polymorphic matchers for Status or StatusOr on status code. */ template bool IsCode(StatusOr const& x, StatusCode code) { return x.status().code() == code; } inline bool IsCode(Status const& x, StatusCode code) { return x.code() == code; } template class StatusMatcherImpl : public ::testing::MatcherInterface { public: explicit StatusMatcherImpl(StatusCode code) : code_(code) {} void DescribeTo(::std::ostream* os) const override { *os << "is " << absl::StatusCodeToString(code_); } void DescribeNegationTo(::std::ostream* os) const override { *os << "is not " << absl::StatusCodeToString(code_); } bool MatchAndExplain( T x, ::testing::MatchResultListener* listener) const override { return IsCode(x, code_); } private: StatusCode code_; }; class StatusMatcher { public: explicit StatusMatcher(StatusCode code) : code_(code) {} template operator testing::Matcher() const { // NOLINT return ::testing::MakeMatcher(new StatusMatcherImpl(code_)); } private: StatusCode code_; }; StatusMatcher IsCode(StatusCode code); StatusMatcher IsOk(); template class ProtoMatcherImpl : public ::testing::MatcherInterface { public: explicit ProtoMatcherImpl(const google::protobuf::Message& arg) : arg_(CloneMessage(arg)) {} explicit ProtoMatcherImpl(const std::string& arg) : arg_(ParseMessage(arg)) {} void DescribeTo(::std::ostream* os) const override { *os << "is " << arg_->DebugString(); } void DescribeNegationTo(::std::ostream* os) const override { *os << "is not " << arg_->DebugString(); } bool MatchAndExplain( T x, ::testing::MatchResultListener* listener) const override { if (x.GetDescriptor()->full_name() != arg_->GetDescriptor()->full_name()) { *listener << "Argument proto is of type " << arg_->GetDescriptor()->full_name() << " but expected proto of type " << x.GetDescriptor()->full_name(); return false; } google::protobuf::util::MessageDifferencer differencer; std::string reported_differences; differencer.ReportDifferencesToString(&reported_differences); if (!differencer.Compare(*arg_, x)) { *listener << reported_differences; return false; } return true; } private: static std::unique_ptr CloneMessage( const google::protobuf::Message& message) { std::unique_ptr copy_of_message = absl::WrapUnique(message.New()); copy_of_message->CopyFrom(message); return copy_of_message; } static std::unique_ptr ParseMessage( const std::string& proto_text) { using V = std::remove_cv_t>; std::unique_ptr message = std::make_unique(); *message = PARSE_TEXT_PROTO(proto_text); return message; } std::unique_ptr arg_; }; template class ProtoMatcher { public: explicit ProtoMatcher(const T& arg) : arg_(arg) {} template operator testing::Matcher() const { // NOLINT using V = std::remove_cv_t>; static_assert(std::is_base_of::value && !std::is_same::value); return ::testing::MakeMatcher(new ProtoMatcherImpl(arg_)); } private: T arg_; }; // Proto matcher that takes another proto message reference as an argument. template ::value && !std::is_same::value, int>::type = 0> inline ProtoMatcher EqualsProto(const T& arg) { return ProtoMatcher(arg); } // Proto matcher that takes a text proto as an argument. inline ProtoMatcher EqualsProto(const std::string& arg) { return ProtoMatcher(arg); } // Utility function which creates and traces an instance of test error Error TraceTestError(SourceLocation loc = SourceLocation::current()); } // namespace fcp #endif // FCP_TESTING_TESTING_H_