The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;

use Test::More tests => 14;
use Test::Exception;
use File::Temp qw(tempdir);
use Expect;

#$Expect::Exp_Internal = 1;
#$Expect::Debug = 1;

my $tempdir = tempdir( CLEANUP => 1 );
my $Perl = $^X;

subtest perl => sub {
	diag "Basic tests...";
	plan tests => 4;

	my $exp = Expect->spawn("$Perl -v");
	ok( defined $exp );
	$exp->log_user(0);
	is( $exp->expect( 10, "krzlbrtz",   "Copyright" ), 2 );
	is( $exp->expect( 10, "Larry Wall", "krzlbrtz" ),  1 );
	ok( not $exp->expect( 3, "Copyright" ) );
};


subtest exec_failure => sub {
	diag "Testing exec failure...";
	plan tests => 6;

	my $exp = Expect->new;
	ok( defined $exp );
	$exp->log_stdout(0);
	$! = 0;
	ok( not defined $exp->spawn("Ignore_This_Error_Its_A_Test__efluna3w6868tn8") );
	ok($!);
	my $val = '';
	my $res = $exp->expect(
		20,
		[ "Cannot exec" => sub { $val = 'cannot_exec'; } ],
		[ eof           => sub { $val = 'eof'; } ],
		[ timeout       => sub { $val = 'timeout'; } ],
	);
	is $val, 'cannot_exec';
	ok( defined $res );
	is( $res, 1 );
};


subtest exp_continue => sub {
	diag "Testing exp_continue...";
	plan tests => 1;

	my $exp   = Expect->new( $Perl . q{ -e 'foreach (qw(A B C D End)) { print "$_\n"; }' } );
	my $state = "A";
	my @val;
	$exp->expect(
		2,
		[   "[ABCD]" => sub {
				my $self = shift;
				push @val, $self->match;
				exp_continue;
			}
		],
		[ "End"   => sub { push @val, 'End'; } ],
		[ eof     => sub { push @val, 'eof'; } ],
		[ timeout => sub { push @val, 'timeout'; } ],
	);
	is_deeply \@val, [qw(A B C D End)], '5 states of exp_continue';
	$exp->hard_close();
};

subtest exp_continue_sleep => sub {
	plan tests => 5;

	my $exp = Expect->new( $Perl . q{ -e 'print "Begin\n"; sleep (5); print "End\n";' } );
	my $cnt = 0;
	my ( $begin, $end, $eof );
	$exp->expect(
		1,
		[ "Begin" => sub { $begin = 1; exp_continue; } ],
		[ "End"   => sub { $end   = 1; } ],
		[ eof     => sub { $eof   = 1; } ],
		[ timeout => sub { $cnt++; ( $cnt < 7 ) ? exp_continue : 0; } ],
	);
	ok $begin;
	ok $end;
	ok !$eof;
	diag "number of timeout calls in 5 sec: $cnt";
	cmp_ok( $cnt, '>', 2 );
	cmp_ok( $cnt, '<', 7 );
	$exp->hard_close();
};

subtest timeout => sub {
	diag "timeout shouldn't destroy accum contents";
	plan tests => 3;

	my $exp = Expect->new( $Perl . q{ -e 'print "some string\n"; sleep (5);' } );
	ok( not defined $exp->expect( 1, "NoMaTcH" ) );
	my $i = $exp->expect( 1, '-re', 'some\s' );
	ok( defined $i );
	is $i, 1;
	$exp->hard_close();
};


