xref: /aosp_15_r20/external/federated-compute/fcp/tracing/test_tracing_recorder.h (revision 14675a029014e728ec732f129a32e299b2da0601)
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