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"
28#include <initializer_list>
46template <
typename EventList>
class sink_only {
48 [[nodiscard]]
auto introspect_node() const -> processor_info {
49 return processor_info(
this,
"sink_only");
52 [[nodiscard]]
auto introspect_graph() const -> processor_graph {
53 return processor_graph().push_entry_point(
this);
57 requires convertible_to_type_list_member<std::remove_cvref_t<E>,
59 void handle(E &&event) {
60 [[maybe_unused]] std::remove_reference_t<E>
const e =
61 std::forward<E>(event);
84 return internal::sink_only<
type_list<Event...>>();
102 return internal::sink_only<EventList>();
118inline auto operator<<(std::ostream &stream,
feed_as cat) -> std::ostream & {
121 return stream <<
"feed_as::const_lvalue";
123 return stream <<
"feed_as::rvalue";
125 internal::unreachable();
147inline auto operator<<(std::ostream &stream,
emitted_as 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";
159 internal::unreachable();
165enum class emitted_value_category {
173 emitted_value_category actual) {
174 if (actual == emitted_value_category::nonconst_lvalue)
175 throw std::logic_error(
"non-const lvalue event not allowed");
194 if (actual == emitted_value_category::nonconst_rvalue)
195 throw std::logic_error(
"expected lvalue event, found rvalue");
198 if (actual != emitted_value_category::nonconst_rvalue)
199 throw std::logic_error(
"expected rvalue event, found lvalue");
204inline auto operator<<(std::ostream &stream, emitted_value_category 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 <<
"&&";
220template <
typename EventList>
221using recorded_event =
222 std::pair<emitted_value_category, variant_event<EventList>>;
224template <
typename EventList>
225auto operator<<(std::ostream &stream, recorded_event<EventList>
const &pair)
227 return stream << pair.second <<
' ' << pair.first;
231class capture_output_accessor_impl_base {
233 virtual ~capture_output_accessor_impl_base() =
default;
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;
254class capture_output_accessor {
255 std::unique_ptr<internal::capture_output_accessor_impl_base> impl;
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());
266 explicit capture_output_accessor(
267 std::unique_ptr<internal::capture_output_accessor_impl_base>
269 : impl(std::move(implementation)) {}
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());
282 if (impl->is_flushed()) {
283 throw std::logic_error(
"cannot accept input (" + input +
284 "): output has been flushed");
307 template <
typename Event,
typename EventList>
311 auto const events = peek_events<EventList>();
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");
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
329 if (events.empty()) {
330 stream <<
" no events";
333 for (
auto const &e : events)
336 throw std::logic_error(stream.str());
364 template <
typename Event,
typename EventList>
366 Event
const &expected_event) ->
bool {
368 auto events = peek_events<EventList>();
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");
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
387 if (events.empty()) {
388 stream <<
" no events";
391 for (
auto const &e : events)
394 throw std::logic_error(stream.str());
410 if (not impl->is_empty()) {
411 throw std::logic_error(
412 "expected no recorded output events but found:" +
413 impl->events_as_string());
415 if (impl->is_flushed()) {
416 throw std::logic_error(
417 "expected output unflushed but found flushed");
434 if (not impl->is_empty()) {
435 throw std::logic_error(
436 "expected no recorded output events but found:" +
437 impl->events_as_string());
439 if (not impl->is_flushed()) {
440 throw std::logic_error(
441 "expected output flushed but found unflushed");
453 impl->set_up_to_throw(
count,
true);
463 impl->set_up_to_throw(
count,
false);
470 impl->set_up_to_throw(std::numeric_limits<std::size_t>::max(),
true);
477 impl->set_up_to_throw(std::numeric_limits<std::size_t>::max(),
false);
502 : acc(std::move(accessor)), feeder_valcat(feeder_value_category) {}
510 std::shared_ptr<context>
context,
511 std::string
const &name)
513 feeder_value_category,
522 template <
typename Event>
auto pop() -> Event {
539 return acc.pop<Event, EventList>(feeder_valcat, value_category);
548 template <
typename Event>
auto check(Event
const &expected_event) ->
bool {
571 template <
typename Event>
575 return acc.check<Event, EventList>(feeder_valcat, value_category,
612 acc.throw_error_on_next(
count);
622 acc.throw_end_processing_on_next(
count);
634 acc.throw_end_processing_on_flush();
640template <
typename EventList>
class capture_output {
641 static_assert(type_list_like<EventList>);
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())");
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)");
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;
662 access_tracker<capture_output_accessor> trk;
664 struct accessor_impl : capture_output_accessor_impl_base {
665 capture_output *self;
667 explicit accessor_impl(capture_output *self) : self(self) {}
669 [[nodiscard]]
auto peek_events() const -> std::any final {
672 void pop_event() final { self->output.pop(); }
673 [[nodiscard]]
auto is_empty() const ->
bool final {
674 return self->output.empty();
676 [[nodiscard]]
auto is_flushed() const ->
bool final {
677 return self->flushed;
679 void set_up_to_throw(std::size_t count,
bool use_error)
final {
680 self->set_up_to_throw(count, use_error);
682 [[nodiscard]]
auto events_as_string() const -> std::
string final {
683 return self->events_as_string();
688 std::vector<recorded_event<EventList>> ret;
689 ret.reserve(output.size());
691 output.for_each([&ret](
auto const &pair) { ret.push_back(pair); });
695 [[nodiscard]]
auto events_as_string() const -> std::
string {
696 std::ostringstream stream;
697 output.for_each([&](
auto const &event) { stream <<
'\n' << event; });
701 void set_up_to_throw(std::size_t count,
bool use_error) {
702 if (count == std::numeric_limits<std::size_t>::max()) {
704 error_on_flush =
true;
716 explicit capture_output(access_tracker<capture_output_accessor> &&tracker)
717 : trk(std::move(tracker)) {
718 trk.register_accessor_factory([](
auto &tracker) {
721 return capture_output_accessor(
722 std::make_unique<accessor_impl>(self));
726 [[nodiscard]]
auto introspect_node() const -> processor_info {
727 return processor_info(
this,
"capture_output");
730 [[nodiscard]]
auto introspect_graph() const -> processor_graph {
731 return processor_graph().push_entry_point(
this);
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;
742 return emitted_value_category::nonconst_lvalue;
744 if constexpr (std::is_const_v<Event>)
745 return emitted_value_category::const_rvalue;
747 return emitted_value_category::nonconst_rvalue;
752 throw test_error(
"test error upon event");
753 output.push(std::pair{valcat, std::forward<Event>(event)});
755 throw end_of_processing(
"test end-of-stream upon event");
762 if (error_on_flush) {
763 throw test_error(
"test error upon flush");
767 throw end_of_processing(
"test end-of-stream upon flush");
773 bool flushed =
false;
774 bool error_on_flush =
false;
775 bool end_on_flush =
false;
776 access_tracker<capture_output_accessor> trk;
778 struct accessor_impl : capture_output_accessor_impl_base {
781 explicit accessor_impl(capture_output *self) : self(self) {}
783 [[noreturn]]
static void not_allowed() {
784 throw std::logic_error(
785 "operation not allowed on capture_output with empty event list");
788 [[nodiscard]]
auto peek_events() const -> std::any final {
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;
796 void set_up_to_throw(std::size_t ,
800 [[nodiscard]]
auto events_as_string() const -> std::
string final {
805 void set_up_to_throw(std::size_t count,
bool use_error) {
806 if (count == std::numeric_limits<std::size_t>::max()) {
808 error_on_flush =
true;
815 explicit capture_output(access_tracker<capture_output_accessor> &&tracker)
816 : trk(std::move(tracker)) {
817 trk.register_accessor_factory([](
auto &tracker) {
820 return capture_output_accessor(
821 std::make_unique<accessor_impl>(self));
825 [[nodiscard]]
auto introspect_node() const -> processor_info {
826 return processor_info(
this,
"capture_output");
829 [[nodiscard]]
auto introspect_graph() const -> processor_graph {
830 return processor_graph().push_entry_point(
this);
835 if (error_on_flush) {
836 throw test_error(
"test error upon flush");
840 throw end_of_processing(
"test end-of-stream upon flush");
844template <
typename Downstream>
845 requires processor<Downstream>
847 std::vector<std::pair<std::shared_ptr<context>, std::string>>
849 feed_as refmode = feed_as::const_lvalue;
850 Downstream downstream;
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);
863 : downstream(std::move(downstream)) {}
865 explicit feed_input(feed_as mode, Downstream downstream)
866 : refmode(mode), downstream(std::move(downstream)) {}
868 [[nodiscard]]
auto introspect_node() const -> processor_info {
869 return processor_info(
this,
"feed_input");
872 [[nodiscard]]
auto introspect_graph() const -> processor_graph {
873 return downstream.introspect_graph().push_entry_point(
this);
876 void require_output_checked(std::shared_ptr<context> context,
878 context->access<capture_output_accessor>(name);
879 outputs_to_check.emplace_back(std::move(context), std::move(name));
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()));
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));
894 downstream.handle(std::forward<Event>(event));
899 check_outputs_ready(
"flush");
933template <
typename EventList>
935 return internal::capture_output<EventList>(std::move(tracker));
972template <
typename Downstream>
974 return internal::feed_input<Downstream>(value_category,
975 std::move(downstream));
995 return strm <<
"empty_test_event<" << N <<
">";
1008template <
int N,
typename NumericTraits = default_numeric_traits>
1021 return strm <<
"time_tagged_test_event<" << N <<
">{" << e.abstime
1034template <
typename T>
1036 using U = std::remove_cv_t<T>;
1037 struct test_storage {
1040 auto storage = test_storage{std::vector<U>(il.begin(), il.end())};
1041 return bucket<T>(std::span(storage.v), std::move(storage));
1052 using U = std::remove_cv_t<T>;
1053 struct test_storage {
1056 auto storage = test_storage{std::vector<U>(s.begin(), s.end())};
1057 return bucket<T>(std::span(storage.v), std::move(storage));
1073 std::shared_ptr<bucket_source<T>> src;
1075 std::size_t count = 0;
1077 explicit test_bucket_source(
1079 : src(std::move(backing_source)), value(std::move(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)));
1092 auto b = src->bucket_of_size(size);
1093 std::fill(b.begin(), b.end(), value);
1106 return src->supports_shared_views();
1112 return src->shared_view_of(bkt);
1137template <
typename Event>
1140 static_assert(std::is_trivial_v<Event>);
1141 auto const srcspan = std::as_bytes(std::span(&bytes, 1));
1143 auto const retspan = std::as_writable_bytes(std::span(&ret, 1));
1144 std::reverse_copy(srcspan.begin(), srcspan.end(), retspan.begin());
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