subtest notransfer => sub {
	diag "Testing -notransfer...";
	plan tests => 8;

	my $exp = Expect->new( $Perl . q{ -e 'print "X some other\n"; sleep 5;'} );
	$exp->notransfer(1);

	my @expected = ( 'some', 'some', 'other' );
	foreach my $e (@expected) {
		my $val = '';
		$exp->expect(
			3,
			[ $e      => sub { $val = $e; } ],
			[ eof     => sub { $val = 'eof'; } ],
			[ timeout => sub { $val = 'timeout'; } ],
		);
		is $val, $e;
	}

	sleep(6);
	my $val1 = '';
	my $acc1 = '';
	$exp->expect(
		3,
		[ 'some'  => sub { my $self = shift; $val1 = 'some'; $acc1 = $self->set_accum( $self->after() ); } ],
		[ eof     => sub { $val1 = 'eof'; } ],
		[ timeout => sub { $val1 = 'timeout'; } ],
	);
	like $acc1, qr/^X some other[\r\n]*$/, 'accumulator';
	is $val1, 'some';

	my $val2 = '';
	my $acc2 = '';
	$exp->expect(
		3,
		[ 'some'  => sub { $val2 = 'some'; } ],
		[ 'other' => sub { $val2 = 'other'; my $self = shift; my $acc2 = $self->set_accum( $self->after() ); } ],
		[ eof     => sub { $val2 = 'eof'; } ],
		[ timeout => sub { $val2 = 'timeout'; } ],
	);
	is $acc2, '', 'accumulator';
	is $val2, 'other';

	my $val3 = '';
	$exp->expect(
		3,
		[ "some"  => sub { $val3 = 'some'; } ],
		[ "other" => sub { $val3 = 'other'; } ],
		[ eof     => sub { $val3 = 'eof'; } ],
		[ timeout => sub { $val3 = 'timeout'; } ],
	);
	is $val3, 'eof';
};


subtest raw_reversing => sub {
	diag "Testing raw reversing...";
	plan tests => 11;

	my @Strings = (
		"The quick brown fox jumped over the lazy dog.",
		"Ein Neger mit Gazelle zagt im Regen nie",
		"Was ich brauche ist ein Lagertonnennotregal",
	);

	my $exp = Expect->new;

	#    my $exp = Expect->new("$Perl -MIO::File -ne 'BEGIN {\$|=1; \$in = IO::File->new( \">reverse.in\" ) or die; \$in->autoflush(1); \$out = IO::File->new( \">reverse.out\" ) or die; \$out->autoflush(1); } chomp; print \$in \"\$_\\n\"; \$_ = scalar reverse; print \"\$_\\n\"; print \$out \"\$_\\n\"; '");

	diag "isatty(\$exp): " . (POSIX::isatty($exp) ? "YES" : "NO");

	$exp->raw_pty(1);
	$exp->spawn(qq{$Perl -ne 'chomp; sleep 0; print scalar reverse, "\\n"'})
		or die "Cannot spawn $Perl: $!\n";
	my $called = 0;
	$exp->log_file( sub { $called++; } );
	foreach my $s (@Strings) {
		my $val = '';
		my $rev = scalar reverse $s;
		$exp->send("$s\n");
		$exp->expect(
			10,
			[ quotemeta($rev) => sub { $val = 'match'; } ],
			[ timeout => sub { $val = 'timeout' } ], # was die!
			[ eof     => sub { $val = 'eof'; } ],    # was die!
		);
		is $val, 'match', $s;
	}
	diag "Called: $called";
	cmp_ok $called, '>=', @Strings;
	$exp->log_file(undef);

	# now with send_slow
	$called = 0;
	$exp->log_file( sub { $called++; } );
	my $delay = 0.1;
	foreach my $s (@Strings) {
		my $rev = scalar reverse $s;
		my $now = time;
		$exp->send_slow( $delay, "$s\n" );
		my $val = '';
		$exp->expect(
			10,
			[ quotemeta($rev) => sub { $val = 'match'; } ],
			[ timeout => sub { $val = 'timeout'; } ],  # was die!
			[ eof     => sub { $val = 'eof'; } ],      # was die!
		);
		is $val, 'match', $s;
		my $dur = time + 1 - $now;
		my $delay_by_expect = length($s) * $delay;
		diag "Elapsed time: $dur  delay by expect: $delay_by_expect";
		# TODO: Without that +1 this test has randomly failed. (Is this a bug in Expect.pm or a bad expectation?)
		cmp_ok $dur, '>', $delay_by_expect;
	}
	diag "Called: $called";
	cmp_ok $called, '>=', @Strings;
	$exp->log_file(undef);
};

