#!./perl -w

# Tests for the source filters in coderef-in-@INC

BEGIN {
    chdir 't' if -d 't';
    @INC = qw(. ../lib);
    require './test.pl';
    skip_all_if_miniperl('no dynamic loading on miniperl, no Filter::Util::Call');
    skip_all_without_perlio();
}
use strict;
use Config;
use Filter::Util::Call;

plan(tests => 153);

unshift @INC, sub {
    no warnings 'uninitialized';
    ref $_[1] eq 'ARRAY' ? @{$_[1]} : $_[1];
};

my $fh;

open $fh, "<", \'pass("Can return file handles from \@INC");';
do $fh or die;

my @origlines = ("# This is a blank line\n",
		 "pass('Can return generators from \@INC');\n",
		 "pass('Which return multiple lines');\n",
		 "1",
		 );
my @lines = @origlines;
sub generator {
    $_ = shift @lines;
    # Return of 0 marks EOF
    return defined $_ ? 1 : 0;
};

do \&generator or die;

@lines = @origlines;
# Check that the array dereferencing works ready for the more complex tests:
do [\&generator] or die;

sub generator_with_state {
    my $param = $_[1];
    is (ref $param, 'ARRAY', "Got our parameter");
    $_ = shift @$param;
    return defined $_ ? 1 : 0;
}

do [\&generator_with_state,
    ["pass('Can return generators which take state');\n",
     "pass('And return multiple lines');\n",
    ]] or die;
   

open $fh, "<", \'fail("File handles and filters work from \@INC");';

do [$fh, sub {s/fail/pass/; return;}] or die;

open $fh, "<", \'fail("File handles and filters with state work from \@INC");';

do [$fh, sub {s/$_[1]/pass/; return;}, 'fail'] or die;

print "# 2 tests with pipes from subprocesses.\n";

my ($echo_command, $pass_arg, $fail_arg);

if ($^O eq 'VMS') {
    $echo_command = 'write sys$output';
    $pass_arg = '"pass"';
    $fail_arg = '"fail"';
}
else {
    if ($^O =~ /android/) {
        $echo_command = q{sh -c 'echo $@' -- };
    }
    else {
        $echo_command = 'echo';
    }
    $pass_arg = 'pass';
    $fail_arg = 'fail';
}

open $fh, "$echo_command $pass_arg|" or die $!;

do $fh or die;

open $fh, "$echo_command $fail_arg|" or die $!;

do [$fh, sub {s/$_[1]/pass/; return;}, 'fail'] or die;

sub rot13_filter {
    filter_add(sub {
		   my $status = filter_read();
		   tr/A-Za-z/N-ZA-Mn-za-m/;
		   $status;
	       })
}

open $fh, "<", \<<'EOC';
BEGIN {rot13_filter};
cnff("This will rot13'ed prepend");
EOC

do $fh or die;

open $fh, "<", \<<'EOC';
ORTVA {ebg13_svygre};
pass("This will rot13'ed twice");
EOC

do [$fh, sub {tr/A-Za-z/N-ZA-Mn-za-m/; return;}] or die;

my $count = 32;
sub prepend_rot13_filter {
    filter_add(sub {
		   my $previous = $_;
		   # Filters should append to any existing data in $_
		   # But (logically) shouldn't filter it twice.
		   my $test = "fzrt!";
		   $_ = $test;
		   my $status = filter_read();
		   my $got = substr $_, 0, length $test, '';
		   is $got, $test, "Upstream didn't alter existing data";
		   tr/A-Za-z/N-ZA-Mn-za-m/;
		   $_ = $previous . $_;
		   die "Looping infinitely" unless $count--;
		   $status;
	       })
}

open $fh, "<", \<<'EOC';
ORTVA {cercraq_ebg13_svygre};
pass("This will rot13'ed twice");
EOC

do [$fh, sub {tr/A-Za-z/N-ZA-Mn-za-m/; return;}] or die;

# This generates a heck of a lot of oks, but I think it's necessary.
my $amount = 1;
sub prepend_block_counting_filter {
    filter_add(sub {
		   my $output = $_;
		   my $count = 256;
		   while (--$count) {
		       $_ = '';
		       my $status = filter_read($amount);
		       cmp_ok (length $_, '<=', $amount, "block mode works?");
		       $output .= $_;
		       if ($status <= 0 or /\n/s) {
			   $_ = $output;
			   return $status;
		       }
		   }
		   die "Looping infinitely";
			  
	       })
}

open $fh, "<", \<<'EOC';
BEGIN {prepend_block_counting_filter};
pass("one by one");
pass("and again");
EOC

