1 // Copyright 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #ifndef FCP_TRACING_TEST_TRACING_RECORDER_H_
16 #define FCP_TRACING_TEST_TRACING_RECORDER_H_
17
18 #include <memory>
19 #include <string>
20 #include <utility>
21 #include <vector>
22
23 #include "gmock/gmock.h"
24 #include "absl/base/thread_annotations.h"
25 #include "absl/container/flat_hash_map.h"
26 #include "absl/container/flat_hash_set.h"
27 #include "absl/synchronization/mutex.h"
28 #include "fcp/base/source_location.h"
29 #include "fcp/tracing/test_tracing_recorder_impl.h"
30 #include "fcp/tracing/tracing_recorder.h"
31
32 namespace fcp {
33
34 // Tracing recorder recording all interactions for use in unit tests.
35 // Automatically installs itself to be a global for its lifetime.
36 //
37 // Provides functionality for setting expected errors and failing if either
38 // an error is seen that was not expected, or one of the expected errors is
39 // never seen.
40 //
41 // Also provides functionality for searching for particular tracing spans or
42 // events, and verifying expectations about the children of a span or event
43 // (if desired, one could write a test that verifies the entire expected tree
44 // of traces.) See fcp/tracing/test/tracing_test.cc for examples.
45 //
46 // Upon creation of a tracing span or event, a Record representing the trace is
47 // added to a centrally owned hashmap (which is protected by a lock, as tracing
48 // spans and events may be created from multiple threads.) Each record stores
49 // a reference to its parent.
50 //
51 // When verifying the expected traces in a test, the tree structure of the
52 // traces is reconstructed by searching through the values in the map for
53 // children of a given parent ID. An ordering of siblings is re-established
54 // by sorting by the tracing span ID. This strategy is due to the fact that
55 // children of a single parent may be created from multiple threads (think a
56 // parent spawning many fibers.)
57 //
58 // By not creating the tree structure until it is needed in a test, we avoid
59 // additional locking on a parent node at trace creation time. This does incur
60 // a performance penalty when verifying expectations, however- if one is to
61 // verify the full tree of traces the overhead to reconstruct the tree will be
62 // O(n^2).
63 class TestTracingRecorder
64 : public TracingRecorder,
65 tracing_internal::TestTracingRecorderImpl::TraceListener {
66 public:
67 explicit TestTracingRecorder(SourceLocation loc = SourceLocation::current());
68 ~TestTracingRecorder() override;
69
70 // Allowlists a given error type as expected (non-allowlisted errors will
71 // trigger testing assertions.)
72 template <typename FlatBufferTable>
73 void ExpectError();
74
75 struct Record {
RecordRecord76 explicit Record(TracingSpanId parent_id, TracingSpanId id,
77 flatbuffers::DetachedBuffer data)
78 : parent_id(parent_id), id(id), data(std::move(data)) {}
RecordRecord79 explicit Record(TracingSpanId id, flatbuffers::DetachedBuffer data)
80 : parent_id(std::nullopt), id(id), data(std::move(data)) {}
81 std::optional<TracingSpanId> parent_id;
82 TracingSpanId id;
83 flatbuffers::DetachedBuffer data;
84 };
85
86 template <typename FlatBufferTable>
87 class Span;
88 template <typename FlatBufferTable>
89 class Event;
90
91 // Allows to dynamically access properties of spans or event, associated
92 // with an encapsulated tracing record.
93 class SpanOrEvent {
94 public:
95 explicit SpanOrEvent(TestTracingRecorder* recorder, Record* record);
96
97 // To integrate easily woth googletest/gmock, SpanOrEvent behaves like
98 // a collection of children SpanOrEvent objects. Following group of methods
99 // mimic STL collection interface by delegating calls to std::vector.
100 using value_type = SpanOrEvent;
101 using iterator = std::vector<SpanOrEvent>::iterator;
102 using const_iterator = std::vector<SpanOrEvent>::const_iterator;
begin()103 const_iterator begin() const { return children_.begin(); }
end()104 const_iterator end() const { return children_.end(); }
105 const SpanOrEvent& operator[](size_t idx) { return children_.at(idx); }
empty()106 bool empty() const { return children_.empty(); }
size()107 size_t size() const { return children_.size(); }
108
109 // Checks if span/event is of certain type:
110 template <typename FlatBufferTable>
111 bool HasType() const;
112
113 // Returns typed flatbuffer pointer (fails if type mismatch)
114 template <typename FlatBufferTable>
115 const FlatBufferTable* data() const;
116
117 // Creates text representation:
118 std::string TextFormat() const;
119
120 // Find all spans of given type recursively:
121 template <typename FlatBufferTable>
122 std::vector<Span<FlatBufferTable>> FindAllSpans();
123
124 // Find all events of given type recursively:
125 template <typename FlatBufferTable>
126 std::vector<Event<FlatBufferTable>> FindAllEvents();
127
128 // Find exactly one span recursively, fails if not found
129 template <typename FlatBufferTable>
130 Span<FlatBufferTable> FindOnlySpan(
131 SourceLocation loc = SourceLocation::current());
132
133 // Find exactly one event recursively, fails if not found
134 template <typename FlatBufferTable>
135 Event<FlatBufferTable> FindOnlyEvent(
136 SourceLocation loc = SourceLocation::current());
137
138 TracingTraitsBase const* traits() const;
139
140 protected:
141 Record* record_;
142
143 private:
144 std::vector<SpanOrEvent> children_;
145 TestTracingRecorder* recorder_;
146 };
147
148 // This is used to access root span
149 class RootSpan : public SpanOrEvent {
150 public:
RootSpan(TestTracingRecorder * recorder,Record * record)151 explicit RootSpan(TestTracingRecorder* recorder, Record* record)
152 : SpanOrEvent(recorder, record) {}
153 };
154
155 // Strongly-typed accessor for spans
156 template <typename FlatBufferTable>
157 class Span : public SpanOrEvent {
158 public:
159 explicit Span(TestTracingRecorder* recorder, Record* record);
160
161 // Re-declaring SpanOrEvent::data() for convenience, so there's no need
162 // to specify type argument when calling it.
163 const FlatBufferTable* data() const;
164 };
165
166 // Strongly-typed accessor for events
167 template <typename FlatBufferTable>
168 class Event : public SpanOrEvent {
169 public:
170 explicit Event(TestTracingRecorder* recorder, Record* record);
171
172 // Re-declaring SpanOrEvent::data() for convenience, so there's no need
173 // to specify type argument when calling it.
174 const FlatBufferTable* data() const;
175 };
176
177 RootSpan root();
178
179 template <typename FlatBufferTable>
FindAllSpans()180 std::vector<Span<FlatBufferTable>> FindAllSpans() {
181 return root().FindAllSpans<FlatBufferTable>();
182 }
183
184 template <typename FlatBufferTable>
FindAllEvents()185 std::vector<Event<FlatBufferTable>> FindAllEvents() {
186 return root().FindAllEvents<FlatBufferTable>();
187 }
188
189 template <typename FlatBufferTable>
190 Span<FlatBufferTable> FindOnlySpan(
191 SourceLocation loc = SourceLocation::current()) {
192 return root().FindOnlySpan<FlatBufferTable>(loc);
193 }
194
195 template <typename FlatBufferTable>
196 Event<FlatBufferTable> FindOnlyEvent(
197 SourceLocation loc = SourceLocation::current()) {
198 return root().FindOnlyEvent<FlatBufferTable>(loc);
199 }
200
201 private:
202 void InstallAsGlobal() override;
203 void UninstallAsGlobal() override;
204 void InstallAsThreadLocal() override;
205 void UninstallAsThreadLocal() override;
206
207 // Retrieves all children of the trace represented by the provided record
208 // by iterating through a hashmap to identify children that reference this
209 // record's ID as their parent.
210 //
211 // parent is borrowed by this function and must live for the duration of the
212 // function.
213 //
214 // The records in the returned vector are borrowed by the caller and will live
215 // until the destruction of this TestTracingRecorder.
216 std::vector<Record*> GetChildren(Record* parent);
217 // Implements TraceListener interface to react to creation of the root span by
218 // creating a record representing the root span, saving it in a map of all
219 // traces, and saving a pointer to it.
220 void OnRoot(TracingSpanId id, flatbuffers::DetachedBuffer data) override;
221 // Implements TraceListener interface which creates a record
222 // representing the span or event identified by id, adds it to the map of all
223 // traces to make it possible to find for verifying test expectations. Also
224 // checks of this is an event that has error severity and if so, checks that
225 // this is an expected error as registered by ExpectError().
226 void OnTrace(TracingSpanId parent_id, TracingSpanId id,
227 flatbuffers::DetachedBuffer data) override;
228
229 SourceLocation loc_;
230 Record* root_record_ = nullptr;
231 absl::Mutex map_lock_;
232 // Global map for storing traces. This map will only grow during the
233 // lifetime of this TestTracingRecorder (elements will never be removed or
234 // replaced.)
235 absl::flat_hash_map<TracingSpanId, std::unique_ptr<Record>> id_to_record_map_
236 ABSL_GUARDED_BY(map_lock_);
237 std::shared_ptr<tracing_internal::TestTracingRecorderImpl> impl_;
238
239 absl::Mutex expected_errors_lock_;
240 // Expectations registered by the test for error events that should be created
241 // during test execution.
242 absl::flat_hash_set<TracingTag> expected_errors_
243 ABSL_GUARDED_BY(expected_errors_lock_);
244 // Errors that are expected but have not yet been seen. If any are present on
245 // destruction of this object, a precondition check will fail.
246 absl::flat_hash_set<TracingTag> unseen_expected_errors_
247 ABSL_GUARDED_BY(expected_errors_lock_);
248 };
249
250 template <typename FlatBufferTable>
ExpectError()251 void TestTracingRecorder::ExpectError() {
252 absl::MutexLock locked(&expected_errors_lock_);
253 expected_errors_.insert(TracingTraits<FlatBufferTable>::kTag);
254 unseen_expected_errors_.insert(TracingTraits<FlatBufferTable>::kTag);
255 }
256
257 template <typename FlatBufferTable>
Event(TestTracingRecorder * recorder,Record * record)258 TestTracingRecorder::Event<FlatBufferTable>::Event(
259 TestTracingRecorder* recorder, Record* record)
260 : SpanOrEvent(recorder, record) {
261 static_assert(!TracingTraits<FlatBufferTable>::kIsSpan,
262 "FlatBufferTable must be an event, not a span");
263 }
264
265 template <typename FlatBufferTable>
data()266 const FlatBufferTable* TestTracingRecorder::Event<FlatBufferTable>::data()
267 const {
268 return SpanOrEvent::data<FlatBufferTable>();
269 }
270
271 template <typename FlatBufferTable>
Span(TestTracingRecorder * recorder,Record * record)272 TestTracingRecorder::Span<FlatBufferTable>::Span(TestTracingRecorder* recorder,
273 Record* record)
274 : SpanOrEvent(recorder, record) {
275 static_assert(TracingTraits<FlatBufferTable>::kIsSpan,
276 "FlatBufferTable must be a span");
277 }
278
279 template <typename FlatBufferTable>
data()280 const FlatBufferTable* TestTracingRecorder::Span<FlatBufferTable>::data()
281 const {
282 return SpanOrEvent::data<FlatBufferTable>();
283 }
284
285 template <typename FlatBufferTable>
286 std::vector<TestTracingRecorder::Span<FlatBufferTable>>
FindAllSpans()287 TestTracingRecorder::SpanOrEvent::FindAllSpans() {
288 static_assert(TracingTraits<FlatBufferTable>::kIsSpan,
289 "FlatBufferTable must be a span");
290 std::vector<TestTracingRecorder::Span<FlatBufferTable>> result;
291 if (HasType<FlatBufferTable>()) {
292 result.emplace_back(recorder_, record_);
293 }
294 for (auto& c : children_) {
295 auto child_result = c.FindAllSpans<FlatBufferTable>();
296 result.insert(result.end(), child_result.begin(), child_result.end());
297 }
298 return result;
299 }
300
301 template <typename FlatBufferTable>
302 std::vector<TestTracingRecorder::Event<FlatBufferTable>>
FindAllEvents()303 TestTracingRecorder::SpanOrEvent::FindAllEvents() {
304 static_assert(!TracingTraits<FlatBufferTable>::kIsSpan,
305 "FlatBufferTable must be an event not a span");
306 std::vector<TestTracingRecorder::Event<FlatBufferTable>> result;
307 if (HasType<FlatBufferTable>()) {
308 result.emplace_back(recorder_, record_);
309 }
310 for (auto& c : children_) {
311 auto child_result = c.FindAllEvents<FlatBufferTable>();
312 result.insert(result.end(), child_result.begin(), child_result.end());
313 }
314 return result;
315 }
316
317 template <typename FlatBufferTable>
318 TestTracingRecorder::Span<FlatBufferTable>
FindOnlySpan(SourceLocation loc)319 TestTracingRecorder::SpanOrEvent::FindOnlySpan(SourceLocation loc) {
320 auto all_spans = FindAllSpans<FlatBufferTable>();
321 EXPECT_THAT(all_spans, testing::SizeIs(1))
322 << "Expected exactly one span of type "
323 << TracingTraits<FlatBufferTable>().Name() << ". " << std::endl
324 << "Source location: " << std::endl
325 << loc.file_name() << ":" << loc.line();
326 return all_spans[0];
327 }
328
329 template <typename FlatBufferTable>
330 TestTracingRecorder::Event<FlatBufferTable>
FindOnlyEvent(SourceLocation loc)331 TestTracingRecorder::SpanOrEvent::FindOnlyEvent(SourceLocation loc) {
332 auto all_events = FindAllEvents<FlatBufferTable>();
333 EXPECT_THAT(all_events, testing::SizeIs(1))
334 << "Expected exactly one event of type "
335 << TracingTraits<FlatBufferTable>().Name() << ". " << std::endl
336 << "Source location: " << std::endl
337 << loc.file_name() << ":" << loc.line();
338 return all_events[0];
339 }
340
341 template <typename FlatBufferTable>
HasType()342 bool TestTracingRecorder::SpanOrEvent::HasType() const {
343 return *TracingTag::FromFlatbuf(record_->data) ==
344 TracingTraits<FlatBufferTable>::kTag;
345 }
346
347 template <typename FlatBufferTable>
data()348 const FlatBufferTable* TestTracingRecorder::SpanOrEvent::data() const {
349 return flatbuffers::GetRoot<FlatBufferTable>(record_->data.data());
350 }
351
352 using ::testing::Matcher;
353 using ::testing::MatcherInterface;
354 using ::testing::MatchResultListener;
355
PrintTo(const TestTracingRecorder::SpanOrEvent & value,std::ostream * os)356 inline void PrintTo(const TestTracingRecorder::SpanOrEvent& value,
357 std::ostream* os) {
358 *os << value.TextFormat();
359 }
360
361 // This wraps std::tuple with method .get<I>() as a member. This
362 // allows to compose a tuple matcher from multiple testing::Property matchers,
363 // combined with testing::AllOf.
364 template <typename Tuple>
365 class TupleWrapper {
366 public:
TupleWrapper(Tuple const & tuple)367 explicit TupleWrapper(Tuple const& tuple) : tuple_(tuple) {}
368 template <std::size_t I>
get()369 const typename std::tuple_element<I, Tuple>::type& get() const {
370 return std::get<I>(tuple_);
371 }
372
373 private:
374 const Tuple tuple_;
375 };
376
377 // Universal matcher for spans and events, checks for type and content.
378 template <typename FlatBufferTable>
379 class SpanOrEventTypeMatcher
380 : public MatcherInterface<const TestTracingRecorder::SpanOrEvent&> {
381 public:
382 using TupleType = typename TracingTraits<FlatBufferTable>::TupleType;
383 using ContentMatcher = testing::Matcher<TupleWrapper<TupleType>>;
384
SpanOrEventTypeMatcher(absl::string_view kind,ContentMatcher content_matcher)385 explicit SpanOrEventTypeMatcher(absl::string_view kind,
386 ContentMatcher content_matcher)
387 : kind_(kind), content_matcher_(content_matcher) {}
388
MatchAndExplain(const TestTracingRecorder::SpanOrEvent & value,MatchResultListener * listener)389 bool MatchAndExplain(const TestTracingRecorder::SpanOrEvent& value,
390 MatchResultListener* listener) const override {
391 *listener << " { " << value.TextFormat() << " } ";
392 bool result = value.HasType<FlatBufferTable>();
393 if (result) {
394 auto content =
395 TupleWrapper<TupleType>(TracingTraits<FlatBufferTable>::MakeTuple(
396 value.data<FlatBufferTable>()));
397 result = content_matcher_.MatchAndExplain(content, listener);
398 }
399 return result;
400 }
401
DescribeTo(std::ostream * os)402 void DescribeTo(std::ostream* os) const override {
403 *os << "Expecting " << kind_ << " of type "
404 << TracingTraits<FlatBufferTable>().Name() << " with fields ";
405 content_matcher_.DescribeTo(os);
406 }
407
DescribeNegationTo(std::ostream * os)408 void DescribeNegationTo(std::ostream* os) const override {
409 *os << "Expecting NOT " << kind_ << " of type "
410 << TracingTraits<FlatBufferTable>().Name() << " with fields ";
411 content_matcher_.DescribeNegationTo(os);
412 }
413
414 private:
415 std::string kind_;
416 ContentMatcher content_matcher_;
417 };
418
419 template <typename Tuple, typename... M, std::size_t... I>
MatchTupleWrapper(M...m,std::index_sequence<I...>)420 auto MatchTupleWrapper(M... m, std::index_sequence<I...>) {
421 return testing::AllOf(
422 testing::Property(&TupleWrapper<Tuple>::template get<I>, m)...);
423 }
424
425 template <typename Tuple, typename... M>
MatchTupleElements(M...m)426 testing::Matcher<TupleWrapper<Tuple>> MatchTupleElements(M... m) {
427 return testing::MatcherCast<TupleWrapper<Tuple>>(
428 MatchTupleWrapper<Tuple, M...>(m...,
429 std::make_index_sequence<sizeof...(M)>{}));
430 }
431
432 template <typename FlatBufferTable, typename... M>
IsSpan(M...field_matchers)433 Matcher<const TestTracingRecorder::SpanOrEvent&> IsSpan(M... field_matchers) {
434 static_assert(TracingTraits<FlatBufferTable>::kIsSpan,
435 "FlatBufferTable must be a span");
436 if constexpr (sizeof...(M) != 0) {
437 constexpr size_t number_of_fields = std::tuple_size<
438 typename TracingTraits<FlatBufferTable>::TupleType>::value;
439 static_assert(
440 sizeof...(M) == number_of_fields,
441 "Matchers must be provided for every field in FlatBufferTable");
442 return MakeMatcher(new SpanOrEventTypeMatcher<FlatBufferTable>(
443 "span",
444 MatchTupleElements<typename TracingTraits<FlatBufferTable>::TupleType>(
445 field_matchers...)));
446 } else {
447 // When no field matchers provided it should match anything
448 return MakeMatcher(
449 new SpanOrEventTypeMatcher<FlatBufferTable>("span", testing::_));
450 }
451 }
452
453 template <typename FlatBufferTable, typename... M>
IsEvent(M...field_matchers)454 Matcher<const TestTracingRecorder::SpanOrEvent&> IsEvent(M... field_matchers) {
455 static_assert(!TracingTraits<FlatBufferTable>::kIsSpan,
456 "FlatBufferTable must not be a span");
457 if constexpr (sizeof...(M) != 0) {
458 return MakeMatcher(new SpanOrEventTypeMatcher<FlatBufferTable>(
459 "event",
460 MatchTupleElements<typename TracingTraits<FlatBufferTable>::TupleType>(
461 field_matchers...)));
462 } else {
463 // When no field matchers provided it should match anything
464 return MakeMatcher(
465 new SpanOrEventTypeMatcher<FlatBufferTable>("event", testing::_));
466 }
467 }
468
469 } // namespace fcp
470
471 #endif // FCP_TRACING_TEST_TRACING_RECORDER_H_
472