package IPC::Run::Test;

use strict;
use warnings;
use Test::More;
use Exporter;
use IPC::Run qw{ harness };
use IPC::Run::IO;

use vars qw{@ISA @EXPORT};

BEGIN {
    @ISA    = qw{ Exporter };
    @EXPORT = qw{ filter_tests };
}

## This is not needed by most users.  Should really move to IPC::Run::TestUtils
#=item filter_tests
#
#   my @tests = filter_tests( "foo", "in", "out", \&filter );
#   $_->() for ( @tests );
#
#This creates a list of test subs that can be used to test most filters
#for basic functionality.  The first parameter is the name of the
#filter to be tested, the second is sample input, the third is the
#test(s) to apply to the output(s), and the rest of the parameters are
#the filters to be linked and tested.
#
#If the filter chain is to be fed multiple inputs in sequence, the second
#parameter should be a reference to an array of those inputs:
#
#   my @tests = filter_tests( "foo", [qw(1 2 3)], "123", \&filter );
#
#If the filter chain should produce a sequence of outputs, then the
#third parameter should be a reference to an array of those outputs:
#
#   my @tests = filter_tests(
#      "foo",
#      "1\n\2\n",
#      [ qr/^1$/, qr/^2$/ ],
#      new_chunker
#   );
#
#See t/run.t and t/filter.t for an example of this in practice.
#
#=cut

##
## Filter testing routines
##
sub filter_tests($;@) {
    my ( $name, $in, $exp, @filters ) = @_;
    my @in  = ref $in eq 'ARRAY'  ? @$in  : ($in);
    my @exp = ref $exp eq 'ARRAY' ? @$exp : ($exp);
    my IPC::Run::IO $op;
    my $output;
    my @input;
    my $in_count = 0;
    my @out;
    my $h;

  SCOPE: {
        $h  = harness();
        $op = IPC::Run::IO->_new_internal(
            '<', 0, 0, 0, undef,
            IPC::Run::new_string_sink( \$output ),
            @filters,
            IPC::Run::new_string_source( \@input ),
        );
        $op->_init_filters;
        @input  = ();
        $output = '';
        is(
            !defined $op->_do_filters($h),
            1,
            "$name didn't pass undef (EOF) through"
        );
    }

    ## See if correctly does nothing on 0, (please try again)
  SCOPE: {
        $op->_init_filters;
        $output = '';
        @input  = ('');
        is(
            $op->_do_filters($h),
            0,
            "$name didn't return 0 (please try again) when given a 0"
        );
    }

  SCOPE: {
        @input = ('');
        is(
            $op->_do_filters($h),
            0,
            "$name didn't return 0 (please try again) when given a second 0"
        );
    }

  SCOPE: {
        for ( 1 .. 100 ) {
            last unless defined $op->_do_filters($h);
        }
        is(
            !defined $op->_do_filters($h),
            1,
            "$name didn't return undef (EOF) after two 0s and an undef"
        );
    }

    ## See if it can take @in and make @out
  SCOPE: {
        $op->_init_filters;
        $output = '';
        @input  = @in;
        while ( defined $op->_do_filters($h) && @input ) {
            if ( length $output ) {
                push @out, $output;
                $output = '';
            }
        }
        if ( length $output ) {
            push @out, $output;
            $output = '';
        }
        is(
            scalar @input,
            0,
            "$name didn't consume its input"
        );
    }

  SCOPE: {
        for ( 1 .. 100 ) {
            last unless defined $op->_do_filters($h);
            if ( length $output ) {
                push @out, $output;
                $output = '';
            }
        }
        is(
            !defined $op->_do_filters($h),
            1,
            "$name didn't return undef (EOF), tried  100 times"
        );
    }

  SCOPE: {
        is(
            join( ', ', map "'$_'", @out ),
            join( ', ', map "'$_'", @exp ),
            $name
        );
    }

  SCOPE: {
        ## Force the harness to be cleaned up.
        $h = undef;
        ok(1);
    }
}

1;