subtest system_dependent => sub {
	diag 'Check if the raw pty can handle large chunks of text at once';
	plan tests => 1;

	my $randstring =
		'fakjdf ijj845jtirg8e 4jy8 gfuoyhjgt8h gues9845th guoaeh gt98hae 45t8u ha8rhg ue4ht 8eh tgo8he4 t8 gfj aoingf9a8hgf uain dgkjadshftuehgfusand987vgh afugh 8h 98H 978H 7HG zG 86G (&g (O/g &(GF(/EG F78G F87SG F(/G F(/a sldjkf hajksdhf jkahsd fjkh asdHJKGDSGFKLZSTRJKSGOSJDFKGHSHGDFJGDSFJKHGSDFHJGSDKFJGSDGFSHJDGFljkhf lakjsdh fkjahs djfk hasjkdh fjklahs dfkjhasdjkf hajksdh fkjah sdjfk hasjkdh fkjashd fjkha sdjkfhehurthuerhtuwe htui eruth ZI AHD BIZA Di7GH )/g98 9 97 86tr(& TA&(t 6t &T 75r 5$R%/4r76 5&/% R79 5 )/&';

	my $exp = Expect->new;
	$exp->raw_pty(1);
	test_reverse($exp, $randstring, 160, 'raw');
};

# Now test for the max. line length. Some systems are limited to ~255
# chars per line, after which they start loosing characters.  As Cygwin
# then hangs and cannot be freed via alarm, we only test up to 160 characters
# to avoid that.

subtest max_line_length => sub {
	diag 'Check if the default pty can handle large chunks of text at once';
	plan tests => 1;

	my $randstring =
		'Fakjdf ijj845jtirg8 gfuoyhjgt8h gues9845th guoaeh gt9vgh afugh 8h 98H 97BH 7HG zG 86G (&g (O/g &(GF(/EG F78G F87SG F(/G F(/a slkf ksdheq@f jkahsd fjkh%&/"§ä#üßw';

	my $exp = Expect->new;
	test_reverse($exp, $randstring, 100, 'default');
};

sub test_reverse {
	my ($exp, $randstring, $min, $type) = @_;

	diag <<_EOT_;
------------------------------------------------------------------------------
  The following tests check system-dependend behaviour, so even if some fail,
  Expect might still be perfectly usable for you!
------------------------------------------------------------------------------
_EOT_

	$exp->spawn(qq{$Perl -ne 'chomp; sleep 0; print scalar reverse, "\\n"'})
		or die "Cannot spawn $Perl: $!\n";
	$SIG{ALRM} = sub { die "TIMEOUT on send" };

	$exp->log_stdout(0);
	$exp->log_file("$tempdir/test.log");
	diag 'Length: ' . length($randstring);
	my $status = '';
	my $maxlen = 0;
	my $exitloop;
	foreach my $len ( 1 .. length($randstring) ) {
		#print "$len\r";
		my $s = substr( $randstring, 0, $len );
		my $rev = scalar reverse $s;
		eval {
			alarm(10);
			$exp->send("$s\n");
			alarm(0);
		};
		if ($@) {
			#ok( $maxlen > 80 );
			diag "Warning: your default pty blocks when sending more than $maxlen bytes per line!";
			$status = 'block';
			$exitloop = 1;
			last;
		}
		$exp->expect(
			10,
			[ quotemeta($rev) => sub { $maxlen = $len; $status = 'match' } ],
			[ timeout => sub {
					diag "Warning: your $type pty can only handle $maxlen bytes at a time!\n";
					$status = 'limit';
					$exitloop = 1;
				}
			],
			[ eof => sub { $status = 'eof';} ],
		);
		last if $exitloop;
	}
	diag "Good, your $type pty can handle lines of at least " . length($randstring) . " bytes at a time."
		if not $exitloop;
	diag "Status: $status";
	cmp_ok $maxlen, '>', $min;
	$SIG{ALRM} = 'DEFAULT';

}

