libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
test_utils.hpp
1/*
2 * This file is part of libtcspc
3 * Copyright 2019-2026 Board of Regents of the University of Wisconsin System
4 * SPDX-License-Identifier: MIT
5 */
6
7#pragma once
8
9#include "bucket.hpp"
10#include "common.hpp"
11#include "context.hpp"
12#include "errors.hpp"
13#include "event.hpp"
14#include "introspect.hpp"
15#include "numeric_traits.hpp"
16#include "processor.hpp"
17#include "type_list.hpp"
18#include "variant_event.hpp"
19#include "vector_queue.hpp"
20
21#include <algorithm>
22#include <any>
23#include <array>
24#include <cassert>
25#include <concepts>
26#include <cstdint>
27#include <cstdio>
28#include <initializer_list>
29#include <limits>
30#include <memory>
31#include <ostream>
32#include <span>
33#include <sstream>
34#include <stdexcept>
35#include <string>
36#include <type_traits>
37#include <typeinfo>
38#include <utility>
39#include <variant>
40#include <vector>
41
42namespace tcspc {
43
44namespace internal {
45
46template <typename EventList> class sink_only {
47 public:
48 [[nodiscard]] auto introspect_node() const -> processor_info {
49 return processor_info(this, "sink_only");
50 }
51
52 [[nodiscard]] auto introspect_graph() const -> processor_graph {
53 return processor_graph().push_entry_point(this);
54 }
55
56 template <typename E>
57 requires convertible_to_type_list_member<std::remove_cvref_t<E>,
58 EventList>
59 void handle(E &&event) {
60 [[maybe_unused]] std::remove_reference_t<E> const e =
61 std::forward<E>(event);
62 }
63
64 void flush() {}
65};
66
67} // namespace internal
68
83template <typename... Event> auto sink_only() {
84 return internal::sink_only<type_list<Event...>>();
85}
86
101template <typename EventList> auto sink_only_list() {
102 return internal::sink_only<EventList>();
103}
104
116
118inline auto operator<<(std::ostream &stream, feed_as cat) -> std::ostream & {
119 switch (cat) {
121 return stream << "feed_as::const_lvalue";
122 case feed_as::rvalue:
123 return stream << "feed_as::rvalue";
124 }
125 internal::unreachable();
126}
127
145
147inline auto operator<<(std::ostream &stream, emitted_as cat)
148 -> std::ostream & {
149 switch (cat) {
151 return stream << "emitted_as::any_allowed";
153 return stream << "emitted_as::same_as_fed";
155 return stream << "emitted_as::always_lvalue";
157 return stream << "emitted_as::always_rvalue";
158 }
159 internal::unreachable();
160}
161
162namespace internal {
163
164// Value category observed by capture_output.
165enum class emitted_value_category {
166 const_lvalue,
167 nonconst_lvalue,
168 const_rvalue,
169 nonconst_rvalue,
170};
171
172inline void check_value_category(feed_as feed_cat, emitted_as expected,
173 emitted_value_category actual) {
174 if (actual == emitted_value_category::nonconst_lvalue)
175 throw std::logic_error("non-const lvalue event not allowed");
176
177 if (expected == emitted_as::same_as_fed) {
178 switch (feed_cat) {
180 expected = emitted_as::always_lvalue;
181 break;
182 case feed_as::rvalue:
183 expected = emitted_as::always_rvalue;
184 break;
185 }
186 }
187
188 switch (expected) {
190 break;
192 unreachable();
194 if (actual == emitted_value_category::nonconst_rvalue)
195 throw std::logic_error("expected lvalue event, found rvalue");
196 break;
198 if (actual != emitted_value_category::nonconst_rvalue)
199 throw std::logic_error("expected rvalue event, found lvalue");
200 break;
201 }
202}
203
204inline auto operator<<(std::ostream &stream, emitted_value_category cat)
205 -> std::ostream & {
206 switch (cat) {
207 using e = emitted_value_category;
208 case e::const_lvalue:
209 return stream << "const &";
210 case e::nonconst_lvalue:
211 return stream << "&";
212 case e::const_rvalue:
213 return stream << "const &&";
214 case e::nonconst_rvalue:
215 return stream << "&&";
216 }
217 unreachable();
218}
219
220template <typename EventList>
221using recorded_event =
222 std::pair<emitted_value_category, variant_event<EventList>>;
223
224template <typename EventList>
225auto operator<<(std::ostream &stream, recorded_event<EventList> const &pair)
226 -> std::ostream & {
227 return stream << pair.second << ' ' << pair.first;
228}
229
230// Polymorphic function set for type-erasing capture_output_accessor.
231class capture_output_accessor_impl_base {
232 public:
233 virtual ~capture_output_accessor_impl_base() = default;
234 // Returned std::any contains std::vector<recorded_event<EventList>>.
235 [[nodiscard]] virtual auto peek_events() const -> std::any = 0;
236 virtual void pop_event() = 0;
237 [[nodiscard]] virtual auto is_empty() const -> bool = 0;
238 [[nodiscard]] virtual auto is_flushed() const -> bool = 0;
239 virtual void set_up_to_throw(std::size_t count, bool use_error) = 0;
240 [[nodiscard]] virtual auto events_as_string() const -> std::string = 0;
241};
242
243} // namespace internal
244
254class capture_output_accessor {
255 std::unique_ptr<internal::capture_output_accessor_impl_base> impl;
256
257 template <typename EventList>
258 auto peek_events() const
259 -> std::vector<internal::recorded_event<EventList>> {
260 return std::any_cast<std::vector<internal::recorded_event<EventList>>>(
261 impl->peek_events());
262 }
263
264 public:
266 explicit capture_output_accessor(
267 std::unique_ptr<internal::capture_output_accessor_impl_base>
268 implementation)
269 : impl(std::move(implementation)) {}
270
275 void check_ready_for_input(std::string const &input) const {
276 if (not impl->is_empty()) {
277 throw std::logic_error(
278 "cannot accept input (" + input +
279 "): recorded output events remain unchecked:" +
280 impl->events_as_string());
281 }
282 if (impl->is_flushed()) {
283 throw std::logic_error("cannot accept input (" + input +
284 "): output has been flushed");
285 }
286 }
287
307 template <typename Event, typename EventList>
308 auto pop(feed_as feeder_value_category, emitted_as value_category)
309 -> Event {
311 auto const events = peek_events<EventList>();
312 try {
313 if (events.empty())
314 throw std::logic_error("missing event");
315 check_value_category(feeder_value_category, value_category,
316 events.front().first);
317 auto const *event = std::get_if<Event>(&events.front().second);
318 if (event == nullptr)
319 throw std::logic_error("type mismatch");
320 impl->pop_event();
321 return *event;
322 } catch (std::logic_error const &exc) {
323 std::ostringstream stream;
324 stream << "event pop failed: " << exc.what() << '\n';
325 stream << "expected recorded output event of type "
326 << std::string(typeid(Event).name()) << " ("
327 << feeder_value_category << ", " << value_category
328 << ") but found";
329 if (events.empty()) {
330 stream << " no events";
331 } else {
332 stream << ':';
333 for (auto const &e : events)
334 stream << '\n' << e;
335 }
336 throw std::logic_error(stream.str());
337 }
338 }
339
364 template <typename Event, typename EventList>
365 auto check(feed_as feeder_value_category, emitted_as value_category,
366 Event const &expected_event) -> bool {
368 auto events = peek_events<EventList>();
369 try {
370 if (events.empty())
371 throw std::logic_error("missing event");
372 check_value_category(feeder_value_category, value_category,
373 events.front().first);
374 auto const *event = std::get_if<Event>(&events.front().second);
375 if (event == nullptr)
376 throw std::logic_error("type mismatch");
377 if (*event != expected_event)
378 throw std::logic_error("value mismatch");
379 impl->pop_event();
380 return true;
381 } catch (std::logic_error const &exc) {
382 std::ostringstream stream;
383 stream << "event check failed: " << exc.what() << '\n';
384 stream << "expected recorded output event " << expected_event
385 << " (" << feeder_value_category << ", " << value_category
386 << ") but found";
387 if (events.empty()) {
388 stream << " no events";
389 } else {
390 stream << ':';
391 for (auto const &e : events)
392 stream << '\n' << e;
393 }
394 throw std::logic_error(stream.str());
395 }
396 }
397
409 auto check_not_flushed() -> bool {
410 if (not impl->is_empty()) {
411 throw std::logic_error(
412 "expected no recorded output events but found:" +
413 impl->events_as_string());
414 }
415 if (impl->is_flushed()) {
416 throw std::logic_error(
417 "expected output unflushed but found flushed");
418 }
419 return true;
420 }
421
433 auto check_flushed() -> bool {
434 if (not impl->is_empty()) {
435 throw std::logic_error(
436 "expected no recorded output events but found:" +
437 impl->events_as_string());
438 }
439 if (not impl->is_flushed()) {
440 throw std::logic_error(
441 "expected output flushed but found unflushed");
442 }
443 return true;
444 }
445
452 void throw_error_on_next(std::size_t count = 0) {
453 impl->set_up_to_throw(count, true);
454 }
455
462 void throw_end_processing_on_next(std::size_t count = 0) {
463 impl->set_up_to_throw(count, false);
464 }
465
470 impl->set_up_to_throw(std::numeric_limits<std::size_t>::max(), true);
471 }
472
477 impl->set_up_to_throw(std::numeric_limits<std::size_t>::max(), false);
478 }
479};
480
490template <typename EventList> class capture_output_checker {
492
493 feed_as feeder_valcat;
494
495 public:
500 explicit capture_output_checker(feed_as feeder_value_category,
502 : acc(std::move(accessor)), feeder_valcat(feeder_value_category) {}
503
508 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
509 explicit capture_output_checker(feed_as feeder_value_category,
510 std::shared_ptr<context> context,
511 std::string const &name)
513 feeder_value_category,
514 context->access<capture_output_accessor>(name)) {}
515
522 template <typename Event> auto pop() -> Event {
524 }
525
537 template <typename Event> auto pop(emitted_as value_category) -> Event {
539 return acc.pop<Event, EventList>(feeder_valcat, value_category);
540 }
541
548 template <typename Event> auto check(Event const &expected_event) -> bool {
549 return check(emitted_as::any_allowed, expected_event);
550 }
551
571 template <typename Event>
572 auto check(emitted_as value_category, Event const &expected_event)
573 -> bool {
575 return acc.check<Event, EventList>(feeder_valcat, value_category,
576 expected_event);
577 }
578
590 auto check_not_flushed() -> bool { return acc.check_not_flushed(); }
591
603 auto check_flushed() -> bool { return acc.check_flushed(); }
604
611 void throw_error_on_next(std::size_t count = 0) {
612 acc.throw_error_on_next(count);
613 }
614
621 void throw_end_processing_on_next(std::size_t count = 0) {
622 acc.throw_end_processing_on_next(count);
623 }
624
628 void throw_error_on_flush() { acc.throw_error_on_flush(); }
629
634 acc.throw_end_processing_on_flush();
635 }
636};
637
638namespace internal {
639
640template <typename EventList> class capture_output {
641 static_assert(type_list_like<EventList>);
642 static_assert(
643 is_copy_constructible_list_v<EventList>,
644 "capture_output requires every event in EventList to be "
645 "copy-constructible (recorded events are copied for inspection)");
646 static_assert(is_equality_comparable_list_v<EventList>,
647 "capture_output requires every event in EventList to be "
648 "std::equality_comparable (used by check())");
649 static_assert(
650 is_ostreamable_list_v<EventList>,
651 "capture_output requires every event in EventList to provide "
652 "operator<< to std::ostream (used by events_as_string() and "
653 "Catch2 stringification)");
654
655 vector_queue<recorded_event<EventList>> output;
656 bool flushed = false;
657 std::size_t error_in = std::numeric_limits<std::size_t>::max();
658 std::size_t end_in = std::numeric_limits<std::size_t>::max();
659 bool error_on_flush = false;
660 bool end_on_flush = false;
661
662 access_tracker<capture_output_accessor> trk;
663
664 struct accessor_impl : capture_output_accessor_impl_base {
665 capture_output *self;
666
667 explicit accessor_impl(capture_output *self) : self(self) {}
668
669 [[nodiscard]] auto peek_events() const -> std::any final {
670 return self->peek();
671 }
672 void pop_event() final { self->output.pop(); }
673 [[nodiscard]] auto is_empty() const -> bool final {
674 return self->output.empty();
675 }
676 [[nodiscard]] auto is_flushed() const -> bool final {
677 return self->flushed;
678 }
679 void set_up_to_throw(std::size_t count, bool use_error) final {
680 self->set_up_to_throw(count, use_error);
681 }
682 [[nodiscard]] auto events_as_string() const -> std::string final {
683 return self->events_as_string();
684 }
685 };
686
687 auto peek() const {
688 std::vector<recorded_event<EventList>> ret;
689 ret.reserve(output.size());
690 // Cannot use std::copy because vector_queue lacks iterators.
691 output.for_each([&ret](auto const &pair) { ret.push_back(pair); });
692 return ret;
693 }
694
695 [[nodiscard]] auto events_as_string() const -> std::string {
696 std::ostringstream stream;
697 output.for_each([&](auto const &event) { stream << '\n' << event; });
698 return stream.str();
699 }
700
701 void set_up_to_throw(std::size_t count, bool use_error) {
702 if (count == std::numeric_limits<std::size_t>::max()) {
703 if (use_error)
704 error_on_flush = true;
705 else
706 end_on_flush = true;
707 } else {
708 if (use_error)
709 error_in = count;
710 else
711 end_in = count;
712 }
713 }
714
715 public:
716 explicit capture_output(access_tracker<capture_output_accessor> &&tracker)
717 : trk(std::move(tracker)) {
718 trk.register_accessor_factory([](auto &tracker) {
719 auto *self =
720 LIBTCSPC_OBJECT_FROM_TRACKER(capture_output, trk, tracker);
721 return capture_output_accessor(
722 std::make_unique<accessor_impl>(self));
723 });
724 }
725
726 [[nodiscard]] auto introspect_node() const -> processor_info {
727 return processor_info(this, "capture_output");
728 }
729
730 [[nodiscard]] auto introspect_graph() const -> processor_graph {
731 return processor_graph().push_entry_point(this);
732 }
733
734 template <typename Event>
735 requires type_list_member<std::remove_cvref_t<Event>, EventList>
736 void handle(Event &&event) {
737 static constexpr auto valcat = [] {
738 if constexpr (std::is_lvalue_reference_v<Event>) {
739 if constexpr (std::is_const_v<std::remove_reference_t<Event>>)
740 return emitted_value_category::const_lvalue;
741 else
742 return emitted_value_category::nonconst_lvalue;
743 } else {
744 if constexpr (std::is_const_v<Event>)
745 return emitted_value_category::const_rvalue;
746 else
747 return emitted_value_category::nonconst_rvalue;
748 }
749 }();
750 assert(not flushed);
751 if (error_in == 0)
752 throw test_error("test error upon event");
753 output.push(std::pair{valcat, std::forward<Event>(event)});
754 if (end_in == 0)
755 throw end_of_processing("test end-of-stream upon event");
756 --error_in;
757 --end_in;
758 }
759
760 void flush() {
761 assert(not flushed);
762 if (error_on_flush) {
763 throw test_error("test error upon flush");
764 }
765 flushed = true;
766 if (end_on_flush)
767 throw end_of_processing("test end-of-stream upon flush");
768 }
769};
770
771// Specialization for empty event list.
772template <> class capture_output<type_list<>> {
773 bool flushed = false;
774 bool error_on_flush = false;
775 bool end_on_flush = false;
776 access_tracker<capture_output_accessor> trk;
777
778 struct accessor_impl : capture_output_accessor_impl_base {
779 capture_output *self;
780
781 explicit accessor_impl(capture_output *self) : self(self) {}
782
783 [[noreturn]] static void not_allowed() {
784 throw std::logic_error(
785 "operation not allowed on capture_output with empty event list");
786 }
787
788 [[nodiscard]] auto peek_events() const -> std::any final {
789 not_allowed();
790 }
791 void pop_event() final { not_allowed(); }
792 [[nodiscard]] auto is_empty() const -> bool final { return true; }
793 [[nodiscard]] auto is_flushed() const -> bool final {
794 return self->flushed;
795 }
796 void set_up_to_throw(std::size_t /* count */,
797 bool /* use_error */) final {
798 not_allowed();
799 }
800 [[nodiscard]] auto events_as_string() const -> std::string final {
801 not_allowed();
802 }
803 };
804
805 void set_up_to_throw(std::size_t count, bool use_error) {
806 if (count == std::numeric_limits<std::size_t>::max()) {
807 if (use_error)
808 error_on_flush = true;
809 else
810 end_on_flush = true;
811 }
812 }
813
814 public:
815 explicit capture_output(access_tracker<capture_output_accessor> &&tracker)
816 : trk(std::move(tracker)) {
817 trk.register_accessor_factory([](auto &tracker) {
818 auto *self =
819 LIBTCSPC_OBJECT_FROM_TRACKER(capture_output, trk, tracker);
820 return capture_output_accessor(
821 std::make_unique<accessor_impl>(self));
822 });
823 }
824
825 [[nodiscard]] auto introspect_node() const -> processor_info {
826 return processor_info(this, "capture_output");
827 }
828
829 [[nodiscard]] auto introspect_graph() const -> processor_graph {
830 return processor_graph().push_entry_point(this);
831 }
832
833 void flush() {
834 assert(not flushed);
835 if (error_on_flush) {
836 throw test_error("test error upon flush");
837 }
838 flushed = true;
839 if (end_on_flush)
840 throw end_of_processing("test end-of-stream upon flush");
841 }
842};
843
844template <typename Downstream>
845 requires processor<Downstream>
846class feed_input {
847 std::vector<std::pair<std::shared_ptr<context>, std::string>>
848 outputs_to_check; // (context, name)
849 feed_as refmode = feed_as::const_lvalue;
850 Downstream downstream;
851
852 void check_outputs_ready(std::string const &input) {
853 if (outputs_to_check.empty())
854 throw std::logic_error(
855 "feed_input has no registered capture_output to check");
856 for (auto &[context, name] : outputs_to_check)
857 context->template access<capture_output_accessor>(name)
858 .check_ready_for_input(input);
859 }
860
861 public:
862 explicit feed_input(Downstream downstream)
863 : downstream(std::move(downstream)) {}
864
865 explicit feed_input(feed_as mode, Downstream downstream)
866 : refmode(mode), downstream(std::move(downstream)) {}
867
868 [[nodiscard]] auto introspect_node() const -> processor_info {
869 return processor_info(this, "feed_input");
870 }
871
872 [[nodiscard]] auto introspect_graph() const -> processor_graph {
873 return downstream.introspect_graph().push_entry_point(this);
874 }
875
876 void require_output_checked(std::shared_ptr<context> context,
877 std::string name) {
878 context->access<capture_output_accessor>(name); // Fail early.
879 outputs_to_check.emplace_back(std::move(context), std::move(name));
880 }
881
882 template <typename Event>
883 requires handler_for<Downstream, std::remove_cvref_t<Event>>
884 void handle(Event &&event) {
885 check_outputs_ready("event of type " +
886 std::string(typeid(event).name()));
887
888 if (refmode == feed_as::const_lvalue) {
889 downstream.handle(static_cast<Event const &>(event));
890 } else if constexpr (std::is_lvalue_reference_v<Event>) {
891 std::remove_cvref_t<Event> copy(event);
892 downstream.handle(std::move(copy));
893 } else {
894 downstream.handle(std::forward<Event>(event));
895 }
896 }
897
898 void flush() {
899 check_outputs_ready("flush");
900 downstream.flush();
901 }
902};
903
904} // namespace internal
905
933template <typename EventList>
935 return internal::capture_output<EventList>(std::move(tracker));
936}
937
972template <typename Downstream>
973auto feed_input(feed_as value_category, Downstream downstream) {
974 return internal::feed_input<Downstream>(value_category,
975 std::move(downstream));
976}
977
985template <int N> struct empty_test_event {
987 friend auto operator==(empty_test_event<N> const &lhs,
988 empty_test_event<N> const &rhs) noexcept
989 -> bool = default;
990
992 friend auto operator<<(std::ostream &strm,
993 empty_test_event<N> const & /* e */)
994 -> std::ostream & {
995 return strm << "empty_test_event<" << N << ">";
996 }
997};
998
1008template <int N, typename NumericTraits = default_numeric_traits>
1011 NumericTraits::abstime_type abstime;
1012
1014 friend auto operator==(time_tagged_test_event const &lhs,
1015 time_tagged_test_event const &rhs) noexcept
1016 -> bool = default;
1017
1019 friend auto operator<<(std::ostream &strm, time_tagged_test_event const &e)
1020 -> std::ostream & {
1021 return strm << "time_tagged_test_event<" << N << ">{" << e.abstime
1022 << "}";
1023 }
1024};
1025
1034template <typename T>
1035auto test_bucket(std::initializer_list<T> il) -> bucket<T> {
1036 using U = std::remove_cv_t<T>;
1037 struct test_storage {
1038 std::vector<U> v;
1039 };
1040 auto storage = test_storage{std::vector<U>(il.begin(), il.end())};
1041 return bucket<T>(std::span(storage.v), std::move(storage));
1042}
1043
1051template <typename T> auto test_bucket(std::span<T> s) -> bucket<T> {
1052 using U = std::remove_cv_t<T>;
1053 struct test_storage {
1054 std::vector<U> v;
1055 };
1056 auto storage = test_storage{std::vector<U>(s.begin(), s.end())};
1057 return bucket<T>(std::span(storage.v), std::move(storage));
1058}
1059
1072template <typename T> class test_bucket_source : public bucket_source<T> {
1073 std::shared_ptr<bucket_source<T>> src;
1074 T value;
1075 std::size_t count = 0;
1076
1077 explicit test_bucket_source(
1078 std::shared_ptr<bucket_source<T>> backing_source, T fill_value)
1079 : src(std::move(backing_source)), value(std::move(fill_value)) {}
1080
1081 public:
1083 static auto create(std::shared_ptr<bucket_source<T>> backing_source,
1084 T fill_value)
1085 -> std::shared_ptr<test_bucket_source<T>> {
1086 return std::shared_ptr<test_bucket_source<T>>(new test_bucket_source(
1087 std::move(backing_source), std::move(fill_value)));
1088 }
1089
1091 auto bucket_of_size(std::size_t size) -> bucket<T> override {
1092 auto b = src->bucket_of_size(size);
1093 std::fill(b.begin(), b.end(), value);
1094 ++count;
1095 return b;
1096 }
1097
1099 [[nodiscard]] auto bucket_count() const noexcept -> std::size_t {
1100 return count;
1101 }
1102
1104 [[nodiscard]] auto supports_shared_views() const noexcept
1105 -> bool override {
1106 return src->supports_shared_views();
1107 }
1108
1110 [[nodiscard]] auto shared_view_of(bucket<T> const &bkt)
1111 -> bucket<T const> override {
1112 return src->shared_view_of(bkt);
1113 }
1114};
1115
1137template <typename Event>
1138inline auto
1139from_reversed_bytes(std::array<std::uint8_t, sizeof(Event)> bytes) noexcept {
1140 static_assert(std::is_trivial_v<Event>);
1141 auto const srcspan = std::as_bytes(std::span(&bytes, 1));
1142 Event ret{};
1143 auto const retspan = std::as_writable_bytes(std::span(&ret, 1));
1144 std::reverse_copy(srcspan.begin(), srcspan.end(), retspan.begin());
1145 return ret;
1146}
1147
1148} // namespace tcspc
Tracker that mediates access to objects via a tcspc::context.
Definition context.hpp:39
Value-semantic container for array data allowing use of custom storage.
Definition bucket.hpp:110
Accessor for tcspc::capture_output() processors.
Definition test_utils.hpp:254
auto check_not_flushed() -> bool
Check that no recorded output events remain but the output has not been flushed.
Definition test_utils.hpp:409
void throw_error_on_flush()
Arrange to throw tcspc::test_error on receiving a flush.
Definition test_utils.hpp:469
auto check_flushed() -> bool
Check that no recorded output events remain and the output has been flushed.
Definition test_utils.hpp:433
void throw_end_processing_on_flush()
Arrange to throw tcspc::end_of_processing on receiving a flush.
Definition test_utils.hpp:476
void check_ready_for_input(std::string const &input) const
Check if ready for input; normally used internally by tcspc::feed_input().
Definition test_utils.hpp:275
void throw_end_processing_on_next(std::size_t count=0)
Arrange to throw tcspc::end_of_processing on receiving the given number of events.
Definition test_utils.hpp:462
auto check(feed_as feeder_value_category, emitted_as value_category, Event const &expected_event) -> bool
Check that the next recorded output event matches with the given one.
Definition test_utils.hpp:365
void throw_error_on_next(std::size_t count=0)
Arrange to throw tcspc::test_error on receiving the given number of events.
Definition test_utils.hpp:452
auto pop(feed_as feeder_value_category, emitted_as value_category) -> Event
Retrieve the next recorded output event.
Definition test_utils.hpp:308
auto check(Event const &expected_event) -> bool
Check that the next recorded output event matches with the given event, disregarding value category.
Definition test_utils.hpp:548
capture_output_checker(feed_as feeder_value_category, capture_output_accessor accessor)
Construct from a tcspc::capture_output_accessor, with the feeder's value category.
Definition test_utils.hpp:500
void throw_end_processing_on_next(std::size_t count=0)
Arrange to throw tcspc::end_of_processing on receiving the given number of events.
Definition test_utils.hpp:621
auto check_flushed() -> bool
Check that no recorded output events remain and the output has been flushed.
Definition test_utils.hpp:603
auto check(emitted_as value_category, Event const &expected_event) -> bool
Check that the next recorded output event matches with the given event and value category.
Definition test_utils.hpp:572
capture_output_checker(feed_as feeder_value_category, std::shared_ptr< context > context, std::string const &name)
Construct from a context, tracker name of tcspc::capture_output processor, and feeder's value categor...
Definition test_utils.hpp:509
auto pop(emitted_as value_category) -> Event
Retrieve the next recorded output event, checking its value category.
Definition test_utils.hpp:537
void throw_end_processing_on_flush()
Arrange to throw tcspc::end_of_processing on receiving a flush.
Definition test_utils.hpp:633
void throw_error_on_next(std::size_t count=0)
Arrange to throw tcspc::test_error on receiving the given number of events.
Definition test_utils.hpp:611
auto pop() -> Event
Retrieve the next recorded output event, disregarding value category.
Definition test_utils.hpp:522
void throw_error_on_flush()
Arrange to throw tcspc::test_error on receiving a flush.
Definition test_utils.hpp:628
auto check_not_flushed() -> bool
Check that no recorded output events remain but the output has not been flushed.
Definition test_utils.hpp:590
Context for enabling access to objects after they have been incorporated into a processing graph.
Definition context.hpp:113
auto supports_shared_views() const noexcept -> bool override
Implements sharable bucket source requirement.
Definition test_utils.hpp:1104
auto bucket_of_size(std::size_t size) -> bucket< T > override
Implements bucket source requirement.
Definition test_utils.hpp:1091
static auto create(std::shared_ptr< bucket_source< T > > backing_source, T fill_value) -> std::shared_ptr< test_bucket_source< T > >
Create an instance.
Definition test_utils.hpp:1083
auto bucket_count() const noexcept -> std::size_t
Return the number of buckets created so far.
Definition test_utils.hpp:1099
auto shared_view_of(bucket< T > const &bkt) -> bucket< T const > override
Implements sharable bucket source requirement.
Definition test_utils.hpp:1110
Concept that is satisfied when a type is contained in a type list.
Definition type_list.hpp:194
#define LIBTCSPC_OBJECT_FROM_TRACKER(obj_type, tracker_field_name, tracker)
Recover the object address from a tcspc::access_tracker embedded in the object.
Definition context.hpp:255
auto test_bucket(std::initializer_list< T > il) -> bucket< T >
Create an ad-hoc tcspc::bucket<T> for testing, from a list of values.
Definition test_utils.hpp:1035
emitted_as
Value category to check emitted events against.
Definition test_utils.hpp:135
feed_as
Value category used to feed an event via tcspc::feed_input.
Definition test_utils.hpp:110
auto from_reversed_bytes(std::array< std::uint8_t, sizeof(Event)> bytes) noexcept
Bit-cast an array of bytes to an event after reversing the order.
Definition test_utils.hpp:1139
@ always_rvalue
Require non-const rvalue.
Definition test_utils.hpp:143
@ any_allowed
Require const lvalue or rvalue, or non-const rvalue.
Definition test_utils.hpp:137
@ same_as_fed
Require the same category as the events being fed.
Definition test_utils.hpp:139
@ always_lvalue
Require const lvalue.
Definition test_utils.hpp:141
@ rvalue
Feed as non-const rvalue.
Definition test_utils.hpp:114
@ const_lvalue
Feed as const lvalue.
Definition test_utils.hpp:112
auto count(access_tracker< count_accessor > &&tracker, Downstream downstream)
Create a processor that counts events of a given type.
Definition count.hpp:312
auto capture_output(access_tracker< capture_output_accessor > &&tracker)
Create a sink that records the output of a processor under test.
Definition test_utils.hpp:934
auto sink_only()
Create a processor that ignores only specific event types.
Definition test_utils.hpp:83
auto feed_input(feed_as value_category, Downstream downstream)
Create a source for feeding test input to a processor under test.
Definition test_utils.hpp:973
auto sink_only_list()
Create a processor that ignores only specific event types.
Definition test_utils.hpp:101
libtcspc namespace.
Definition acquire.hpp:30
Abstract base class for polymorphic bucket sources.
Definition bucket.hpp:504
Empty event for testing.
Definition test_utils.hpp:985
friend auto operator<<(std::ostream &strm, empty_test_event< N > const &) -> std::ostream &
Stream insertion operator.
Definition test_utils.hpp:992
friend auto operator==(empty_test_event< N > const &lhs, empty_test_event< N > const &rhs) noexcept -> bool=default
Equality comparison operator.
Timestamped event for testing.
Definition test_utils.hpp:1009
friend auto operator<<(std::ostream &strm, time_tagged_test_event const &e) -> std::ostream &
Stream insertion operator.
Definition test_utils.hpp:1019
friend auto operator==(time_tagged_test_event const &lhs, time_tagged_test_event const &rhs) noexcept -> bool=default
Equality comparison operator.
NumericTraits::abstime_type abstime
Timestamp.
Definition test_utils.hpp:1011
Compile-time representation of a list of types.
Definition type_list.hpp:38