Skip to content

Conversation

kirkshoop
Copy link
Contributor

test_context implements a time-scheduler with additional test features.

test_context is used to build tests using marble sequence senders
and marble recorders

test_clock_context & test_clock are used to represent time for the tests

test_scheduler is used to queue tasks on the test_context at virtual-time points

One or more __test_sequence(s) are constructed from marble
diagrams and each schedules marbles on the test_scheduler
when connected and started

Once one or more __test_sequence(s) have been composed into an expression,
the test_context is used to record the expression results as a
set of marbles. To support the testing of infinite sequences
the recording will be requested to stop at a default of 1000ms from
start() if the expression has not completed.

The expression result marbles are then compared to an expected
set of marbles generated from a separate marble diagram

An example of usage:

TEST_CASE(
"test_scheduler - test_context marble-sequence never",
"[sequence_senders][test_scheduler]") {
  test_context __test{};
  auto __clock = __test.get_clock();
  CHECK(test_clock::time_point{0ms} == __clock.now());

  // a sequence that will produce '0' at 1ms from start()
  // and then never complete
  auto __sequence = __test.get_marble_sequence_from(
    "  -0-"_mstr);

  // the set of marbles for a sequence that contains '5'
  // at 1ms from start() and then is externally stopped
  // after 1000ms have elapsed since start()
  auto expected = get_marbles_from(__clock,
    "=^-5 998ms $"_mstr);

  // record an expression that is expected to turn '0' to '5'
  auto actual = __test.get_marbles_from(
                       __sequence
                       | then_each([](char c){ return c+5; }));

  CHECK(test_clock::time_point{1000ms} == __clock.now());
  CAPTURE(__sequence.__marbles_);
  CHECK(expected == actual);
}

with output:

-------------------------------------------------------------------------------
test_scheduler - test_context marble-sequence never
-------------------------------------------------------------------------------
/home/coder/stdexec/test/exec/sequence/test_test_scheduler.cpp:114
...............................................................................

/home/coder/stdexec/test/exec/sequence/test_test_scheduler.cpp:117: PASSED:
  CHECK( test_clock::time_point{0ms} == __clock.now() )
with expansion:
  0ms == 0ms

/home/coder/stdexec/test/exec/sequence/test_test_scheduler.cpp:123: PASSED:
  CHECK( test_clock::time_point{1000ms} == __clock.now() )
with expansion:
  1000ms == 1000ms

/home/coder/stdexec/test/exec/sequence/test_test_scheduler.cpp:125: PASSED:
  CHECK( expected == actual )
with expansion:
  { sequence_connect()@0ms, sequence_start()@0ms, set_value('5')@1ms,
  sequence_stopped()@1000ms }
  ==
  { sequence_connect()@0ms, sequence_start()@0ms, set_value('5')@1ms,
  sequence_stopped()@1000ms }
with message:
  __sequence.__marbles_ := { set_value('0')@1ms }

Marbles:

marble_t<_Clock> represents a signal for a sequence
sender and the frame at which the signal occurs/occured

a marble diagram is a string that is parsed into a
vector<marble_t<_Clock>>

Example:
this marble diagram
"--a--b--c|"
is equivalent to this set of marble_t

    marble_t{2ms, ex::set_value, 'a'},
    marble_t{5ms, ex::set_value, 'b'},
    marble_t{8ms, ex::set_value, 'c'},
    marble_t{8ms, sequence_end}

which is displayed as

    set_value('a')@2ms, set_value('b')@5ms,
    set_value('c')@8ms, sequence_end()@8ms

Diagram reference:
Time:

  ' ' indicates that 0ms has elapsed - used to line up diagrams in a visually pleasing manner
  '-' indicates that 1ms has elapsed
  ' 0-9+(ms|s|m) '
      indicates elapsed time at that point in the diagram that is equal to the specified number
      of ms - milliseconds,  s - seconds, m - minutes
      NOTE: must have a preceding and following space to disambiguate from values
  '(' begins a group of signals that all occur on the frame that the group begins on
  ')' ends a group of signals that all occur on the frame that the group begins on

Value:

  '0'-'9' 'a'-'z' 'A'-'Z'
      indicates that a value in the sequence completes with set_value( char )
  '#' indicates that a value in the sequence completes with set_error(error_code(interrupted))
  '.' indicates that a value in the sequence completes with set_stopped()

Sequence:

  '=' indicates connect() on the sequence sender
  '^' indicates start() on the sequence operation
  '|' indicates that the sequence completes with set_value()
  '$' indicates that the sequence completes with set_stopped()
  '?' indicates that request_stop() was sent to the sequence from an external source

record_marbles() will record a set of marbles from the signals of the specified sequence sender

- add `__debug_sequence_sender`
- add `__well_formed_sequence_sender`
- add static-member-fn support to `get_item_types`
- add static-member-fn support to `subscribe`
- add support for `STDEXEC_ENABLE_EXTRA_TYPE_CHECKING`
- add `__well_formed_sequence_sender` constraint
- add `__mexception` overloads for `get_item_types`
- remove constraints that were causing SFINAE that would skip the debug info
each input sequence may be on a different scheduler.
The merged items will invoke `set_next` on the receiver from all of the contexts.
Depending on the schedulers in play, the calls to `set_next` may overlap in parallel.
merge_each is a sequence adaptor that takes a sequence of nested
sequences and merges all the nested values from all the nested
sequences into a single output sequence.

the first error encountered will trigger a stop request for all
active operations. The error is stored and is emitted only after
all the active operations have completed.

If the error was emitted from an item, a new item is emitted
at the end to deliver the stored error.

any nested sequence or nested value that completes with
set_stopped will not cause any other operations to be stopped.
This allows individual nested sequences to be stopped without
breaking the merge of the remaining sequences.
…signatures, and get_item_types to SFINAE away.

This helps compiler errors to have better information
This is needed in tests that use the test_scheduler
Copy link

copy-pr-bot bot commented Oct 19, 2025

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@ericniebler
Copy link
Collaborator

/ok to test 84b041f

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants