The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package t::lib::PPI;

use warnings;
use strict;

use File::Spec::Functions ':ALL';
use Test::More;
use Test::Object;
use Params::Util qw{_STRING _INSTANCE};
use List::MoreUtils 'any';
use PPI::Dumper;

use vars qw{$VERSION};
BEGIN {
	$VERSION = '1.220';
}





#####################################################################
# PPI::Document Testing

Test::Object->register(
	class => 'PPI::Document',
	tests => 1,
	code  => \&document_ok,
);

sub document_ok {
	my $doc = shift;

	# A document should have zero or more children that are either
	# a statement or a non-significant child.
	my @children = $doc->children;
	my $good = grep {
		_INSTANCE($_, 'PPI::Statement')
		or (
			_INSTANCE($_, 'PPI::Token') and ! $_->significant
			)
		} @children;

	is( $good, scalar(@children),
		'Document contains only statements and non-significant tokens' );

	1;
}





#####################################################################
# Are there an unknowns

Test::Object->register(
	class => 'PPI::Document',
	tests => 3,
	code  => \&unknown_objects,
);

sub unknown_objects {
	my $doc = shift;

	is(
		$doc->find_any('Token::Unknown'),
		'',
		"Contains no PPI::Token::Unknown elements",
	);
	is(
		$doc->find_any('Structure::Unknown'),
		'',
		"Contains no PPI::Structure::Unknown elements",
	);
	is(
		$doc->find_any('Statement::Unknown'),
		'',
		"Contains no PPI::Statement::Unknown elements",
	);

	1;
}





#####################################################################
# Are there any invalid nestings?

Test::Object->register(
	class => 'PPI::Document',
	tests => 1,
	code  => \&nested_statements,
);

sub nested_statements {
	my $doc = shift;

	ok(
		! $doc->find_any( sub {
			_INSTANCE($_[1], 'PPI::Statement')
			and
			any { _INSTANCE($_, 'PPI::Statement') } $_[1]->children
		} ),
		'Document contains no nested statements',
	);	
}

Test::Object->register(
	class => 'PPI::Document',
	tests => 1,
	code  => \&nested_structures,
);

sub nested_structures {
	my $doc = shift;

	ok(
		! $doc->find_any( sub {
			_INSTANCE($_[1], 'PPI::Structure')
			and
			any { _INSTANCE($_, 'PPI::Structure') } $_[1]->children
		} ),
		'Document contains no nested structures',
	);
}

Test::Object->register(
	class => 'PPI::Document',
	tests => 1,
	code  => \&no_attribute_in_attribute,
);

sub no_attribute_in_attribute {
	my $doc = shift;

	ok(
		! $doc->find_any( sub {
			_INSTANCE($_[1], 'PPI::Token::Attribute')
			and
			! exists $_[1]->{_attribute}
		} ),
		'No ->{_attribute} in PPI::Token::Attributes',
	);
}





#####################################################################
# PPI::Statement Tests

Test::Object->register(
	class => 'PPI::Document',
	tests => 1,
	code  => \&valid_compound_type,
);

sub valid_compound_type {
	my $document = shift;
	my $compound = $document->find('PPI::Statement::Compound') || [];
	is(
		scalar( grep { not defined $_->type } @$compound ),
		0, 'All compound statements have defined ->type',
	);
}





#####################################################################
# Does ->location work properly
# As an aside, fixes #23788: PPI::Statement::location() returns undef for C<({})>.

Test::Object->register(
	class => 'PPI::Document',
	tests => 1,
	code   => \&defined_location,
);

sub defined_location {
	my $document = shift;
	my $bad      = $document->find( sub {
		not defined $_[1]->location
	} );
	is( $bad, '', '->location always defined' );
}





#####################################################################
# Process a .code/.dump file pair
# plan: 2 + 14 * npairs

