class Test::Builder-0.2.1;

use Test::Builder::Test;
use Test::Builder::Output;
use Test::Builder::TestPlan;

my  Test::Builder           $!singleton;
has Test::Builder::Output   $.output handles 'diag';
has Test::Builder::TestPlan $.testplan;
has                         @!results;

method new ( Test::Builder $Class: $plan?, $output? )
{
    return $!singleton //= $Class.SUPER::new(
        testplan => $plan, output => $output
    );
}

method create ( Test::Builder $Class: $plan?, $output? )
{
    return $Class.SUPER::new( testplan => $plan, output => $output );
}

submethod BUILD
(
    Test::Builder::TestPlan $.testplan?,
    Test::Builder::Output   $.output = Test::Builder::Output.new()
)
{}

submethod DESTROY
{
    my $footer = $.testplan.footer( +@!results );
    $.output.write( $footer ) if $footer;
}

method get_test_number
{
    return +@!results + 1;
}

method plan ( Str $explanation?, Int $tests? )
{
    fail "Plan already set!" if $.testplan;

    if $tests
    {
        $.testplan = Test::Builder::TestPlan.new( expect => $tests );
    }
    elsif $explanation eq 'no_plan'
    {
        $.testplan = Test::Builder::NullPlan.new();
    }
    else
    {
        fail "Unknown plan";
    }

    $.output.write( $.testplan.header() );
}

method ok returns Bit ( $self: Bit $passed, Str $description = '' )
{
    $self.report_test(
        Test::Builder::Test.new(
            number      => $self.get_test_number(),
            passed      =>  $passed,
            description =>  $description,
        )
    );

    return $passed;
}

method diag ( Str $diagnostic? = '' )
{
    $.output.diag( $diagnostic );
}

method todo returns Bit ( $self: Bit $passed, Str $description?, Str $reason? )
{
    $self.report_test(
        Test::Builder::Test.new(
            todo        => 1,
            number      => $self.get_test_number(),
            reason      =>  $reason,
            description =>  $description,
        )
    );

    return $passed;
}

method skip ( $self: Int $num = 1, Str $reason = 'skipped' )
{
    for 1 .. $num
    {
        $self.report_test(
            Test::Builder::Test.new(
                skip   => 1,
                number => $self.get_test_number(),
                reason =>  $reason,
            )
        );
    }
}

method skip_all
{
    fail "Cannot skip_all with a plan" if $.testplan;

    $.output.write( "1..0" );
    exit 0;
}

method BAILOUT ( Str $reason = '' )
{
    $.output.write( "Bail out!  $reason" );
    exit 255;
}

method report_test ( Test::Builder::Test $test )
{
    fail 'No plan set!' unless $.testplan;

    push @!results, $test;
    $.output.write( $test.report() );
}

=pod

=head1 NAME

Test::Builder - Backend for building test libraries

=head1 SYNOPSIS

  module My::Test::Module;

  use Test::Builder;
  use Test::Builder::Output;

  my Test::Builder $Test .= new(
        output => Test::Builder::Output.new(
           error_output => open('my_error_log_file')
        )
    );

  sub plan (Str $explanation?, Int $tests?) is export {
     $Test.testplan($explanation, $tests);
  }

  sub ok ($passed, $description?, $todo?) is export {
     if $todo {
        $Test.todo($passed, $description, $todo) 
            || $Test.diag("FAILED : $description");
     }
     else {
        $Test.ok($passed, $description)
            || $Test.diag("FAILED : $description");
     }
  }

  sub is ($got, $expected, $description?, $todo?) is export {
     if $todo {
        $Test.todo($got eq $expected, $description, $todo)
            || $Test.diag("FAILED : $description");
     }
     else {
        $Test.ok($got eq $expected, $description)
            || $Test.diag("FAILED : $description");        
     }
  }

  # then using our test module the test file themselves ...

  use My::Test::Module;

  plan('no_plan');
  # or
  plan :tests<20>;

  ok(2 == 2, '... 2 is equal to 2');
  is(2 + 2, 5, '... 2 plus 2 should be 5', :todo<bug>);

=head1 DESCRIPTION

This is a Perl 6 port of the Perl 5 module Test::Builder.

=head1 PUBLIC ATTRIBUTES

=over 4

=item B<Test::Builder::Output $.output>

=item B<Test::Builder::TestPlan $.testplan>

=back

=head1 METHODS

=over 4

=item B<new( *@args )>

This method actually returns a Test::Builder singleton, creating it if
necessary.  The optional named arguments are:

=over 4

=item C<output>

A Test::Builder::Output object.

=item C<plan>

A Test::Builder::TestPlan object.

=back

=item B<create( *@args )>

This method actually creates and returns a new Test::Builder instance.  It
takes the same optional named arguments as C<new>.

=item B<plan( Str $explanation?, Int $tests? )>

Sets the current test plan or throws an exception if there's already a plan in
place.  You have two options for the plan.  If you pass a pair such as C<tests
=Egt 10>, the plan is to run ten tests.  If you pass the string C<no_plan>,
there is no set number of tests to run.

Those are the only valid arguments.

You must have a plan set before you can record any tests.

=item B<ok returns Bit ( Bit $passed, Str $description = '' )>

Records that a test has passed or failed, depending on the value of C<$passed>,
recording C<$description> as an optional explanation.

=item B<todo returns Bit ( Bit $passed, Str $description?, Str $reason? )>

Records that a test has passed or failed, depending on C<$passed> with an
optional C<$description>, but marks it as a TODO test with an optional
C<$reason>.

=item B<skip( Int $num = 1, Str $reason = 'skipped' )>

Records the skipping of C<$num> tests (one by default), giving an optional
C<$reason> for skipping them.

=item B<skip_all()>

Skips all of the tests before running them.

Fails if there is a test plan set.

=item B<BAILOUT( Str $reason = '' )>

Aborts the entire test run.

=item B<get_test_number()>

Returns the number of the I<next> test to record.

=item B<report_test( Test::Builder::Test $test )>

Records a test.  Internal use only, probably.

=back

=head1 SEE ALSO

Perl 5 Test::Builder.

=head1 AUTHORS

code by chromatic E<lt>chromatic@wgz.orgE<gt>

documentation by Stevan Little E<lt>stevan@iinteractive.comE<gt> and chromatic.

=cut
