Acme-FSM

 view release on metacpan or  search on metacpan

t/TestSuite.pm  view on Meta::CPAN


=over

=item I<$main::rc>

ARRAY of items FSM has just left behind (contents of I<$main::rc>);

=item I<\%blackboard>

A blackboard snapshot after FSM has been run;
That snapshotting means:

=over

=item *

all keys of I<$main::bb>, except special I<_> key, are copied;

=item *

all keys of I<$main::bb{_}>, exccept I<fst> key, are copied.

=back

That is, everything, except filtered goes in one HASH.

=back

If B<Test::More::is_deeply()> fails then a line in a test-unit where it
happened is hinted with B<AFSMTS_diag()>.

=cut

sub AFSMTS_deeply ( \@$ )                      {
    my( $expected, $descr ) = @_;
    my $got = { };
    $got->{$_} = $main::bb->{$_}     foreach grep $_ ne q|_|, keys %$main::bb;
    $got->{$_} = $main::bb->{_}{$_}                                    foreach
      grep $_ ne q|fst|, keys %{$main::bb->{_}};
    unless( Test::More::is_deeply(
    [ $main::rc, $got ], $expected, $descr )) {
        AFSMTS_diag sprintf qq|   at %s line %i.|, ( caller )[1,2];
        AFSMTS_dump [ $main::rc ];
        AFSMTS_dump [ $got      ]              }}

=item B<AFSMTS_wrap()>

    use t::TestSuite qw/ :run /;
    our( $rc, %st, $bb, %opts );
    our( $stdout, $stderr );

    AFSMTS_wrap;
    AFSMTS_deeply @{[ ]}, 'again!';

    TODO: {
        local TODO = 'oops, not yet';
        AFSMTS_wrap;
        isnt $rc, "ALRM\n", 'success!';
    }

Wraps B<connect()> and B<process()>.
Everything is got from I<main>.
Those are:

=over

=item I<$rc>

ARRAY;
storage for FSM return;

=item I<%st>

Status table;

=item I<$bb>

B<Acme::FSM> object;
An object is reZ<>B<connect>ed;
I<$bb{queue}> is created and set to empty ARRAY.

=item I<%opts>

A hash of options, those will be passed to constructor.

=back

