=begin pod
=head1 PREFACE

This document provides general information about C<Test::Async>. Technical details are provided in corresponding
modules.

General test framework use information can be found in the documentation of Raku's standard
L<Test suite|https://docs.raku.org/type/Test>.
L<C<Test::Async::Base>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Base.md> provides
information about differences and additions between the standard framework and C<Test::Async>.

=head1 INTRODUCTION

=head2 Terminology

Throughout documentation the following terms are to be used:

=head3 I<Test suite>

This term can have two meanings:

=item a collection of tests
=item the core object responsible for running the tests

The particular meaning is determined by a context or some other way.

=head3 I<Test bundle> or just I<bundle>

A module or a role implementing a set of test tools or extending/modifying the core functionality. A bundle providing
the default set of tools is included into the framework and implemented by
L<C<Test::Async::Base>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Base.md>.

=head3 I<Reporter>

A test bundle which provides reporting capabilities. For example,
L<C<Test::Async::Reporter::TAP>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Reporter/TAP.md>
implements TAP output.

=head3 I<Test tool>

This is a routine provided by a bundle to test a condition. Typical and commonly known I<test tools> are C<pass>,
C<flunk>, C<ok>, C<nok>, etc.

=head1 ARCHITECTURE

The framework is built around I<test suite> objects driven by events. Suites are organized with parent-child relations with a
single topmost suite representing the main test compunit. Child suites are subjects of a job manager control.

A typical workflow consist of the following steps:

=item a test suite is created
=item it's body is executed. Any invoked test tool results in one or couple events sent
=item events are taken care of by a reporter which presents a user with meaningful representation of testing outcomes
=item if a child suite created it is either invoked instantly or postponed for later depending on it's parent suite
status
=item when suite is finished `done-testing` is invoked either implicitly or explicitly

=head2 Test Suite Creation

On startup the framework constructs a custom C<Test::Async::Suite> class which incorporates all core functionality and
extensions provided by bundles. The following code:

    use Test::Async;
    say test-suite.^mro(:roles).map( *.^shortname ).join(", ")

results in:

    Suit, Base_class, Base, TAP_class, TAP, Reporter, Hub, JobMgr, Aggregator, Any, Mu
    1..0

I<Note that C<:roles> named parameter is available since Rakudo compiler release 2020.01.>

Next paragraphs are explaining where this output comes from.

Let's start with bundles. One is created with either C<test-bundle> or C<test-reporter> keyword provided by
L<C<Test::Async::Decl>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Decl.md> module.
For example:

    test-bundle MyBundle {
        method my-test($got, $expected, $message) is test-tool {
            ...
        }
    }

In fact it is nothing else but a role declaration but with two important side effects:

=item the role is backed by
L<C<Test::Async::Metamodel::BundleHOW>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Metamodel/BundleHOW.md>
metaclass which subclasses
C<Metamodel::ParametricRoleHOW>
=item the declaration installs C<ENTER> phaser on the compunit it is declared in which auto-registers the bundle with
the framework core.

The second item means that this code:

    use MyBundle;
    use Test::Async;
    plan 1;
    my-test pi, 2*pi, "whatever";

would just work. BTW, if one would try to dump parents and role of the suite object, as show above, he would get:

    Suit, MyBundle_class, MyBundle, TAP_class, TAP, Reporter, Hub, JobMgr, Aggregator, Any, Mu

Becase the framework skips loading the default bundle if there is one explicitly requested by a user. Same applies for
C<TAP> which is the default reporter bundle and which wouldn't be loaded if the user C<use>s an alternative.

When all bundles were loaded and registered, time comes for
L<C<Test::Async>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async.md>
module to actually construct the suite class.

B<Note> that this is why
L<C<Test::Async>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async.md>
must always be C<use>d last. No bundle registered post-suite construction would be actually used.

The construction algorithm could roughly be written as:

=item1 take the C<Test::Async::Hub> class as the first and the current parent
=item1 take bundles in the order they registered and make classes of them
=item2   class is created as an empty one with bundle role applied
=item2   the current parent class is added as a parent
=item2   the new bundle class is set as the current parent
=item1 a custom C<Test::Async::Suite> class created, its only parent is set to the current parent

Putting this into a diagram would give us something like this for the default case:

    .         Suite -> Base_class -> TAP_class -> Hub -> Any -> Mu
    .                  |             |
    . bundle roles:    Base          TAP

See example script:
L<examples/multi-bundle.raku|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/examples/multi-bundle.raku>

This approach allows custom bundles easily extend the core functionality or even override certain aspects of it. The
latter is as simple as overriding parent methods. For example,
L<C<Test::Async::Base>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Base.md>
module uses this technique to
implement C<test-flunks> tool. It is doing so by intercepting test events passed in to C<send-test> method of
L<C<Test::Async::Hub>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Hub.md>.
It is then inverts test's outcome if necessary and does few other adjustments to a new test event
profile and passes on the control to the original C<send-test> to complete the task.