sub run_testdir {
	my $pkg     = shift;
	my $testdir = catdir(@_);

	# Does the test directory exist?
	ok( (-e $testdir and -d $testdir and -r $testdir), "Test directory $testdir found" );

	# Find the .code test files
        local *TESTDIR;
	opendir( TESTDIR, $testdir ) or die "opendir: $!";
	my @code = map { catfile( $testdir, $_ ) } sort grep { /\.code$/ } readdir(TESTDIR);
	closedir( TESTDIR ) or die "closedir: $!";
	ok( scalar @code, 'Found at least one code file' );

	foreach my $codefile ( @code ) {
		# Does the .code file have a matching .dump file
		my $dumpfile = $codefile;
		$dumpfile =~ s/\.code$/\.dump/;
		my $codename = $codefile;
		$codename =~ s/\.code$//;
		ok( (-f $dumpfile and -r $dumpfile), "$codename: Found matching .dump file" );

		# Create the lexer and get the Document object
		my $document = PPI::Document->new( $codefile );
		ok( $document, "$codename: Lexer->Document returns true" );
		ok( _INSTANCE($document, 'PPI::Document'), "$codename: Object isa PPI::Document" );

		my $rv;
		local *CODEFILE;
		SKIP: {
			skip "No Document to test", 12 unless $document;

			# Index locations
			ok( $document->index_locations, "$codename: ->index_locations ok" );

			# Check standard things
			object_ok( $document ); # 7 tests contained within

			# Get the dump array ref for the Document object
			my $Dumper = PPI::Dumper->new( $document );
			ok( _INSTANCE($Dumper, 'PPI::Dumper'), "$codename: Object isa PPI::Dumper" );
			my @dump_list = $Dumper->list;
			ok( scalar @dump_list, "$codename: Got dump content from dumper" );

			# Try to get the .dump file array
			local *DUMP;
			open( DUMP, '<', $dumpfile ) or die "open: $!";
			my @content = <DUMP>;
			close( DUMP ) or die "close: $!";
			chomp @content;

			# Compare the two
			is_deeply( \@dump_list, \@content, "$codename: Generated dump matches stored dump" );

			# Also, do a round-trip check
			$rv = open( CODEFILE, '<', $codefile );
			ok( $rv, "$codename: Opened file" );
		}
		SKIP: {
			unless ( $document and $rv ) {
				skip "Missing file", 1;
			}
			my $source = do { local $/ = undef; <CODEFILE> };
			close CODEFILE;
			$source =~ s/(?:\015{1,2}\012|\015|\012)/\n/g;

			is( $document->serialize, $source, "$codename: Round-trip back to source was ok" );
		}
	}
}






#####################################################################
# Process a .code/.dump file pair
# plan: 2 + 14 * npairs

sub increment_testdir {
	my $pkg     = shift;
	my $testdir = catdir(@_);

	# Does the test directory exist?
	ok( (-e $testdir and -d $testdir and -r $testdir), "Test directory $testdir found" );

	# Find the .code test files
        local *TESTDIR;
	opendir( TESTDIR, $testdir ) or die "opendir: $!";
	my @code = map { catfile( $testdir, $_ ) } sort grep { /\.code$/ } readdir(TESTDIR);
	closedir( TESTDIR ) or die "closedir: $!";
	ok( scalar @code, 'Found at least one code file' );

	foreach my $codefile ( @code ) {
		# Does the .code file have a matching .dump file
		my $codename = $codefile;
		$codename =~ s/\.code$//;

		# Load the file
		local *CODEFILE;
		local $/ = undef;
		open( CODEFILE, '<', $codefile ) or die "open: $!";
		my $buffer = <CODEFILE>;
		close( CODEFILE ) or die "close: $!";

		# Cover every possible transitional state in
		# the regression test code fragments.
		foreach my $chars ( 1 .. length($buffer) ) {
			my $string   = substr( $buffer, 0, $chars );
			my $document = eval {
				PPI::Document->new( \$string );
			};
			is(
				$@ => '',
				"$codename: $chars chars ok",
			);
			is(
				ref($document) => 'PPI::Document',
				"$codename: $chars chars document",
			);
			is(
				$document->serialize => $string,
				"$codename: $chars char roundtrip",
			);
		}
	}
}

1;