I<STDOUT> and I<STDERR> are backed up in scalars;
those are saved in I<$main::stdout> and I<$main::stderr>.
I<STDERR> is output with B<AFSMTS_diag()> anyway.
However, it's trimmed to first 1024 bytes
(unless I<$t::TestSuite::NO_TRIM> is TRUE)
(it's not clear yet if those are 1024 bytes or characters).

Also, there's a timeout feature.
That timeout should be protected with TODO of B<Test::More>.
I<STDERR> is dumped too.

That timeout is implemented with B<alarm>ed B<eval>.
That B<eval> protects against B<die>s too.

=cut

sub AFSMTS_wrap ( )   {
    open my $stdout_bak, q|>&|, \*STDOUT;
    open my $stderr_bak, q|>&|, \*STDERR;

    close STDOUT; open STDOUT, q|>|, \$main::stdout;
    close STDERR; open STDERR, q|>|, \$main::stderr;
    local $SIG{__DIE__} = sub          {
        alarm 0;
        close STDOUT; open STDOUT, q|>&|, $stdout_bak;
        close STDERR;
        open STDERR, q|>&|, $stderr_bak };

    do                                                               {
        no warnings qw| once |;
        $main::bb = Acme::FSM->connect( { %main::opts }, \%main::st ) };
    $main::bb->{queue} = [ ];
    my $rc = [ eval {
        local $SIG{ALRM} = sub { die qq|ALRM\n| };
        alarm 3;
        $main::rc = [ $main::bb->process ];
        alarm 0;
        1            } ];
    unless( @$rc )        {
# TODO:20121120224141:whynot: Make sure it's 1024 characters not bytes.
        $main::stderr = substr $main::stderr || '', 0, 1024   unless $NO_TRIM;
        $main::rc = [ $@ ] }
    close STDERR; open STDERR, q|>&|, $stderr_bak;
    close STDOUT; open STDOUT, q|>&|, $stdout_bak;

    AFSMTS_diag $main::stderr  }

=item B<AFSMTS_class_wrap()>

    use t::Test::Suite qw/ :wraps /;
    our( $rc, %st, $bb );
    our( $stdout, $stderr );
    AFSMTS_class_wrap @list;

Complete analogy of B<AFSMTS_wrap()> except B<process()> isn't called and
there's no timeout protection.
Also, there's I<$t::TestSuite::class_cheat>, what, if B<defined> is supposed
to be class name of B<A::F> descandant.

=cut

our $class_cheat;
sub AFSMTS_class_wrap ( @ ) {
    open my $stdout_bak, q|>&|, \*STDOUT;
    open my $stderr_bak, q|>&|, \*STDERR;

    close STDOUT; open STDOUT, q|>|, \$main::stdout;
    close STDERR; open STDERR, q|>|, \$main::stderr;
    local $SIG{__DIE__} = sub {
        close STDOUT; open STDOUT, q|>&|, $stdout_bak;
        close STDERR; open STDERR, q|>&|, $stderr_bak;
        AFSMTS_diag $main::stderr    };
    $main::bb = $class_cheat                        ?
      eval qq|${class_cheat}->connect( \@_ )| :
      Acme::FSM->connect( @_ );
    close STDERR; open STDERR, q|>&|, $stderr_bak;
    close STDOUT; open STDOUT, q|>&|, $stdout_bak;

    AFSMTS_diag $main::stderr }

=item B<AFSMTS_object_wrap()>

    use t::TestSuite qw/ :wraps /;
    our( $rc, %st, $bb );
    our( $stdout, $stderr );
    AFSMTS_object_wrap $childof_A_F, @list;

Complete analogy of B<AFSMTS_wrap()> except B<process()> isn't called and
there's no timeout protection.
It's different from B<AFSMTS_class_wrap> that it goes with
object-construction.
That object goes as a first parameter, then comes list of items to process.

=cut

sub AFSMTS_object_wrap ( $@ ) {
    my $obj = shift @_;
    open my $stdout_bak, q|>&|, \*STDOUT;
    open my $stderr_bak, q|>&|, \*STDERR;

    close STDOUT; open STDOUT, q|>|, \$main::stdout;
    close STDERR; open STDERR, q|>|, \$main::stderr;
    local $SIG{__DIE__} = sub {
        close STDOUT; open STDOUT, q|>&|, $stdout_bak;
        close STDERR; open STDERR, q|>&|, $stderr_bak;
        AFSMTS_diag $main::stderr    };
    $main::bb = $obj->connect( @_ );
    close STDERR; open STDERR, q|>&|, $stderr_bak;
    close STDOUT; open STDOUT, q|>&|, $stdout_bak;

    AFSMTS_diag $main::stderr }

=item B<AFSMTS_method_wrap()>

    use t::TestSuite qw/ :wraps /;
    our( $rc, %st, $bb );
    our( $stdout, $stderr );
    AFSMTS_method_wrap 'some_method', @list;

Complete analogy of B<AFSMTS_wrap()> except instead of B<process()> some
requested I<$method> is B<can>ed first, than invoked with I<@list> over
I<$main::bb> in list context.
What is returned is placed in I<$main::rc> wrapped in ARRAY.
If I<$method> returned one element then ARRAY is replaced with scalar.

=cut

sub AFSMTS_method_wrap ( $@ ) {
    open my $stdout_bak, q|>&|, \*STDOUT;
    open my $stderr_bak, q|>&|, \*STDERR;

    close STDOUT; open STDOUT, q|>|, \$main::stdout;
    close STDERR; open STDERR, q|>|, \$main::stderr;
    my $method = $main::bb->can( shift @_ );
    my $rc = [ eval {
        local $SIG{ALRM} = sub { die qq|ALRM\n| };
        alarm 3;
        $main::rc  = [ $main::bb->$method( @_ ) ];
        alarm 0;
        1 } ];
    alarm 0;
    unless( @$rc )        {
        $main::stderr = substr $main::stderr // '', 0, 1024   unless $NO_TRIM;
        $main::rc = [ $@ ] }
    $main::rc  = $main::rc->[0]                            if 1 == @$main::rc;
    close STDERR; open STDERR, q|>&|, $stderr_bak;
    close STDOUT; open STDOUT, q|>&|, $stdout_bak;

    AFSMTS_diag $main::stderr  }

=item B<AFSMTS_croakson> 'actual description'

    use t::TestSuite qw/ :diag /;
    $rc = eval { die 'as expected'; 1 };
    is !$rc, 0, AFSMTS_croakson 'surprise';

That will add I<$@> (with newlines replaced with spaces) to otherwise dumb
description, like this:

    ok 1 - croaks on (surprise) (as expected at test-unit.t line 12 )

=cut

sub AFSMTS_croakson ( $ )                                     {
    my $eval_msg = $@;
    $eval_msg =~ tr{\n}{ };
    return sprintf q|croaks on (%s) (%s)|, shift @_, $eval_msg }

=item B<AFSMTS_shift()>

    our %opts;
    our @inbase = ( qw/ a b c /, undef );
    our @input = @inbase;
    $opts{source} = \&AFSMTS_shift;
    AFSMTS_wrap;

Quiet generic implementation of I<{source}> code.
Uses script globals:

=over



( run in 0.936 second using v1.01-cache-2.11-cpan-39bf76dae61 )