=head2 Job Management

The asynchronous nature of the framework requires a proper job management subsystem. It is implemented by
L<C<Test::Async::JobMgr>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/JobMgr.md> role and
L<C<Test::Async::Job>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Job.md>
class representing a single job to be done. The subsystem implements the following concepts:

=item synchronous execution
=item asynchronous (threaded) execution
=item asynchronous job management with limited number of simultaneously executed jobs
=item postponing

A job is L<C<Code>|https://docs.raku.org/type/Code> instance accompanied with its associated attributes. Code return
value is never provided directly but only via a fulfilled L<C<Promise>|https://docs.raku.org/type/Promise>.

The way the manager works is it creates a pool (not a queue) of jobs. The order in which they're executed is defined by
the user code invoking them. When a job completes the manager removes it from the pool. Though not directly manager's
job, but it provides a possibility to postpone a job. In this case it is placed into a queue from where it could be
picked up and invoked any time it is needed. For example, C<Test::Async::Hub> is using this to invoke child suites in
a random order: jobs for corresponding suites are postponed and when the main code block of the parent suite finishes
it takes the postponed queue, shuffles jobs in it and invokes them in the resulting order.

=head2 Events

 C<Test::Async> framework handles concurrency using event-driven flow control. Each event is an instance of a class
 inheriting from
 L<C<Test::Async::Event>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Event.md> class. Events
 are queued using a L<C<Channel>|https://docs.raku.org/type/Channel> where they're read from by a dedicated thread and
 dispatched for handling by suite object methods. So it makes each suit own at least two threads: first is for tests
 themselves, the other one is for event handling.

    Thread#1 \
              \
    Thread#2 --> [Event Queue] -> Event Handler Thread
              /
    Thread#3 /

The approach allows to combine the best of two worlds: speed of asynchronous operations and predictability of sequential
code. In particular, it proves to be useful for object state changes like, for example, for collecting messages from
child suites ran asynchronously. Because the messages are stashed in an L<C<Array>|https://docs.raku.org/type/Array> the
procedure is prone to race condition bugs. But when the responsibility of updating the array is in hands of a single
thread it greatly simplifies the task.

Another advantage of the events is the ease of extending the framework functionality. Look at
L<C<Test::Async::Reporter::TAP>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Reporter/TAP.md>,
for example. It takes the burden of reporting to user on its 'shoulders' unloading it off the core. And it does so
simply by listening to C<Event::Test> kind of events. It would be as easy to implement an alternative reporter to
get the test results be sent anywhere!

=head2 Suite Plan And Lifecycle

Suite has a number of parameters affecting it's execution. Those are:

=item number of tests planned
=item do child suites are invoked in parallel?
=item do child suites invoked randomly?
=item should the suite be skipped over?
=item does suite tests for a TODO feature?

While executed, the suite passes a few stages:

=item I<initialization>
=item I<in progress> - tests are being ran
=item I<finishing> - any postponed jobs are executed
=item I<finished> - testing is done, suite is summing up and possibly reporting the results
=item I<dismissed> - all done, suit object can be dropped

The parameters can only be set or changed while suite is being initialized and no test tools can be invoked at and after
the I<finished> stage.

Worth noting that I<finishing> stage is basically same as C<in progress> except that it indicates that the time of
postponed jobs has come.

=head2 Test Tools

A test tool is a method with C<test-tool> trait applied. It has two properties:

=item C<readify> which defines whether invoking the tool results in suite transition from stage I<initializing> into
I<in progress>
=item C<skippable> defines whether the tool can be skipped over. For example, C<ok> from
L<C<Test::Async::Base>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Base.md> is skippable;
but C<skip> and the family themselves are not, as well as C<todo> and few other.

    test-bundle Test::Foo {
        method test-foo(...) is test-tool(:!skippable, :!readify) { ... }
        method test-bar(...) is test-tool { ... }
    }

=head1 SEE ALSO

L<C<Test::Async::CookBook>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/CookBook.md>,
L<C<Test::Async::Base>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Base.md>,
L<C<Test::Async::When>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/When.md>,
L<C<Test::Async::Hub>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Hub.md>,
L<C<Test::Async::Event>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Event.md>,
L<C<Test::Async::Decl>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Decl.md>,
L<C<Test::Async::X>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/X.md>,
L<C<Test::Async::Utils>|https://github.com/vrurg/raku-Test-Async/blob/v0.0.5/docs/md/Test/Async/Utils.md>,

=AUTHOR Vadim Belman <vrurg@cpan.org>

=end pod
