xref: /aosp_15_r20/external/federated-compute/fcp/testing/testing.h (revision 14675a029014e728ec732f129a32e299b2da0601)
1 /*
2  * Copyright 2017 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef FCP_TESTING_TESTING_H_
18 #define FCP_TESTING_TESTING_H_
19 
20 #include <iostream>
21 #include <memory>
22 #include <string>
23 #include <type_traits>
24 
25 #include "google/protobuf/util/message_differencer.h"
26 #include "gmock/gmock.h"
27 #include "gtest/gtest.h"
28 #include "absl/status/status.h"
29 #include "absl/strings/string_view.h"
30 #include "fcp/base/error.h"
31 #include "fcp/base/monitoring.h"
32 #include "fcp/base/platform.h"
33 #include "fcp/base/result.h"
34 #include "fcp/base/source_location.h"
35 #include "fcp/testing/result_matchers.h"
36 
37 #include "fcp/testing/parse_text_proto.h"
38 
39 // This file defines platform dependent utilities for testing,
40 // based on the public version of googletest.
41 
42 namespace fcp {
43 
44 // A macro for use inside a GTest test that executes the provided code as a
45 // function returning a Result and asserts that the return value is not an
46 // Error.
47 //
48 // The code provided to the macro will be much like the code one would write in
49 // the body of a regular test, with the differences being that the code must
50 // return Result<Unit>, and only EXPECT_* statements are allowed, not ASSERT_*.
51 //
52 // This makes it possible to greatly simplify the test body by using FCP_TRY(),
53 // rather than having to check in the test body that every return value of
54 // Result type is not an error.
55 //
56 // Example:
57 //
58 //   TEST(FooTest, GetFoo) {
59 //     FCP_EXPECT_NO_ERROR(
60 //       Foo foo = FCP_TRY(GetFoo());
61 //       EXPECT_TRUE(foo.HasBar());
62 //       return Unit{};
63 //     );
64 //   }
65 #define FCP_EXPECT_NO_ERROR(test_contents)           \
66   auto test_fn = []() -> Result<Unit> test_contents; \
67   ASSERT_THAT(test_fn(), testing::Not(IsError()))
68 
69 // Convenience macros for `EXPECT_THAT(s, IsOk())`, where `s` is either
70 // a `Status` or a `StatusOr<T>`.
71 // Old versions of the protobuf library define EXPECT_OK as well, so we only
72 // conditionally define our version.
73 #if !defined(EXPECT_OK)
74 #define EXPECT_OK(result) EXPECT_THAT(result, fcp::IsOk())
75 #endif
76 #define ASSERT_OK(result) ASSERT_THAT(result, fcp::IsOk())
77 
78 /** Returns the current test's name. */
79 std::string TestName();
80 
81 /**
82  * Gets path to a test data file based on a path relative to project root.
83  */
84 std::string GetTestDataPath(absl::string_view relative_path);
85 
86 /**
87  * Creates a temporary file name with given suffix unique for the running test.
88  */
89 std::string TemporaryTestFile(absl::string_view suffix);
90 
91 /**
92  * Verifies a provided content against an expected stored in a baseline file.
93  * Returns an empty string if both are identical, otherwise a diagnostic
94  * message for error reports.
95  *
96  * A return status of not ok indicates an operational error which made the
97  * comparison impossible.
98  *
99  * The baseline file name must be provided relative to the project root.
100  */
101 StatusOr<std::string> VerifyAgainstBaseline(absl::string_view baseline_file,
102                                             absl::string_view content);
103 
104 /**
105  * Polymorphic matchers for Status or StatusOr on status code.
106  */
107 template <typename T>
IsCode(StatusOr<T> const & x,StatusCode code)108 bool IsCode(StatusOr<T> const& x, StatusCode code) {
109   return x.status().code() == code;
110 }
IsCode(Status const & x,StatusCode code)111 inline bool IsCode(Status const& x, StatusCode code) {
112   return x.code() == code;
113 }
114 
115 template <typename T>
116 class StatusMatcherImpl : public ::testing::MatcherInterface<T> {
117  public:
StatusMatcherImpl(StatusCode code)118   explicit StatusMatcherImpl(StatusCode code) : code_(code) {}
DescribeTo(::std::ostream * os)119   void DescribeTo(::std::ostream* os) const override {
120     *os << "is " << absl::StatusCodeToString(code_);
121   }
DescribeNegationTo(::std::ostream * os)122   void DescribeNegationTo(::std::ostream* os) const override {
123     *os << "is not " << absl::StatusCodeToString(code_);
124   }
MatchAndExplain(T x,::testing::MatchResultListener * listener)125   bool MatchAndExplain(
126       T x, ::testing::MatchResultListener* listener) const override {
127     return IsCode(x, code_);
128   }
129 
130  private:
131   StatusCode code_;
132 };
133 
134 class StatusMatcher {
135  public:
StatusMatcher(StatusCode code)136   explicit StatusMatcher(StatusCode code) : code_(code) {}
137 
138   template <typename T>
139   operator testing::Matcher<T>() const {  // NOLINT
140     return ::testing::MakeMatcher(new StatusMatcherImpl<T>(code_));
141   }
142 
143  private:
144   StatusCode code_;
145 };
146 
147 StatusMatcher IsCode(StatusCode code);
148 StatusMatcher IsOk();
149 
150 template <typename T>
151 class ProtoMatcherImpl : public ::testing::MatcherInterface<T> {
152  public:
ProtoMatcherImpl(const google::protobuf::Message & arg)153   explicit ProtoMatcherImpl(const google::protobuf::Message& arg)
154       : arg_(CloneMessage(arg)) {}
155 
ProtoMatcherImpl(const std::string & arg)156   explicit ProtoMatcherImpl(const std::string& arg) : arg_(ParseMessage(arg)) {}
157 
DescribeTo(::std::ostream * os)158   void DescribeTo(::std::ostream* os) const override {
159     *os << "is " << arg_->DebugString();
160   }
DescribeNegationTo(::std::ostream * os)161   void DescribeNegationTo(::std::ostream* os) const override {
162     *os << "is not " << arg_->DebugString();
163   }
MatchAndExplain(T x,::testing::MatchResultListener * listener)164   bool MatchAndExplain(
165       T x, ::testing::MatchResultListener* listener) const override {
166     if (x.GetDescriptor()->full_name() != arg_->GetDescriptor()->full_name()) {
167       *listener << "Argument proto is of type "
168                 << arg_->GetDescriptor()->full_name()
169                 << " but expected proto of type "
170                 << x.GetDescriptor()->full_name();
171       return false;
172     }
173 
174     google::protobuf::util::MessageDifferencer differencer;
175     std::string reported_differences;
176     differencer.ReportDifferencesToString(&reported_differences);
177     if (!differencer.Compare(*arg_, x)) {
178       *listener << reported_differences;
179       return false;
180     }
181     return true;
182   }
183 
184  private:
CloneMessage(const google::protobuf::Message & message)185   static std::unique_ptr<google::protobuf::Message> CloneMessage(
186       const google::protobuf::Message& message) {
187     std::unique_ptr<google::protobuf::Message> copy_of_message =
188         absl::WrapUnique(message.New());
189     copy_of_message->CopyFrom(message);
190     return copy_of_message;
191   }
192 
ParseMessage(const std::string & proto_text)193   static std::unique_ptr<google::protobuf::Message> ParseMessage(
194       const std::string& proto_text) {
195     using V = std::remove_cv_t<std::remove_reference_t<T>>;
196     std::unique_ptr<V> message = std::make_unique<V>();
197     *message = PARSE_TEXT_PROTO(proto_text);
198     return message;
199   }
200 
201   std::unique_ptr<google::protobuf::Message> arg_;
202 };
203 
204 template <typename T>
205 class ProtoMatcher {
206  public:
ProtoMatcher(const T & arg)207   explicit ProtoMatcher(const T& arg) : arg_(arg) {}
208 
209   template <typename U>
210   operator testing::Matcher<U>() const {  // NOLINT
211     using V = std::remove_cv_t<std::remove_reference_t<U>>;
212     static_assert(std::is_base_of<google::protobuf::Message, V>::value &&
213                   !std::is_same<google::protobuf::Message, V>::value);
214     return ::testing::MakeMatcher(new ProtoMatcherImpl<U>(arg_));
215   }
216 
217  private:
218   T arg_;
219 };
220 
221 // Proto matcher that takes another proto message reference as an argument.
222 template <class T,
223           typename std::enable_if<std::is_base_of<google::protobuf::Message, T>::value &&
224                                       !std::is_same<google::protobuf::Message, T>::value,
225                                   int>::type = 0>
EqualsProto(const T & arg)226 inline ProtoMatcher<T> EqualsProto(const T& arg) {
227   return ProtoMatcher<T>(arg);
228 }
229 
230 // Proto matcher that takes a text proto as an argument.
EqualsProto(const std::string & arg)231 inline ProtoMatcher<std::string> EqualsProto(const std::string& arg) {
232   return ProtoMatcher<std::string>(arg);
233 }
234 
235 // Utility function which creates and traces an instance of test error
236 Error TraceTestError(SourceLocation loc = SourceLocation::current());
237 
238 }  // namespace fcp
239 
240 #endif  // FCP_TESTING_TESTING_H_
241