do [$fh, sub {return;}] or die;

open $fh, "<", \<<'EOC';
BEGIN {prepend_block_counting_filter};
pas("SSS make s fast SSS");
EOC

do [$fh, sub {s/s/ss/gs; s/([\nS])/$1$1$1/gs; return;}] or die;

sub prepend_line_counting_filter {
    filter_add(sub {
		   my $output = $_;
		   $_ = '';
		   my $status = filter_read();
		   my $newlines = tr/\n//;
		   cmp_ok ($newlines, '<=', 1, "1 line at most?");
		   $_ = $output . $_ if defined $output;
		   return $status;
	       })
}

open $fh, "<", \<<'EOC';
BEGIN {prepend_line_counting_filter};
pass("You should see this line thrice");
EOC

do [$fh, sub {$_ .= $_ . $_; return;}] or die;

do \"pass\n(\n'Scalar references are treated as initial file contents'\n)\n"
or die;

use constant scalarreffee =>
  "pass\n(\n'Scalar references are treated as initial file contents'\n)\n";
do \scalarreffee or die;
is scalarreffee,
  "pass\n(\n'Scalar references are treated as initial file contents'\n)\n",
  'and are not gobbled up when read-only';

{
    local $SIG{__WARN__} = sub {}; # ignore deprecation warning from ?...?
    do qr/a?, 1/;
    pass "No crash (perhaps) when regexp ref is returned from inc filter";
    # Even if that outputs "ok", it may not have passed, as the crash
    # occurs during globular destruction.  But the crash will result in
    # this script failing.
}

open $fh, "<", \"ss('The file is concatenated');";

do [\'pa', $fh] or die;

open $fh, "<", \"ff('Gur svygre vf bayl eha ba gur svyr');";

do [\'pa', $fh, sub {tr/A-Za-z/N-ZA-Mn-za-m/; return;}] or die;

open $fh, "<", \"SS('State also works');";

do [\'pa', $fh, sub {s/($_[1])/lc $1/ge; return;}, "S"] or die;

@lines = ('ss', '(', "'you can use a generator'", ')');

do [\'pa', \&generator] or die;

do [\'pa', \&generator_with_state,
    ["ss('And generators which take state');\n",
     "pass('And return multiple lines');\n",
    ]] or die;

@origlines = keys %{{ "1\n+\n2\n" => 1 }};
@lines = @origlines;
do \&generator or die;
is $origlines[0], "1\n+\n2\n", 'ink filters do not mangle cow buffers';

@lines = ('$::the_array = "', [], '"');
do \&generator or die;
like ${$::{the_array}}, qr/^ARRAY\(0x.*\)\z/,
   'setting $_ to ref in inc filter';
@lines = ('$::the_array = "', do { no warnings 'once'; *foo}, '"');
do \&generator or die;
is ${$::{the_array}}, "*main::foo", 'setting $_ to glob in inc filter';
@lines = (
    '$::the_array = "',
     do { no strict; no warnings; *{"foo\nbar"}},
    '"');
do \&generator or die;
is ${$::{the_array}}, "*main::foo\nbar",
    'setting $_ to multiline glob in inc filter';

sub TIESCALAR { bless \(my $thing = pop), shift }
sub FETCH {${$_[0]}}
my $done;
do sub {
    return 0 if $done;
    tie $_, "main", '$::the_scalar = 98732';
    return $done = 1;
} or die;
is ${$::{the_scalar}}, 98732, 'tying $_ in inc filter';
@lines = ('$::the_scalar', '= "12345"');
tie my $ret, "main", 1;
do sub :lvalue {
    return 0 unless @lines;
    $_ = shift @lines;
    return $ret;
} or die;
is ${$::{the_scalar}}, 12345, 'returning tied val from inc filter';


# d8723a6a74b2c12e wasn't perfect, as the char * returned by SvPV*() can be
# a temporary, freed at the next FREETMPS. And there is a FREETMPS in
# pp_require

for (0 .. 1) {
    # Need both alternatives on the regexp, because currently the logic in
    # pp_require for what is written to %INC is somewhat confused
    open $fh, "<",
	\'like(__FILE__, qr/(?:GLOB|CODE)\(0x[0-9a-f]+\)/, "__FILE__ is valid");';
    do $fh or die;
}

# [perl #91880] $_ having the wrong refcount inside a
{ #             filter sub
    local @INC; local $|;
    unshift @INC, sub { sub { undef *_; --$| }};
    do "dah";
    pass '$_ has the right refcount inside a filter sub';
}
