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