12#include "data_types.hpp"
14#include "introspect.hpp"
15#include "processor.hpp"
16#include "type_list.hpp"
17#include "variant_event.hpp"
18#include "vector_queue.hpp"
26#include <initializer_list>
44template <
typename EventList>
class sink_events {
46 [[nodiscard]]
auto introspect_node() const -> processor_info {
47 return processor_info(
this,
"sink_events");
50 [[nodiscard]]
auto introspect_graph() const -> processor_graph {
51 return processor_graph().push_entry_point(
this);
55 requires convertible_to_type_list_member<std::remove_cvref_t<E>,
57 void handle(E &&event) {
58 [[maybe_unused]] std::remove_reference_t<E>
const e =
59 std::forward<E>(event);
82 return internal::sink_events<
type_list<Event...>>();
100 return internal::sink_events<EventList>();
116inline auto operator<<(std::ostream &stream,
feed_as cat) -> std::ostream & {
119 return stream <<
"feed_as::const_lvalue";
121 return stream <<
"feed_as::rvalue";
123 internal::unreachable();
145inline auto operator<<(std::ostream &stream,
emitted_as cat)
149 return stream <<
"emitted_as::any_allowed";
151 return stream <<
"emitted_as::same_as_fed";
153 return stream <<
"emitted_as::always_lvalue";
155 return stream <<
"emitted_as::always_rvalue";
157 internal::unreachable();
163enum class emitted_value_category {
171 emitted_value_category actual) {
172 if (actual == emitted_value_category::nonconst_lvalue)
173 throw std::logic_error(
"non-const lvalue event not allowed");
192 if (actual == emitted_value_category::nonconst_rvalue)
193 throw std::logic_error(
"expected lvalue event, found rvalue");
196 if (actual != emitted_value_category::nonconst_rvalue)
197 throw std::logic_error(
"expected rvalue event, found lvalue");
202inline auto operator<<(std::ostream &stream, emitted_value_category cat)
205 using e = emitted_value_category;
206 case e::const_lvalue:
207 return stream <<
"const &";
208 case e::nonconst_lvalue:
209 return stream <<
"&";
210 case e::const_rvalue:
211 return stream <<
"const &&";
212 case e::nonconst_rvalue:
213 return stream <<
"&&";
218template <
typename EventList>
219using recorded_event =
220 std::pair<emitted_value_category, variant_event<EventList>>;
222template <
typename EventList>
223auto operator<<(std::ostream &stream, recorded_event<EventList>
const &pair)
225 return stream << pair.second <<
' ' << pair.first;
229class capture_output_access_impl_base {
231 virtual ~capture_output_access_impl_base() =
default;
233 [[nodiscard]]
virtual auto peek_events() const -> std::any = 0;
234 virtual
void pop_event() = 0;
235 [[nodiscard]] virtual auto is_empty() const ->
bool = 0;
236 [[nodiscard]] virtual auto is_flushed() const ->
bool = 0;
237 virtual
void set_up_to_throw(std::
size_t count,
bool use_error) = 0;
238 [[nodiscard]] virtual auto events_as_string() const -> std::
string = 0;
252class capture_output_access {
253 std::unique_ptr<internal::capture_output_access_impl_base> impl;
255 template <
typename EventList>
256 auto peek_events()
const
257 -> std::vector<internal::recorded_event<EventList>> {
258 return std::any_cast<std::vector<internal::recorded_event<EventList>>>(
259 impl->peek_events());
264 explicit capture_output_access(
265 std::unique_ptr<internal::capture_output_access_impl_base>
267 : impl(std::move(implementation)) {}
274 if (not impl->is_empty()) {
275 throw std::logic_error(
276 "cannot accept input (" + input +
277 "): recorded output events remain unchecked:" +
278 impl->events_as_string());
280 if (impl->is_flushed()) {
281 throw std::logic_error(
"cannot accept input (" + input +
282 "): output has been flushed");
305 template <
typename Event,
typename EventList>
309 auto const events = peek_events<EventList>();
312 throw std::logic_error(
"missing event");
313 check_value_category(feeder_value_category, value_category,
314 events.front().first);
315 auto const *
event = std::get_if<Event>(&events.front().second);
316 if (event ==
nullptr)
317 throw std::logic_error(
"type mismatch");
320 }
catch (std::logic_error
const &exc) {
321 std::ostringstream stream;
322 stream <<
"event pop failed: " << exc.what() <<
'\n';
323 stream <<
"expected recorded output event of type "
324 << std::string(
typeid(Event).name()) <<
" ("
325 << feeder_value_category <<
", " << value_category
327 if (events.empty()) {
328 stream <<
" no events";
331 for (
auto const &e : events)
334 throw std::logic_error(stream.str());
362 template <
typename Event,
typename EventList>
364 Event
const &expected_event) ->
bool {
366 auto events = peek_events<EventList>();
369 throw std::logic_error(
"missing event");
370 check_value_category(feeder_value_category, value_category,
371 events.front().first);
372 auto const *
event = std::get_if<Event>(&events.front().second);
373 if (event ==
nullptr)
374 throw std::logic_error(
"type mismatch");
375 if (*event != expected_event)
376 throw std::logic_error(
"value mismatch");
379 }
catch (std::logic_error
const &exc) {
380 std::ostringstream stream;
381 stream <<
"event check failed: " << exc.what() <<
'\n';
382 stream <<
"expected recorded output event " << expected_event
383 <<
" (" << feeder_value_category <<
", " << value_category
385 if (events.empty()) {
386 stream <<
" no events";
389 for (
auto const &e : events)
392 throw std::logic_error(stream.str());
408 if (not impl->is_empty()) {
409 throw std::logic_error(
410 "expected no recorded output events but found:" +
411 impl->events_as_string());
413 if (impl->is_flushed()) {
414 throw std::logic_error(
415 "expected output unflushed but found flushed");
432 if (not impl->is_empty()) {
433 throw std::logic_error(
434 "expected no recorded output events but found:" +
435 impl->events_as_string());
437 if (not impl->is_flushed()) {
438 throw std::logic_error(
439 "expected output flushed but found unflushed");
451 impl->set_up_to_throw(
count,
true);
461 impl->set_up_to_throw(
count,
false);
468 impl->set_up_to_throw(std::numeric_limits<std::size_t>::max(),
true);
475 impl->set_up_to_throw(std::numeric_limits<std::size_t>::max(),
false);
500 : acc(std::move(access)), feeder_valcat(feeder_value_category) {}
508 std::shared_ptr<context>
context,
509 std::string
const &name)
511 feeder_value_category,
520 template <
typename Event>
auto pop() -> Event {
537 return acc.pop<Event, EventList>(feeder_valcat, value_category);
546 template <
typename Event>
auto check(Event
const &expected_event) ->
bool {
569 template <
typename Event>
573 return acc.check<Event, EventList>(feeder_valcat, value_category,
610 acc.throw_error_on_next(
count);
620 acc.throw_end_processing_on_next(
count);
632 acc.throw_end_processing_on_flush();
638template <
typename EventList>
class capture_output {
639 vector_queue<recorded_event<EventList>> output;
640 bool flushed =
false;
641 std::size_t error_in = std::numeric_limits<std::size_t>::max();
642 std::size_t end_in = std::numeric_limits<std::size_t>::max();
643 bool error_on_flush =
false;
644 bool end_on_flush =
false;
646 access_tracker<capture_output_access> trk;
648 struct access_impl : capture_output_access_impl_base {
649 capture_output *self;
651 explicit access_impl(capture_output *self) : self(self) {}
653 [[nodiscard]]
auto peek_events() const -> std::any final {
656 void pop_event() final { self->output.pop(); }
657 [[nodiscard]]
auto is_empty() const ->
bool final {
658 return self->output.empty();
660 [[nodiscard]]
auto is_flushed() const ->
bool final {
661 return self->flushed;
663 void set_up_to_throw(std::size_t count,
bool use_error)
final {
664 self->set_up_to_throw(count, use_error);
666 [[nodiscard]]
auto events_as_string() const -> std::
string final {
667 return self->events_as_string();
672 std::vector<recorded_event<EventList>> ret;
673 ret.reserve(output.size());
675 output.for_each([&ret](
auto const &pair) { ret.push_back(pair); });
679 [[nodiscard]]
auto events_as_string() const -> std::
string {
680 std::ostringstream stream;
681 output.for_each([&](
auto const &event) { stream <<
'\n' << event; });
685 void set_up_to_throw(std::size_t count,
bool use_error) {
686 if (count == std::numeric_limits<std::size_t>::max()) {
688 error_on_flush =
true;
700 explicit capture_output(access_tracker<capture_output_access> &&tracker)
701 : trk(std::move(tracker)) {
702 trk.register_access_factory([](
auto &tracker) {
705 return capture_output_access(std::make_unique<access_impl>(self));
709 [[nodiscard]]
auto introspect_node() const -> processor_info {
710 return processor_info(
this,
"capture_output");
713 [[nodiscard]]
auto introspect_graph() const -> processor_graph {
714 return processor_graph().push_entry_point(
this);
717 template <
typename Event>
718 requires type_list_member<std::remove_cvref_t<Event>, EventList>
719 void handle(Event &&event) {
720 static constexpr auto valcat = [] {
721 if constexpr (std::is_lvalue_reference_v<Event>) {
722 if constexpr (std::is_const_v<std::remove_reference_t<Event>>)
723 return emitted_value_category::const_lvalue;
725 return emitted_value_category::nonconst_lvalue;
727 if constexpr (std::is_const_v<Event>)
728 return emitted_value_category::const_rvalue;
730 return emitted_value_category::nonconst_rvalue;
735 throw test_error(
"test error upon event");
736 output.push(std::pair{valcat, std::forward<Event>(event)});
738 throw end_of_processing(
"test end-of-stream upon event");
745 if (error_on_flush) {
746 throw test_error(
"test error upon flush");
750 throw end_of_processing(
"test end-of-stream upon flush");
756 bool flushed =
false;
757 bool error_on_flush =
false;
758 bool end_on_flush =
false;
759 access_tracker<capture_output_access> trk;
761 struct access_impl : capture_output_access_impl_base {
764 explicit access_impl(capture_output *self) : self(self) {}
766 [[noreturn]]
static void not_allowed() {
767 throw std::logic_error(
768 "operation not allowed on capture_output with empty event list");
771 [[nodiscard]]
auto peek_events() const -> std::any final {
774 void pop_event() final { not_allowed(); }
775 [[nodiscard]]
auto is_empty() const ->
bool final {
return true; }
776 [[nodiscard]]
auto is_flushed() const ->
bool final {
777 return self->flushed;
779 void set_up_to_throw(std::size_t ,
783 [[nodiscard]]
auto events_as_string() const -> std::
string final {
788 void set_up_to_throw(std::size_t count,
bool use_error) {
789 if (count == std::numeric_limits<std::size_t>::max()) {
791 error_on_flush =
true;
798 explicit capture_output(access_tracker<capture_output_access> &&tracker)
799 : trk(std::move(tracker)) {
800 trk.register_access_factory([](
auto &tracker) {
803 return capture_output_access(std::make_unique<access_impl>(self));
807 [[nodiscard]]
auto introspect_node() const -> processor_info {
808 return processor_info(
this,
"capture_output");
811 [[nodiscard]]
auto introspect_graph() const -> processor_graph {
812 return processor_graph().push_entry_point(
this);
817 if (error_on_flush) {
818 throw test_error(
"test error upon flush");
822 throw end_of_processing(
"test end-of-stream upon flush");
826template <
typename Downstream>
class feed_input {
827 static_assert(processor<Downstream>);
829 std::vector<std::pair<std::shared_ptr<context>, std::string>>
831 feed_as refmode = feed_as::const_lvalue;
832 Downstream downstream;
834 void check_outputs_ready(std::string
const &input) {
835 if (outputs_to_check.empty())
836 throw std::logic_error(
837 "feed_input has no registered capture_output to check");
838 for (
auto &[context, name] : outputs_to_check)
839 context->template access<capture_output_access>(name)
840 .check_ready_for_input(input);
845 : downstream(std::move(downstream)) {}
847 explicit feed_input(feed_as mode, Downstream downstream)
848 : refmode(mode), downstream(std::move(downstream)) {}
850 [[nodiscard]]
auto introspect_node() const -> processor_info {
851 return processor_info(
this,
"feed_input");
854 [[nodiscard]]
auto introspect_graph() const -> processor_graph {
855 return downstream.introspect_graph().push_entry_point(
this);
858 void require_output_checked(std::shared_ptr<context> context,
860 context->access<capture_output_access>(name);
861 outputs_to_check.emplace_back(std::move(context), std::move(name));
864 template <
typename Event>
865 requires handler_for<Downstream, std::remove_cvref_t<Event>>
866 void handle(Event &&event) {
867 check_outputs_ready(
"event of type " +
868 std::string(
typeid(event).name()));
870 if (refmode == feed_as::const_lvalue) {
871 downstream.handle(
static_cast<Event
const &
>(event));
872 }
else if constexpr (std::is_lvalue_reference_v<Event>) {
873 std::remove_cvref_t<Event> copy(event);
874 downstream.handle(std::move(copy));
876 downstream.handle(std::forward<Event>(event));
881 check_outputs_ready(
"flush");
913template <
typename EventList>
915 return internal::capture_output<EventList>(std::move(tracker));
952template <
typename Downstream>
954 return internal::feed_input<Downstream>(value_category,
955 std::move(downstream));
975 return strm <<
"empty_test_event<" << N <<
">";
988template <
int N,
typename DataTypes = default_data_types>
1001 return strm <<
"time_tagged_test_event<" << N <<
">{" << e.abstime
1014template <
typename T>
1016 using U = std::remove_cv_t<T>;
1017 struct test_storage {
1020 auto storage = test_storage{std::vector<U>(il.begin(), il.end())};
1021 return bucket<T>(std::span(storage.v), std::move(storage));
1032 using U = std::remove_cv_t<T>;
1033 struct test_storage {
1036 auto storage = test_storage{std::vector<U>(s.begin(), s.end())};
1037 return bucket<T>(std::span(storage.v), std::move(storage));
1053 std::shared_ptr<bucket_source<T>> src;
1055 std::size_t count = 0;
1057 explicit test_bucket_source(
1059 : src(std::move(backing_source)), value(std::move(fill_value)) {}
1065 -> std::shared_ptr<test_bucket_source<T>> {
1066 return std::shared_ptr<test_bucket_source<T>>(
new test_bucket_source(
1067 std::move(backing_source), std::move(fill_value)));
1072 auto b = src->bucket_of_size(size);
1073 std::fill(b.begin(), b.end(), value);
1086 return src->supports_shared_views();
1092 return src->shared_view_of(bkt);
1117template <
typename Event>
1120 static_assert(std::is_trivial_v<Event>);
1121 auto const srcspan = std::as_bytes(std::span(&bytes, 1));
1123 auto const retspan = std::as_writable_bytes(std::span(&ret, 1));
1124 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
Access for tcspc::capture_output() processors.
Definition test_utils.hpp:252
auto check_not_flushed() -> bool
Check that no recorded output events remain but the output has not been flushed.
Definition test_utils.hpp:407
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:450
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:460
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:273
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:363
auto check_flushed() -> bool
Check that no recorded output events remain and the output has been flushed.
Definition test_utils.hpp:431
void throw_end_processing_on_flush()
Arrange to throw tcspc::end_of_processing on receiving a flush.
Definition test_utils.hpp:474
auto pop(feed_as feeder_value_category, emitted_as value_category) -> Event
Retrieve the next recorded output event.
Definition test_utils.hpp:306
void throw_error_on_flush()
Arrange to throw tcspc::test_error on receiving a flush.
Definition test_utils.hpp:467
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:546
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:619
auto check_flushed() -> bool
Check that no recorded output events remain and the output has been flushed.
Definition test_utils.hpp:601
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:570
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:507
auto pop(emitted_as value_category) -> Event
Retrieve the next recorded output event, checking its value category.
Definition test_utils.hpp:535
void throw_end_processing_on_flush()
Arrange to throw tcspc::end_of_processing on receiving a flush.
Definition test_utils.hpp:631
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:609
capture_output_checker(feed_as feeder_value_category, capture_output_access access)
Construct from a tcspc::capture_output_access, with the feeder's value category.
Definition test_utils.hpp:498
auto pop() -> Event
Retrieve the next recorded output event, disregarding value category.
Definition test_utils.hpp:520
void throw_error_on_flush()
Arrange to throw tcspc::test_error on receiving a flush.
Definition test_utils.hpp:626
auto check_not_flushed() -> bool
Check that no recorded output events remain but the output has not been flushed.
Definition test_utils.hpp:588
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:1084
auto bucket_of_size(std::size_t size) -> bucket< T > override
Implements bucket source requirement.
Definition test_utils.hpp:1071
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:1063
auto bucket_count() const noexcept -> std::size_t
Return the number of buckets created so far.
Definition test_utils.hpp:1079
auto shared_view_of(bucket< T > const &bkt) -> bucket< T const > override
Implements sharable bucket source requirement.
Definition test_utils.hpp:1090
Concept that is satisfied when a type is contained in a type list.
Definition type_list.hpp:158
#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:253
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:1015
emitted_as
Value category to check emitted events against.
Definition test_utils.hpp:133
feed_as
Value category used to feed an event via tcspc::feed_input.
Definition test_utils.hpp:108
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:1119
@ always_rvalue
Require non-const rvalue.
Definition test_utils.hpp:141
@ any_allowed
Require const lvalue or rvalue, or non-const rvalue.
Definition test_utils.hpp:135
@ same_as_fed
Require the same category as the events being fed.
Definition test_utils.hpp:137
@ always_lvalue
Require const lvalue.
Definition test_utils.hpp:139
@ rvalue
Feed as non-const rvalue.
Definition test_utils.hpp:112
@ const_lvalue
Feed as const lvalue.
Definition test_utils.hpp:110
auto count(access_tracker< count_access > &&tracker, Downstream downstream)
Create a processor that counts events of a given type.
Definition count.hpp:313
auto sink_events()
Create a processor that ignores only specific event types.
Definition test_utils.hpp:81
auto sink_event_list()
Create a processor that ignores only specific event types.
Definition test_utils.hpp:99
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:953
auto capture_output(access_tracker< capture_output_access > &&tracker)
Create a sink that records the output of a processor under test.
Definition test_utils.hpp:914
libtcspc namespace.
Definition acquire.hpp:29
Abstract base class for polymorphic bucket sources.
Definition bucket.hpp:505
Empty event for testing.
Definition test_utils.hpp:965
friend auto operator<<(std::ostream &strm, empty_test_event< N > const &) -> std::ostream &
Stream insertion operator.
Definition test_utils.hpp:972
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:989
friend auto operator<<(std::ostream &strm, time_tagged_test_event const &e) -> std::ostream &
Stream insertion operator.
Definition test_utils.hpp:999
friend auto operator==(time_tagged_test_event const &lhs, time_tagged_test_event const &rhs) noexcept -> bool=default
Equality comparison operator.
DataTypes::abstime_type abstime
Timestamp.
Definition test_utils.hpp:991
Compile-time representation of a list of types.
Definition type_list.hpp:38