subtest controlling_termnal => sub {
	diag "Testing controlling terminal...";
	plan tests => 3;

	my $exp =
		Expect->new( $Perl
			. q{ -MIO::Handle -e 'open(TTY, "+>/dev/tty") or die "no controlling terminal"; autoflush TTY 1; print TTY "Expect_test_prompt: "; $s = <TTY>; chomp $s; print "uc: \U$s\n"; close TTY; exit 0;'}
		);

	my $pwd = "pAsswOrd";
	$exp->log_file("$tempdir/test_dev_tty.log");
	my $val = '';
	$exp->expect(
		10,
		[   qr/Expect_test_prompt:/,
			sub {
				my $self = shift;
				$self->send("$pwd\n");
				$exp->log_file(undef);
				exp_continue;
			}
		],
		[   qr/(?m:^uc:\s*(\w+))/,
			sub {
				my $self = shift;
				my ($s) = $self->matchlist;
				chomp $s;
				$val = $s;
			}
		],
		[ eof     => sub { $val = 'eof'; } ],
		[ timeout => sub { $val = 'timeout'; } ],
	);
	my $before = $exp->before;
	$before =~ s/[\r\n]*$//;
	is $before, " pAsswOrd", 'before';
	my $after = $exp->after;
	$after =~ s/[\r\n]*$//;
	is $after,  "", 'after';

	is $val, uc($pwd), 'uc';
};


subtest exit_status => sub {
	diag "Checking if exit status is returned correctly...";
	plan tests => 3;

	my $exp = Expect->new( $Perl . q{ -e 'print "Expect_test_pid: $$\n"; sleep 2; exit(42);'} );
	my $val = '';
	$exp->expect(
		10,
		[ qr/Expect_test_pid:/, sub { my $self = shift; $val = 'test_pid'; } ],
		[ eof     => sub { $val = "eof"; } ],
		[ timeout => sub { $val = "timeout"; } ],
	);
	is $val, 'test_pid';
	my $status = $exp->soft_close();
	diag sprintf "soft_close: 0x%04X\n", $status;
	is $exp->exitstatus(), $status;
	is( ( ( $status >> 8 ) & 0x7F ), 42);
};


subtest signal => sub {
	diag "Checking if signal exit status is returned correctly...";
	plan tests => 3;

	my $exp = Expect->new( $Perl . q{ -e 'print "Expect_test_pid: $$\n"; sleep 2; kill 15, $$;'} );
	my $val = '';
	$exp->expect(
		10,
		[ qr/Expect_test_pid:/, sub { my $self = shift; $val = 'test_pid'; } ],
		[ eof     => sub { $val = "eof"; } ],
		[ timeout => sub { $val = "timeout"; } ],
	);
	is $val, 'test_pid';
	my $status = $exp->soft_close();
	diag sprintf "soft_close: 0x%04X", $status;
	ok( $exp->exitstatus() == $status );
	my ( $hi, $lo ) = ( ( $status >> 8 ) & 0x7F, $status & 0x7F );

	ok( $hi == 15 or $lo == 15 );
};

diag <<__EOT__;

Checking if EOF on pty slave is correctly reported to master...
(this fails on about 50% of the supported systems, so don't panic!
 Expect will work anyway!)

__EOT__

subtest eof_on_pty => sub {
	plan tests => 1;

	my $exp = Expect->new( $Perl . q{ -e 'close STDIN; close STDOUT; close STDERR; sleep 4;'} );
	my $res;
	$exp->expect(
		2,
		[ eof     => sub { $res = 'eof' } ],
		[ timeout => sub { $res = 'timeout' } ],
	);

	# on OSX it seems that when $Config{osvers} < 13 it returns eof  and when osvers is >= 13 then we get timeout
	# http://www.cpantesters.org/distro/E/Expect.html?oncpan=1&distmat=1&version=1.29
	# at least when we sleep 3 and wait for 2
	my $expected = 'timeout';
	if ($^O eq 'freebsd') {
		$expected = 'eof';
	}
	is $res, $expected, "Sorry, you may not notice if the spawned process closes the pty. ($expected)";
	$exp->hard_close();
};

subtest respawn => sub {
	plan tests => 1;

	my $exp = Expect->new;
	$exp->spawn( $Perl . q{ -e 'print "42\n"'} );
	throws_ok { $exp->spawn( $Perl . q{ -e 'print "23\n"'} ) } qr/^Cannot reuse an object with an already spawned command/;
};


use Test::Builder;
my $Test = Test::Builder->new;
diag <<__EOT__ if ( not $Test->is_passing );
Please scroll back and check which test(s) failed and what comments
were given.  Expect probably is still completely usable!!
__EOT__

exit(0);