diff --git a/Changes b/Changes index 12c17f8a..0a5fd900 100644 --- a/Changes +++ b/Changes @@ -1,4 +1,25 @@ Revision history for Perl extension PPI + - Fix 1.218 regression where packages, subs, and words after + labels like /^x\d+/ would parse as x operator (GitHub #122) + (MOREGAN) + +1.221_01 + Summary: + - support Perl 5.12 "package NAMESPACE VERSION BLOCK" syntax + + Details: + - support Perl 5.12 "package NAMESPACE VERSION BLOCK" syntax + (RT #67831, GitHub #70) (BDFOY, MOREGAN) + - Prevent package names like 'x' from being parsed as operators + (GitHub #75) (MOREGAN) + - Prevent package names like 'v10' from being parsed as version + strings (GitHub #76) (MOREGAN) + - Prevent sub names like 'v10' from being parsed as version + strings (RT #74527, GitHub #65) (JAE, MOREGAN) + - Prevent 'use' and 'no' package names from being parsed as + operators (MOREGAN) + - Prevent left side of fat comma from parsing as operator + operators (MOREGAN) 1.220 Tue 11 Nov 2014 Summary: diff --git a/Makefile.PL b/Makefile.PL index ce565279..2f1a6a8a 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -40,6 +40,7 @@ test_requires 'Test::More' => '0.86'; test_requires 'Test::NoWarnings' => '0.084'; test_requires 'Test::Object' => '0.07'; test_requires 'Test::SubCalls' => '1.07'; +test_requires 'Test::Deep'; # Force the existence of the weaken function # (which some distributions annoyingly don't have) diff --git a/lib/PPI/Cache.pm b/lib/PPI/Cache.pm index fef77de0..53a6a56f 100644 --- a/lib/PPI/Cache.pm +++ b/lib/PPI/Cache.pm @@ -268,7 +268,7 @@ sub _md5hex { my $it = _SCALAR($_[0]) ? PPI::Util::md5hex(${$_[0]}) : $_[0]; - return (defined $it and ! ref $it and $it =~ /^[a-f0-9]{32}\z/si) + return (defined $it and ! ref $it and $it =~ /^[[:xdigit:]]{32}\z/s) ? lc $it : undef; } diff --git a/lib/PPI/Lexer.pm b/lib/PPI/Lexer.pm index 9105c2d9..76be197b 100644 --- a/lib/PPI/Lexer.pm +++ b/lib/PPI/Lexer.pm @@ -440,6 +440,28 @@ sub _statement { # Is it a token in our known classes list my $class = $STATEMENT_CLASSES{$Token->content}; + if ( $class ) { + # Is the next significant token a => + # Read ahead to the next significant token + my $Next; + while ( $Next = $self->_get_token ) { + if ( !$Next->significant ) { + push @{$self->{delayed}}, $Next; + next; + } + + last if + !$Next->isa( 'PPI::Token::Operator' ) or $Next->content ne '=>'; + + # Got the next token + # Is an ordinary expression + $self->_rollback( $Next ); + return 'PPI::Statement'; + } + + # Rollback and continue + $self->_rollback( $Next ); + } # Handle potential barewords for subscripts if ( $Parent->isa('PPI::Structure::Subscript') ) { @@ -533,8 +555,16 @@ sub _statement { } # Found the next significant token. + if ( + $Next->isa('PPI::Token::Operator') + and + $Next->content eq '=>' + ) { + # Is an ordinary expression + $self->_rollback( $Next ); + return 'PPI::Statement'; # Is it a v6 use? - if ( $Next->content eq 'v6' ) { + } elsif ( $Next->content eq 'v6' ) { $self->_rollback( $Next ); return 'PPI::Statement::Include::Perl6'; } else { @@ -696,22 +726,23 @@ sub _continues { return ''; } - # Alrighty then, there are only five implied end statement types, - # ::Scheduled blocks, ::Sub declarations, ::Compound, ::Given, and ::When - # statements. - unless ( ref($Statement) =~ /\b(?:Scheduled|Sub|Compound|Given|When)$/ ) { - return 1; - } - - # Of these five, ::Scheduled, ::Sub, ::Given, and ::When follow the same - # simple rule and can be handled first. + # Alrighty then, there are six implied-end statement types: + # ::Scheduled blocks, ::Sub declarations, ::Compound, ::Given, ::When, + # and ::Package statements. + return 1 + if ref $Statement !~ /\b(?:Scheduled|Sub|Compound|Given|When|Package)$/; + + # Of these six, ::Scheduled, ::Sub, ::Given, and ::When follow the same + # simple rule and can be handled first. The block form of ::Package + # follows the rule, too. (The non-block form of ::Package + # requires a statement terminator, and thus doesn't need to have + # an implied end detected.) my @part = $Statement->schildren; my $LastChild = $part[-1]; - unless ( $Statement->isa('PPI::Statement::Compound') ) { - # If the last significant element of the statement is a block, - # then a scheduled statement is done, no questions asked. - return ! $LastChild->isa('PPI::Structure::Block'); - } + # If the last significant element of the statement is a block, + # then an implied-end statement is done, no questions asked. + return !$LastChild->isa('PPI::Structure::Block') + if !$Statement->isa('PPI::Statement::Compound'); # Now we get to compound statements, which kind of suck (to lex). # However, of them all, the 'if' type, which includes unless, are @@ -1118,6 +1149,16 @@ sub _curly { and return 'PPI::Structure::Subscript'; } } + + # Are we the last argument of sub? + # E.g.: 'sub foo {}', 'sub foo ($) {}' + return 'PPI::Structure::Block' if $Parent->isa('PPI::Statement::Sub'); + + # Are we the second or third argument of package? + # E.g.: 'package Foo {}' or 'package Foo v1.2.3 {}' + return 'PPI::Structure::Block' + if $Parent->isa('PPI::Statement::Package'); + if ( $CURLY_CLASSES{$content} ) { # Known type return $CURLY_CLASSES{$content}; diff --git a/lib/PPI/Statement/Package.pm b/lib/PPI/Statement/Package.pm index 52006ec8..19945e7e 100644 --- a/lib/PPI/Statement/Package.pm +++ b/lib/PPI/Statement/Package.pm @@ -47,6 +47,9 @@ BEGIN { @ISA = 'PPI::Statement'; } +# Lexer clues +sub __LEXER__normal() { '' } + =pod =head2 namespace diff --git a/lib/PPI/Statement/Sub.pm b/lib/PPI/Statement/Sub.pm index 4722e799..8c5ebd8e 100644 --- a/lib/PPI/Statement/Sub.pm +++ b/lib/PPI/Statement/Sub.pm @@ -165,6 +165,10 @@ Returns true if it is a special reserved subroutine, or false if not. sub reserved { my $self = shift; my $name = $self->name or return ''; + # perlsub is silent on whether reserveds can contain: + # - underscores; + # we allow them due to existing practice like CLONE_SKIP and __SUB__. + # - numbers; we allow them by PPI tradition. $name eq uc $name; } @@ -172,10 +176,6 @@ sub reserved { =pod -=head1 TO DO - -- Write unit tests for this package - =head1 SUPPORT See the L in the main module. diff --git a/lib/PPI/Token.pm b/lib/PPI/Token.pm index 81660f91..030827e3 100644 --- a/lib/PPI/Token.pm +++ b/lib/PPI/Token.pm @@ -172,9 +172,9 @@ sub content { sub insert_before { my $self = shift; my $Element = _INSTANCE(shift, 'PPI::Element') or return undef; - if ( $Element->isa('PPI::Structure') ) { - return $self->__insert_before($Element); - } elsif ( $Element->isa('PPI::Token') ) { + if ( $Element->isa('PPI::Statement') + || $Element->isa('PPI::Structure') + || $Element->isa('PPI::Token') ) { return $self->__insert_before($Element); } ''; @@ -184,9 +184,9 @@ sub insert_before { sub insert_after { my $self = shift; my $Element = _INSTANCE(shift, 'PPI::Element') or return undef; - if ( $Element->isa('PPI::Structure') ) { - return $self->__insert_after($Element); - } elsif ( $Element->isa('PPI::Token') ) { + if ( $Element->isa('PPI::Statement') + || $Element->isa('PPI::Structure') + || $Element->isa('PPI::Token') ) { return $self->__insert_after($Element); } ''; diff --git a/lib/PPI/Token/HereDoc.pm b/lib/PPI/Token/HereDoc.pm index 7b351e7c..b9bab290 100644 --- a/lib/PPI/Token/HereDoc.pm +++ b/lib/PPI/Token/HereDoc.pm @@ -201,14 +201,10 @@ sub __TOKENIZER__on_char { return undef; } - # Define $line outside of the loop, so that if we encounter the - # end of the file, we have access to the last line still. - my $line; - # Suck in the HEREDOC - $token->{_heredoc} = []; + $token->{_heredoc} = \my @heredoc; my $terminator = $token->{_terminator} . "\n"; - while ( defined($line = $t->_get_line) ) { + while ( defined( my $line = $t->_get_line ) ) { if ( $line eq $terminator ) { # Keep the actual termination line for consistency # when we are re-assembling the file @@ -219,29 +215,30 @@ sub __TOKENIZER__on_char { } # Add the line - push @{$token->{_heredoc}}, $line; + push @heredoc, $line; } # End of file. # Error: Didn't reach end of here-doc before end of file. - # $line might be undef if we get NO lines. - if ( defined $line and $line eq $token->{_terminator} ) { - # If the last line matches the terminator - # but is missing the newline, we want to allow - # it anyway (like perl itself does). In this case - # perl would normally throw a warning, but we will - # also ignore that as well. - pop @{$token->{_heredoc}}; - $token->{_terminator_line} = $line; - } else { - # The HereDoc was not properly terminated. - $token->{_terminator_line} = undef; - # Trim off the trailing whitespace - if ( defined $token->{_heredoc}->[-1] and $t->{source_eof_chop} ) { - chop $token->{_heredoc}->[-1]; + # If the here-doc block is not empty, look at the last line to determine if + # the here-doc terminator is missing a newline (which Perl would fail to + # compile but is easy to detect) or if the here-doc block was just not + # terminated at all (which Perl would fail to compile as well). + $token->{_terminator_line} = undef; + if ( @heredoc and defined $heredoc[-1] ) { + # See PPI::Tokenizer, the algorithm there adds a space at the end of the + # document that we need to make sure we remove. + if ( $t->{source_eof_chop} ) { + chop $heredoc[-1]; $t->{source_eof_chop} = ''; } + + # Check if the last line of the file matches the terminator without + # newline at the end. If so, remove it from the content and set it as + # the terminator line. + $token->{_terminator_line} = pop @heredoc + if $heredoc[-1] eq $token->{_terminator}; } # Set a hint for PPI::Document->serialize so it can diff --git a/lib/PPI/Token/Number/Hex.pm b/lib/PPI/Token/Number/Hex.pm index f7f59745..5237382d 100644 --- a/lib/PPI/Token/Number/Hex.pm +++ b/lib/PPI/Token/Number/Hex.pm @@ -76,7 +76,7 @@ sub __TOKENIZER__on_char { # Allow underscores straight through return 1 if $char eq '_'; - if ( $char =~ /[\da-f]/i ) { + if ( $char =~ /[[:xdigit:]]/ ) { return 1; } diff --git a/lib/PPI/Token/Number/Version.pm b/lib/PPI/Token/Number/Version.pm index fefc0090..daf69cda 100644 --- a/lib/PPI/Token/Number/Version.pm +++ b/lib/PPI/Token/Number/Version.pm @@ -102,17 +102,23 @@ sub __TOKENIZER__on_char { sub __TOKENIZER__commit { my $t = $_[1]; - # Get the rest of the line + # Capture the rest of the token pos $t->{line} = $t->{line_cursor}; - if ( $t->{line} !~ m/\G(v\d+(?:\.\d+)*)/gc ) { + if ( $t->{line} !~ m/\G(v\d+(?:\.\d+)+|v\d+\b)/gc ) { # This was not a v-string after all (it's a word) return PPI::Token::Word->__TOKENIZER__commit($t); } + my $content = $1; + + # If there are no periods this could be a word starting with v\d + # Forced to be a word. Done. + return PPI::Token::Word->__TOKENIZER__commit($t) + if $content !~ /\./ and $t->__current_token_is_forced_word; + # This is a v-string - my $vstring = $1; - $t->{line_cursor} += length($vstring); - $t->_new_token('Number::Version', $vstring); + $t->{line_cursor} += length $content; + $t->_new_token( 'Number::Version', $content ); $t->_finalize_token->__TOKENIZER__on_char($t); } diff --git a/lib/PPI/Token/QuoteLike/Words.pm b/lib/PPI/Token/QuoteLike/Words.pm index 7e15fd9b..2930580f 100644 --- a/lib/PPI/Token/QuoteLike/Words.pm +++ b/lib/PPI/Token/QuoteLike/Words.pm @@ -42,20 +42,27 @@ BEGIN { =head2 literal -Returns the words contained. Note that this method does not check the +Returns the words contained as a list. Note that this method does not check the context that the token is in; it always returns the list and not merely the last element if the token is in scalar context. =cut sub literal { - my $self = shift; - my $section = $self->{sections}->[0]; - return split ' ', substr( - $self->{content}, - $section->{position}, - $section->{size}, - ); + my ( $self ) = @_; + + my $content = $self->_section_content(0); + return if !defined $content; + + # Undo backslash escaping of '\', the left delimiter, + # and the right delimiter. The right delimiter will + # only exist with paired delimiters: qw() qw[] qw<> qw{}. + my ( $left, $right ) = ( $self->_delimiters, '', '' ); + $content =~ s/\\([\Q$left$right\\\E])/$1/g; + + my @words = split ' ', $content; + + return @words; } 1; diff --git a/lib/PPI/Token/Unknown.pm b/lib/PPI/Token/Unknown.pm index a0bae548..cd6deeaa 100644 --- a/lib/PPI/Token/Unknown.pm +++ b/lib/PPI/Token/Unknown.pm @@ -90,7 +90,7 @@ sub __TOKENIZER__on_char { } elsif ( $p0->isa('PPI::Token::Structure') and - $p0->content =~ /^(?:\)|\])$/ + $p0->content =~ /^(?:\)|\]|\})$/ ) { $_class = 'Operator'; } else { diff --git a/lib/PPI/Token/Whitespace.pm b/lib/PPI/Token/Whitespace.pm index 26e76aab..f73b708c 100644 --- a/lib/PPI/Token/Whitespace.pm +++ b/lib/PPI/Token/Whitespace.pm @@ -203,7 +203,8 @@ sub __TOKENIZER__on_line_start { sub __TOKENIZER__on_char { my $t = $_[1]; - my $char = ord substr $t->{line}, $t->{line_cursor}, 1; + my $c = substr $t->{line}, $t->{line_cursor}, 1; + my $char = ord $c; # Do we definitely know what something is? return $COMMITMAP[$char]->__TOKENIZER__commit($t) if $COMMITMAP[$char]; @@ -407,9 +408,9 @@ sub __TOKENIZER__on_char { } } elsif ( $char >= 128 ) { # Outside ASCII - return 'PPI::Token::Word'->__TOKENIZER__commit($t) if $t =~ /\w/; - return 'Whitespace' if $t =~ /\s/; - } + return 'PPI::Token::Word'->__TOKENIZER__commit($t) if $c =~ /\w/; + return 'Whitespace' if $c =~ /\s/; + } # All the whitespaces are covered, so what to do diff --git a/lib/PPI/Token/Word.pm b/lib/PPI/Token/Word.pm index 95c62eaa..56607a2c 100644 --- a/lib/PPI/Token/Word.pm +++ b/lib/PPI/Token/Word.pm @@ -188,17 +188,25 @@ sub __TOKENIZER__on_char { return $t->{class}->__TOKENIZER__commit( $t ); } - # Check for a quote like operator my $word = $t->{token}->{content}; - if ( $QUOTELIKE{$word} and ! $class->__TOKENIZER__literal($t, $word, $tokens) ) { - $t->{class} = $t->{token}->set_class( $QUOTELIKE{$word} ); - return $t->{class}->__TOKENIZER__on_char( $t ); - } + if ( $KEYWORDS{$word} ) { + # Check for a Perl keyword that is forced to be a normal word instead + if ( $t->__current_token_is_forced_word ) { + $t->{class} = $t->{token}->set_class( 'Word' ); + return $t->{class}->__TOKENIZER__on_char( $t ); + } - # Or one of the word operators - if ( $OPERATOR{$word} and ! $class->__TOKENIZER__literal($t, $word, $tokens) ) { - $t->{class} = $t->{token}->set_class( 'Operator' ); - return $t->_finalize_token->__TOKENIZER__on_char( $t ); + # Check for a quote like operator. %QUOTELIKE must be subset of %KEYWORDS + if ( $QUOTELIKE{$word} ) { + $t->{class} = $t->{token}->set_class( $QUOTELIKE{$word} ); + return $t->{class}->__TOKENIZER__on_char( $t ); + } + + # Or one of the word operators. %OPERATOR must be subset of %KEYWORDS + if ( $OPERATOR{$word} ) { + $t->{class} = $t->{token}->set_class( 'Operator' ); + return $t->_finalize_token->__TOKENIZER__on_char( $t ); + } } # Unless this is a simple identifier, at this point @@ -313,7 +321,7 @@ sub __TOKENIZER__commit { # Since its not a simple identifier... $token_class = 'Word'; - } elsif ( $class->__TOKENIZER__literal($t, $word, $tokens) ) { + } elsif ( $KEYWORDS{$word} and $t->__current_token_is_forced_word ) { $token_class = 'Word'; } elsif ( $QUOTELIKE{$word} ) { @@ -363,46 +371,6 @@ sub __TOKENIZER__commit { $t->_finalize_token->__TOKENIZER__on_char($t); } -# Is the word in a "forced" context, and thus cannot be either an -# operator or a quote-like thing. This version is only useful -# during tokenization. -sub __TOKENIZER__literal { - my ($class, $t, $word, $tokens) = @_; - - # Is this a forced-word context? - # i.e. Would normally be seen as an operator. - unless ( $QUOTELIKE{$word} or $PPI::Token::Operator::OPERATOR{$word} ) { - return ''; - } - - # Check the cases when we have previous tokens - pos $t->{line} = $t->{line_cursor}; - if ( $tokens ) { - my $token = $tokens->[0] or return ''; - - # We are forced if we are a method name - return 1 if $token->{content} eq '->'; - - # We are forced if we are a sub name - return 1 if $token->isa('PPI::Token::Word') && $token->{content} eq 'sub'; - - # If we are contained in a pair of curly braces, - # we are probably a bareword hash key - if ( $token->{content} eq '{' and $t->{line} =~ /\G\s*\}/gc ) { - return 1; - } - } - - # In addition, if the word is followed by => it is probably - # also actually a word and not a regex. - if ( $t->{line} =~ /\G\s*=>/gc ) { - return 1; - } - - # Otherwise we probably aren't forced - ''; -} - 1; =pod diff --git a/lib/PPI/Token/_QuoteEngine/Full.pm b/lib/PPI/Token/_QuoteEngine/Full.pm index 85d23717..48dd78f4 100644 --- a/lib/PPI/Token/_QuoteEngine/Full.pm +++ b/lib/PPI/Token/_QuoteEngine/Full.pm @@ -32,7 +32,7 @@ BEGIN { 's' => { operator => 's', braced => undef, separator => undef, _sections => 2, modifiers => 1 }, 'tr' => { operator => 'tr', braced => undef, separator => undef, _sections => 2, modifiers => 1 }, - # Y is the little used variant of tr + # Y is the little-used variant of tr 'y' => { operator => 'y', braced => undef, separator => undef, _sections => 2, modifiers => 1 }, '/' => { operator => undef, braced => 0, separator => '/', _sections => 1, modifiers => 1 }, diff --git a/lib/PPI/Tokenizer.pm b/lib/PPI/Tokenizer.pm index 613ecfca..66fce488 100644 --- a/lib/PPI/Tokenizer.pm +++ b/lib/PPI/Tokenizer.pm @@ -102,7 +102,35 @@ my %X_CAN_FOLLOW_OPERATOR = map { $_ => 1 } qw( -- ++ ); # These are the exceptions. my %X_CAN_FOLLOW_STRUCTURE = map { $_ => 1 } qw( } ] \) ); - +# Something that looks like the x operator but follows a word +# is usually that word's argument. +# These are the exceptions. +# chop, chomp, dump are ambiguous because they can have either parms +# or no parms. +my %X_CAN_FOLLOW_WORD = map { $_ => 1 } qw( + endgrent + endhostent + endnetent + endprotoent + endpwent + endservent + fork + getgrent + gethostent + getlogin + getnetent + getppid + getprotoent + getpwent + getservent + setgrent + setpwent + time + times + wait + wantarray + __SUB__ +); @@ -552,7 +580,7 @@ sub _process_next_char { return 0 if ++$self->{line_cursor} >= $self->{line_length}; # Pass control to the token class - my $result; + my $result; unless ( $result = $self->{class}->__TOKENIZER__on_char( $self ) ) { # undef is error. 0 is "Did stuff ourself, you don't have to do anything" return defined $result ? 1 : undef; @@ -736,7 +764,10 @@ my %OBVIOUS_CONTENT = ( '}' => 'operator', ); -# Try to determine operator/operand context, is possible. + +my %USUALLY_FORCES = map { $_ => 1 } qw( sub package use no ); + +# Try to determine operator/operand context, if possible. # Returns "operator", "operand", or "" if unknown. sub _opcontext { my $self = shift; @@ -768,9 +799,57 @@ sub _current_x_is_operator { $prev && (!$prev->isa('PPI::Token::Operator') || $X_CAN_FOLLOW_OPERATOR{$prev}) && (!$prev->isa('PPI::Token::Structure') || $X_CAN_FOLLOW_STRUCTURE{$prev}) + && (!$prev->isa('PPI::Token::Word') || $X_CAN_FOLLOW_WORD{$prev}) + && !$prev->isa('PPI::Token::Label') ; } + +# Assuming we are at the end of parsing the current token that could be a word, +# a wordlike operator, or a version string, try to determine whether context +# before or after it forces it to be a bareword. This method is only useful +# during tokenization. +sub __current_token_is_forced_word { + my ( $t ) = @_; + + # Check if forced by preceding tokens. + + my ( $prev, $prevprev ) = @{ $t->_previous_significant_tokens(2) }; + if ( !$prev ) { + pos $t->{line} = $t->{line_cursor}; + } + else { + my $content = $prev->{content}; + + # We are forced if we are a method name. + # '->' will always be an operator, so we don't check its type. + return 1 if $content eq '->'; + + # If we are contained in a pair of curly braces, we are probably a + # forced bareword hash key. '{' is never a word or operator, so we + # don't check its type. + pos $t->{line} = $t->{line_cursor}; + return 1 if $content eq '{' and $t->{line} =~ /\G\s*\}/gc; + + # sub, package, use, and no all indicate that what immediately follows + # is a word not an operator or (in the case of sub and package) a + # version string. However, we don't want to be fooled by 'package + # package v10' or 'use no v10'. We're a forced package unless we're + # preceded by 'package sub', in which case we're a version string. + return ( !$prevprev || !$USUALLY_FORCES{$prevprev->content} ) + if $USUALLY_FORCES{$content}; + } + # pos on $t->{line} is guaranteed to be set at this point. + + # Check if forced by following tokens. + + # If the word is followed by => it is probably a word, not a regex. + return 1 if $t->{line} =~ /\G\s*=>/gc; + + # Otherwise we probably aren't forced + return ''; +} + 1; =pod diff --git a/t/01_compile.t b/t/01_compile.t index 507831fe..66f87e05 100644 --- a/t/01_compile.t +++ b/t/01_compile.t @@ -1,29 +1,12 @@ #!/usr/bin/perl -# Formal testing for PPI - # This test script only tests that the tree compiles -use strict; -use File::Spec::Functions ':ALL'; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - -use Test::More tests => 19; -use Test::NoWarnings; - - - - +use t::lib::PPI::Test::pragmas; +use Test::More tests => 18; -# Check their perl version -ok( $] >= 5.006, "Your perl is new enough" ); -# Does the module load +# Do the modules load use_all_ok( qw{ PPI PPI::Tokenizer diff --git a/t/03_document.t b/t/03_document.t index 0ad563a1..14e6da19 100644 --- a/t/03_document.t +++ b/t/03_document.t @@ -2,47 +2,32 @@ # PPI::Document tests -use strict; -use File::Spec::Functions ':ALL'; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} -use PPI; - -# Execute the tests +use t::lib::PPI::Test::pragmas; use Test::More tests => 14; -use Test::NoWarnings; - -# Test file -my $file = catfile(qw{ t data 03_document test.dat }); -my $empty = catfile(qw{ t data 03_document empty.dat }); -ok( -f $file, 'Found test file' ); -ok( -f $empty, 'Found test file' ); - -# Test script -my $script = <<'END_PERL'; -#!/usr/bin/perl - -# A simple test script - -print "Hello World!\n"; -END_PERL - - +use File::Spec::Functions ':ALL'; +use PPI; ##################################################################### # Test a basic document # Parse a simple document in all possible ways -SCOPE: { +NEW: { + my $file = catfile(qw{ t data 03_document test.dat }); + ok( -f $file, 'Found test.dat' ); + my $doc1 = PPI::Document->new( $file ); isa_ok( $doc1, 'PPI::Document' ); + # Test script + my $script = <<'END_PERL'; +#!/usr/bin/perl + +# A simple test script + +print "Hello World!\n"; +END_PERL my $doc2 = PPI::Document->new( \$script ); isa_ok( $doc2, 'PPI::Document' ); @@ -61,7 +46,10 @@ SCOPE: { } # Repeat the above with a null document -SCOPE: { +NEW_EMPTY: { + my $empty = catfile(qw{ t data 03_document empty.dat }); + ok( -f $empty, 'Found empty.dat' ); + my $doc1 = PPI::Document->new( $empty ); isa_ok( $doc1, 'PPI::Document' ); diff --git a/t/04_element.t b/t/04_element.t index 433eb8cb..6e2474ec 100644 --- a/t/04_element.t +++ b/t/04_element.t @@ -5,21 +5,14 @@ # This does an empiric test that when we try to parse something, # something ( anything ) comes out the other side. -use strict; -use File::Spec::Functions ':ALL'; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} -use PPI::Lexer (); - -# Execute the tests +use t::lib::PPI::Test::pragmas; use Test::More tests => 221; -use Test::NoWarnings; + +use File::Spec::Functions ':ALL'; +use PPI; use Scalar::Util 'refaddr'; + sub is_object { my ($left, $right, $message) = @_; $message ||= "Objects match"; diff --git a/t/05_lexer.t b/t/05_lexer.t index 8adab466..92dd0139 100644 --- a/t/05_lexer.t +++ b/t/05_lexer.t @@ -1,36 +1,16 @@ #!/usr/bin/perl -# Compare a large number of specific constructs -# with the expected Lexer dumps. - -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} -use PPI::Lexer; -use PPI::Dumper; - - - - - -##################################################################### -# Prepare +# Compare a large number of specific code samples (.code) +# with the expected Lexer dumps (.dump). +use t::lib::PPI::Test::pragmas; use Test::More tests => 219; -use Test::NoWarnings; + use File::Spec::Functions ':ALL'; +use PPI::Lexer; use t::lib::PPI; - - - - ##################################################################### # Code/Dump Testing -# ntests = 2 + 15 * nfiles t::lib::PPI->run_testdir( catdir( 't', 'data', '05_lexer' ) ); diff --git a/t/06_round_trip.t b/t/06_round_trip.t index 5f43af81..4d148312 100644 --- a/t/06_round_trip.t +++ b/t/06_round_trip.t @@ -3,22 +3,14 @@ # Load ALL of the PPI files, lex them in, dump them # out, and verify that the code goes in and out cleanly. -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More; # Plan comes later -use Test::NoWarnings; + use File::Spec::Functions ':ALL'; use PPI; - - ##################################################################### # Prepare diff --git a/t/07_token.t b/t/07_token.t index 4d5e803d..c5ecb34e 100644 --- a/t/07_token.t +++ b/t/07_token.t @@ -2,20 +2,12 @@ # Formal unit tests for specific PPI::Token classes -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - -# Execute the tests +use t::lib::PPI::Test::pragmas; use Test::More tests => 447; -use Test::NoWarnings; + use File::Spec::Functions ':ALL'; -use t::lib::PPI; use PPI; +use t::lib::PPI; @@ -23,7 +15,6 @@ use PPI; ##################################################################### # Code/Dump Testing -# ntests = 2 + 12 * nfiles t::lib::PPI->run_testdir( catdir( 't', 'data', '07_token' ) ); @@ -33,7 +24,7 @@ t::lib::PPI->run_testdir( catdir( 't', 'data', '07_token' ) ); ##################################################################### # PPI::Token::Symbol Unit Tests -# Note: braces and the symbol() method are tested in regression.t +# Note: braces and the symbol() method are tested in 08_regression.t SCOPE: { # Test both creation methods diff --git a/t/08_regression.t b/t/08_regression.t index 5b7bee8b..a63304fd 100644 --- a/t/08_regression.t +++ b/t/08_regression.t @@ -4,19 +4,11 @@ # Some other regressions tests are included here for simplicity. -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - -# For each new item in t/data/08_regression add another 15 tests +use t::lib::PPI::Test::pragmas; use Test::More tests => 932; -use Test::NoWarnings; -use t::lib::PPI; + use PPI; +use t::lib::PPI; sub pause { local $@; @@ -25,11 +17,8 @@ sub pause { - - ##################################################################### # Code/Dump Testing -# ntests = 2 + 14 * nfiles t::lib::PPI->run_testdir(qw{ t data 08_regression }); diff --git a/t/09_normal.t b/t/09_normal.t index 8935df11..296644e3 100644 --- a/t/09_normal.t +++ b/t/09_normal.t @@ -3,16 +3,9 @@ # Testing of the normalization functions. # (only very basic at this point) -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More tests => 14; -use Test::NoWarnings; + use File::Spec::Functions ':ALL'; use PPI; diff --git a/t/10_statement.t b/t/10_statement.t index 61ca1255..b7a196d0 100644 --- a/t/10_statement.t +++ b/t/10_statement.t @@ -2,49 +2,10 @@ # Test the various PPI::Statement packages -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - -# Execute the tests -use Test::More tests => 12; -use Test::NoWarnings; -use File::Spec::Functions ':ALL'; -use Scalar::Util 'refaddr'; -use PPI::Lexer (); - - +use t::lib::PPI::Test::pragmas; +use Test::More tests => 6; - - -##################################################################### -# Tests for PPI::Statement::Package - -SCOPE: { - # Create a document with various example package statements - my $Document = PPI::Lexer->lex_source( <<'END_PERL' ); -package Foo; -SCOPE: { - package # comment - Bar::Baz; - 1; -} -1; -END_PERL - isa_ok( $Document, 'PPI::Document' ); - - # Check that both of the package statements are detected - my $packages = $Document->find('Statement::Package'); - is( scalar(@$packages), 2, 'Found 2 package statements' ); - is( $packages->[0]->namespace, 'Foo', 'Package 1 returns correct namespace' ); - is( $packages->[1]->namespace, 'Bar::Baz', 'Package 2 returns correct namespace' ); - is( $packages->[0]->file_scoped, 1, '->file_scoped returns true for package 1' ); - is( $packages->[1]->file_scoped, '', '->file_scoped returns false for package 2' ); -} +use PPI; diff --git a/t/11_util.t b/t/11_util.t index 9dcf3094..d802c1de 100644 --- a/t/11_util.t +++ b/t/11_util.t @@ -2,18 +2,10 @@ # Test the PPI::Util package -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More tests => 13; -use Test::NoWarnings; + use File::Spec::Functions ':ALL'; -use PPI::Lexer (); use PPI; use PPI::Util qw{_Document _slurp}; diff --git a/t/12_location.t b/t/12_location.t index 5aeabdbb..673e0881 100644 --- a/t/12_location.t +++ b/t/12_location.t @@ -2,19 +2,12 @@ # Tests the accuracy and features for location functionality -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More tests => 683; -use Test::NoWarnings; -use File::Spec::Functions ':ALL'; + use PPI; + my $test_source = <<'END_PERL'; my $foo = 'bar'; diff --git a/t/13_data.t b/t/13_data.t index 30659368..bca4d972 100644 --- a/t/13_data.t +++ b/t/13_data.t @@ -2,19 +2,13 @@ # Tests functionality relating to __DATA__ sections of files -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More tests => 8; -use Test::NoWarnings; + use File::Spec::Functions ':ALL'; use PPI; + my $module = catfile('t', 'data', '13_data', 'Foo.pm'); ok( -f $module, 'Test file exists' ); diff --git a/t/14_charsets.t b/t/14_charsets.t index d4d1745a..de8734ff 100644 --- a/t/14_charsets.t +++ b/t/14_charsets.t @@ -1,14 +1,7 @@ #!/usr/bin/perl -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More; - BEGIN { if ($] < 5.008007) { Test::More->import( skip_all => "Unicode support requires perl 5.8.7" ); @@ -17,9 +10,7 @@ BEGIN { plan( tests => 17 ); } -use Test::NoWarnings; -use utf8; -use File::Spec::Functions ':ALL'; +use utf8; # perl version check above says this is okay use Params::Util qw{_INSTANCE}; use PPI; diff --git a/t/15_transform.t b/t/15_transform.t index 662835fa..62b0e5fe 100644 --- a/t/15_transform.t +++ b/t/15_transform.t @@ -1,15 +1,8 @@ #!/usr/bin/perl -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More 0.86 tests => 24; -use Test::NoWarnings; + use File::Spec::Functions ':ALL'; use File::Remove; use PPI; diff --git a/t/16_xml.t b/t/16_xml.t index 9f16f561..6895fd4c 100644 --- a/t/16_xml.t +++ b/t/16_xml.t @@ -1,16 +1,8 @@ #!/usr/bin/perl -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More 0.86 tests => 17; -use Test::NoWarnings; -use File::Spec::Functions ':ALL'; + use PPI; diff --git a/t/17_storable.t b/t/17_storable.t index 355850fb..88882987 100644 --- a/t/17_storable.t +++ b/t/17_storable.t @@ -2,14 +2,7 @@ # Test compatibility with Storable -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More; BEGIN { # Is Storable installed? @@ -21,7 +14,6 @@ BEGIN { } } -use Test::NoWarnings; use Scalar::Util 'refaddr'; use PPI; diff --git a/t/18_cache.t b/t/18_cache.t index 8f0251c8..749f2223 100644 --- a/t/18_cache.t +++ b/t/18_cache.t @@ -1,23 +1,17 @@ #!/usr/bin/perl -# Test compatibility with Storable - -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +# Test PPI::Cache +use t::lib::PPI::Test::pragmas; use Test::More tests => 43; -use Test::NoWarnings; + use File::Spec::Unix; use File::Spec::Functions ':ALL'; use Scalar::Util 'refaddr'; use File::Remove (); use PPI::Document (); use PPI::Cache (); +use Test::SubCalls; use constant VMS => !! ( $^O eq 'VMS' ); use constant FILE => VMS ? 'File::Spec::Unix' : 'File::Spec'; @@ -107,10 +101,7 @@ isa_ok( PPI::Document->get_cache, 'PPI::Cache' ); is( refaddr($Cache), refaddr(PPI::Document->get_cache), '->get_cache returns the same cache object' ); -SKIP: { - skip("Test::SubCalls requires >= 5.6", 7 ) if $] < 5.006; - require Test::SubCalls; - +SCOPE: { # Set the tracking on the Tokenizer constructor ok( Test::SubCalls::sub_track( 'PPI::Tokenizer::new' ), 'Tracking calls to PPI::Tokenizer::new' ); Test::SubCalls::sub_calls( 'PPI::Tokenizer::new', 0 ); @@ -130,9 +121,7 @@ SKIP: { 'PPI::Document->new with cache enabled returns two identical objects' ); } -SKIP: { - skip("Test::SubCalls requires >= 5.6", 8 ) if $] < 5.006; - +SCOPE: { # Done now, can we clear the cache? is( PPI::Document->set_cache(undef), 1, '->set_cache(undef) returns true' ); is( PPI::Document->get_cache, undef, '->get_cache returns undef' ); diff --git a/t/19_selftesting.t b/t/19_selftesting.t index 6ceb98cf..b9663e93 100644 --- a/t/19_selftesting.t +++ b/t/19_selftesting.t @@ -5,16 +5,9 @@ # Using PPI to analyse its own code at install-time? Fuck yeah! :) -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More; # Plan comes later -use Test::NoWarnings; + use Test::Object; use File::Spec::Functions ':ALL'; use Params::Util qw{_CLASS _ARRAY _INSTANCE _IDENTIFIER}; @@ -84,7 +77,7 @@ is_deeply( $bad, [ 'Bad::Class1', 'Bad::Class2', 'Bad::Class3', 'Bad::Class4' ], foreach my $file ( @files ) { # MD5 the raw file my $md5a = PPI::Util::md5hex_file($file); - like( $md5a, qr/^[0-9a-f]{32}\z/, 'md5hex_file ok' ); + like( $md5a, qr/^[[:xdigit:]]{32}\z/, 'md5hex_file ok' ); # Load the file my $Document = PPI::Document->new($file); diff --git a/t/20_tokenizer_regression.t b/t/20_tokenizer_regression.t index d9be7a26..b96a02a1 100644 --- a/t/20_tokenizer_regression.t +++ b/t/20_tokenizer_regression.t @@ -1,30 +1,12 @@ #!/usr/bin/perl -# code/dump-style regression tests for known lexing problems. +# Regression tests for known tokenization problems. -# Some other regressions tests are included here for simplicity. +use t::lib::PPI::Test::pragmas; +use Test::More; # Plan comes later -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - -use File::Spec::Functions ':ALL'; - -use PPI::Lexer; -use PPI::Dumper; -use Carp 'croak'; use Params::Util qw{_INSTANCE}; - -sub pause { - local $@; - sleep 1 if !eval { require Time::HiRes; Time::HiRes::sleep(0.1); 1 }; -} - - +use PPI; @@ -61,15 +43,14 @@ BEGIN { ); } -use Test::More tests => 1 + scalar(@FAILURES) * 3; -use Test::NoWarnings; +Test::More::plan( tests => 1 + scalar(@FAILURES) * 3 ); ##################################################################### -# Code/Dump Testing +# Test all the failures foreach my $code ( @FAILURES ) { test_code( $code ); @@ -94,7 +75,7 @@ sub test_code { my $code = shift; my $quotable = quotable($code); my $Document = eval { - # $SIG{__WARN__} = sub { croak('Triggered a warning') }; + # use Carp 'croak'; $SIG{__WARN__} = sub { croak('Triggered a warning') }; PPI::Document->new(\$code); }; ok( _INSTANCE($Document, 'PPI::Document'), @@ -117,7 +98,7 @@ sub test_code { sub quickcheck { my $code = shift; my $fails = $code; - # $SIG{__WARN__} = sub { croak('Triggered a warning') }; + # use Carp 'croak'; $SIG{__WARN__} = sub { croak('Triggered a warning') }; while ( length $fails ) { chop $code; diff --git a/t/21_exhaustive.t b/t/21_exhaustive.t index de89f86c..934549b8 100644 --- a/t/21_exhaustive.t +++ b/t/21_exhaustive.t @@ -2,14 +2,11 @@ # Exhaustively test all possible Perl programs to a particular length -use strict; -use Carp 'croak'; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; +use Test::More; # Plan comes later + +use Params::Util qw{_INSTANCE}; +use PPI; use vars qw{$MAX_CHARS $ITERATIONS $LENGTH @ALL_CHARS}; BEGIN { @@ -35,18 +32,7 @@ BEGIN { # ); } - - - - -##################################################################### -# Prepare - use Test::More tests => ($MAX_CHARS + $ITERATIONS + 3); -use Test::NoWarnings; -use File::Spec::Functions ':ALL'; -use Params::Util qw{_INSTANCE}; -use PPI; @@ -147,7 +133,7 @@ sub test_code2 { sub test_code { my $code = shift; my $Document = eval { - # $SIG{__WARN__} = sub { croak('Triggered a warning') }; + # use Carp 'croak'; $SIG{__WARN__} = sub { croak('Triggered a warning') }; PPI::Document->new(\$code); }; diff --git a/t/22_readonly.t b/t/22_readonly.t index 966ada68..05bf9298 100644 --- a/t/22_readonly.t +++ b/t/22_readonly.t @@ -2,17 +2,9 @@ # Testing of readonly functionality -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More tests => 9; -use Test::NoWarnings; -use File::Spec::Functions ':ALL'; + use PPI::Document; diff --git a/t/23_file.t b/t/23_file.t index c6fb86d9..68138b60 100644 --- a/t/23_file.t +++ b/t/23_file.t @@ -2,16 +2,9 @@ # Testing of PPI::Document::File -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More tests => 5; -use Test::NoWarnings; + use File::Spec::Functions ':ALL'; use PPI::Document::File; diff --git a/t/24_v6.t b/t/24_v6.t index 5445f67b..a015276a 100644 --- a/t/24_v6.t +++ b/t/24_v6.t @@ -3,16 +3,9 @@ # Regression test of a Perl 5 grammar that exploded # with a "98 subroutine recursion" error in 1.201 -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More tests => 9; -use Test::NoWarnings; + use File::Spec::Functions ':ALL'; use PPI; diff --git a/t/25_increment.t b/t/25_increment.t index 205b9c7b..30f40029 100644 --- a/t/25_increment.t +++ b/t/25_increment.t @@ -5,20 +5,10 @@ # state between an empty document and the entire file to make sure # all of them parse as legal documents and don't crash the parser. -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More tests => 3876; -use Test::NoWarnings; -use File::Spec::Functions ':ALL'; -use Params::Util qw{_INSTANCE}; -use PPI::Lexer; -use PPI::Dumper; + +use PPI; use t::lib::PPI; diff --git a/t/26_bom.t b/t/26_bom.t index 9b9a03e9..47ea71d0 100644 --- a/t/26_bom.t +++ b/t/26_bom.t @@ -1,16 +1,8 @@ #!/usr/bin/perl -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - -# For each new item in t/data/08_regression add another 14 tests +use t::lib::PPI::Test::pragmas; use Test::More tests => 21; -use Test::NoWarnings; + use t::lib::PPI; use PPI; @@ -20,6 +12,5 @@ use PPI; ##################################################################### # Code/Dump Testing -# ntests = 2 + 14 * nfiles t::lib::PPI->run_testdir(qw{ t data 26_bom }); diff --git a/t/27_complete.t b/t/27_complete.t index be4f2f8d..5e3b12b8 100644 --- a/t/27_complete.t +++ b/t/27_complete.t @@ -2,16 +2,9 @@ # Testing for the PPI::Document ->complete method -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More; -use Test::NoWarnings; + use File::Spec::Functions ':ALL'; use PPI; diff --git a/t/28_foreach_qw.t b/t/28_foreach_qw.t index 1b0d3fef..6c00dbf4 100644 --- a/t/28_foreach_qw.t +++ b/t/28_foreach_qw.t @@ -2,17 +2,10 @@ # Standalone tests to check "foreach qw{foo} {}" -use strict; -BEGIN { - no warnings 'once'; - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More tests => 13; -use Test::NoWarnings; -use File::Spec::Functions ':ALL'; + +#use File::Spec::Functions ':ALL'; use PPI; diff --git a/t/interactive.t b/t/interactive.t index 58bf31e2..519a2970 100644 --- a/t/interactive.t +++ b/t/interactive.t @@ -1,20 +1,14 @@ #!/usr/bin/perl # Script used to temporarily test the most recent parser bug. -# Testing it here is must more efficient than having to trace +# Testing it here is much more efficient than having to trace # down through the entire set of regression tests. -use strict; -use File::Spec::Functions ':ALL'; -BEGIN { - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::XS_DISABLE = 1; # Prevent warning -} +use t::lib::PPI::Test::pragmas; +use Test::More tests => 3; + use PPI; -# Execute the tests -use Test::More tests => 2; # Define the test code my $code = 'sub f:f('; diff --git a/t/lib/PPI/Test/pragmas.pm b/t/lib/PPI/Test/pragmas.pm new file mode 100644 index 00000000..07b6251e --- /dev/null +++ b/t/lib/PPI/Test/pragmas.pm @@ -0,0 +1,33 @@ +package t::lib::PPI::Test::pragmas; + +=head1 NAME + +PPI::Test::pragmas -- standard complier/runtime setup for PPI tests + +=cut + +use 5.006; +use strict; +use warnings; + +use Test::NoWarnings; + +BEGIN { + select STDERR; ## no critic ( InputOutput::ProhibitOneArgSelect ) + $| = 1; + select STDOUT; ## no critic ( InputOutput::ProhibitOneArgSelect ) + + no warnings 'once'; ## no critic ( TestingAndDebugging::ProhibitNoWarnings ) + $PPI::XS_DISABLE = 1; + $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; +} + +sub import { + strict->import(); + warnings->import(); + Test::NoWarnings->import(); + return; +} + + +1; diff --git a/t/ppi_element.t b/t/ppi_element.t index b926e683..e3cf2427 100644 --- a/t/ppi_element.t +++ b/t/ppi_element.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Element -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 58; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_lexer.t b/t/ppi_lexer.t index 06315e8f..0cc30b82 100644 --- a/t/ppi_lexer.t +++ b/t/ppi_lexer.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Lexer -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 44; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_node.t b/t/ppi_node.t index 71edfa62..b93bef83 100644 --- a/t/ppi_node.t +++ b/t/ppi_node.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Node -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 3; -use Test::NoWarnings; + use PPI; @@ -19,6 +12,7 @@ PRUNE: { # Avoids a bug in old Perls relating to the detection of scripts # Known to occur in ActivePerl 5.6.1 and at least one 5.6.2 install. my $hashbang = reverse 'lrep/nib/rsu/!#'; + my $document = PPI::Document->new( \<<"END_PERL" ); $hashbang diff --git a/t/ppi_normal.t b/t/ppi_normal.t index 0831f4ee..c24ccc6b 100644 --- a/t/ppi_normal.t +++ b/t/ppi_normal.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Normal -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 28; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_statement.t b/t/ppi_statement.t index 076e609c..73e0eeb0 100644 --- a/t/ppi_statement.t +++ b/t/ppi_statement.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Statement -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 23; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_statement_compound.t b/t/ppi_statement_compound.t index ce3490c8..a7b8b0af 100644 --- a/t/ppi_statement_compound.t +++ b/t/ppi_statement_compound.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Statement::Compound -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 53; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_statement_include.t b/t/ppi_statement_include.t index 20abebea..d477da39 100644 --- a/t/ppi_statement_include.t +++ b/t/ppi_statement_include.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Statement::Include -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} -use Test::More tests => 64; -use Test::NoWarnings; +use t::lib::PPI::Test::pragmas; +use Test::More tests => 12066; + use PPI; @@ -45,11 +38,13 @@ use No::Version; use No::Version::With::Argument 'x'; use No::Version::With::Arguments 1, 2; use 5.005; +use VString::Version v10; +use VString::Version::Decimal v1.5; END_PERL isa_ok( $document, 'PPI::Document' ); my $statements = $document->find('PPI::Statement::Include'); - is( scalar @{$statements}, 7, 'Found expected include statements.' ); + is( scalar @{$statements}, 9, 'Found expected include statements.' ); is( $statements->[0]->module_version, 1, 'Integer version' ); is( $statements->[1]->module_version, 1.5, 'Float version' ); is( $statements->[2]->module_version, 1, 'Version and argument' ); @@ -57,6 +52,8 @@ END_PERL is( $statements->[4]->module_version, undef, 'No version, with argument' ); is( $statements->[5]->module_version, undef, 'No version, with arguments' ); is( $statements->[6]->module_version, undef, 'Version include, no module' ); + is( $statements->[7]->module_version, 'v10', 'Version string' ); + is( $statements->[8]->module_version, 'v1.5', 'Version string with decimal' ); } @@ -235,3 +232,50 @@ END_PERL 'arguments with Test::More', ); } + + +KEYWORDS_AS_MODULE_NAMES: { + for my $name ( + # normal names + 'Foo', + 'Foo::Bar', + 'Foo::Bar::Baz', + 'version', + # Keywords must parse as Word and not influence lexing + # of subsequent curly braces. + keys %PPI::Token::Word::KEYWORDS, + # Other weird and/or special words, just in case + '__PACKAGE__', + '__FILE__', + '__LINE__', + '__SUB__', + 'AUTOLOAD', + ) { + for my $include ( 'use', 'no' ) { # 'require' does not force tokes to be words + for my $version ( '', 'v1.2.3', '1.2.3', 'v10' ) { + my $code = "$include $name $version;"; + + my $Document = PPI::Document->new( \"$code 999;" ); + is( $Document->schildren(), 2, "$code number of statements in document" ); + isa_ok( $Document->schild(0), 'PPI::Statement::Include', $code ); + + # first child is the include statement + my $expected_tokens = [ + [ 'PPI::Token::Word', $include ], + [ 'PPI::Token::Word', $name ], + ]; + if ( $version ) { + push @$expected_tokens, [ 'PPI::Token::Number::Version', $version ]; + } + push @$expected_tokens, [ 'PPI::Token::Structure', ';' ]; + my $got_tokens = [ map { [ ref $_, "$_" ] } $Document->schild(0)->schildren() ]; + is_deeply( $got_tokens, $expected_tokens, "$code tokens as expected" ); + + # second child not swallowed up by the first + isa_ok( $Document->schild(1), 'PPI::Statement', "$code prior statement end recognized" ); + isa_ok( $Document->schild(1)->schild(0), 'PPI::Token::Number', $code ); + is( $Document->schild(1)->schild(0), '999', "$code number correct" ); + } + } + } +} diff --git a/t/ppi_statement_package.t b/t/ppi_statement_package.t index 57a3a45e..643d04c3 100644 --- a/t/ppi_statement_package.t +++ b/t/ppi_statement_package.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Statement::Package -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} -use Test::More tests => 3; -use Test::NoWarnings; +use t::lib::PPI::Test::pragmas; +use Test::More tests => 14949; + use PPI; @@ -35,3 +28,111 @@ END_PERL diag $_->parent()->parent()->content() foreach @{$packages}; } } + + +INSIDE_SCOPE: { + # Create a document with various example package statements + my $Document = PPI::Document->new( \<<'END_PERL' ); +package Foo; +SCOPE: { + package # comment + Bar::Baz; + 1; +} +1; +END_PERL + isa_ok( $Document, 'PPI::Document' ); + + # Check that both of the package statements are detected + my $packages = $Document->find('Statement::Package'); + is( scalar(@$packages), 2, 'Found 2 package statements' ); + is( $packages->[0]->namespace, 'Foo', 'Package 1 returns correct namespace' ); + is( $packages->[1]->namespace, 'Bar::Baz', 'Package 2 returns correct namespace' ); + is( $packages->[0]->file_scoped, 1, '->file_scoped returns true for package 1' ); + is( $packages->[1]->file_scoped, '', '->file_scoped returns false for package 2' ); +} + + +PERL_5_12_SYNTAX: { + my @names = ( + # normal name + 'Foo', + # Keywords must parse as Word and not influence lexing + # of subsequent curly braces. + keys %PPI::Token::Word::KEYWORDS, + # regression: misparsed as version string + 'v10', + # regression GitHub #122: 'x' parsed as x operator + 'x64', + # Other weird and/or special words, just in case + '__PACKAGE__', + '__FILE__', + '__LINE__', + '__SUB__', + 'AUTOLOAD', + ); + my @versions = ( + [ 'v1.2.3 ', 'PPI::Token::Number::Version' ], + [ 'v1.2.3', 'PPI::Token::Number::Version' ], + [ '0.50 ', 'PPI::Token::Number::Float' ], + [ '0.50', 'PPI::Token::Number::Float' ], + [ '', '' ], # omit version, traditional + ); + my @blocks = ( + [ ';', 'PPI::Token::Structure' ], # traditional package syntax + [ '{ 1 }', 'PPI::Structure::Block' ], # 5.12 package syntax + ); + $_->[2] = strip_ws_padding( $_->[0] ) for @versions, @blocks; + + for my $name ( @names ) { + for my $version_pair ( @versions ) { + for my $block_pair ( @blocks ) { + my @test = prepare_package_test( $version_pair, $block_pair, $name ); + test_package_blocks( @test ); + } + } + } +} + +sub strip_ws_padding { + my ( $string ) = @_; + $string =~ s/(^\s+|\s+$)//g; + return $string; +} + +sub prepare_package_test { + my ( $version_pair, $block_pair, $name ) = @_; + + my ( $version, $version_type, $version_stripped ) = @{$version_pair}; + my ( $block, $block_type, $block_stripped ) = @{$block_pair}; + + my $code = "package $name $version$block"; + + my $expected_package_tokens = [ + [ 'PPI::Token::Word', 'package' ], + [ 'PPI::Token::Word', $name ], + ($version ne '') ? [ $version_type, $version_stripped ] : (), + [ $block_type, $block_stripped ], + ]; + + return ( $code, $expected_package_tokens ); +} + +sub test_package_blocks { + my ( $code, $expected_package_tokens ) = @_; + + my $Document = PPI::Document->new( \"$code 999;" ); + is( $Document->schildren, 2, "$code number of statements in document" ); + isa_ok( $Document->schild(0), 'PPI::Statement::Package', $code ); + + # first child is the package statement + my $got_tokens = [ map { [ ref $_, "$_" ] } $Document->schild(0)->schildren ]; + is_deeply( $got_tokens, $expected_package_tokens, "$code tokens as expected" ); + + # second child not swallowed up by the first + isa_ok( $Document->schild(1), 'PPI::Statement', "$code prior statement end recognized" ); + isa_ok( $Document->schild(1)->schild(0), 'PPI::Token::Number', $code ); + is( $Document->schild(1)->schild(0), '999', "$code number correct" ); + + return; +} diff --git a/t/ppi_statement_scheduled.t b/t/ppi_statement_scheduled.t index fe0e59ae..463bef51 100644 --- a/t/ppi_statement_scheduled.t +++ b/t/ppi_statement_scheduled.t @@ -2,19 +2,12 @@ # Test PPI::Statement::Scheduled -use strict; - -BEGIN { - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - +use t::lib::PPI::Test::pragmas; use Test::More tests => 241; -use Test::NoWarnings; + use PPI; + SUB_WORD_OPTIONAL: { for my $name ( qw( BEGIN CHECK UNITCHECK INIT END ) ) { for my $sub ( '', 'sub ' ) { diff --git a/t/ppi_statement_sub.t b/t/ppi_statement_sub.t index 0d5e4ce7..2774fe4b 100644 --- a/t/ppi_statement_sub.t +++ b/t/ppi_statement_sub.t @@ -2,19 +2,41 @@ # Test PPI::Statement::Sub -use strict; +use t::lib::PPI::Test::pragmas; +use Test::More tests => 6208; -BEGIN { - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} - -use Test::More tests => 131; -use Test::NoWarnings; use PPI; +NAME: { + for my $test ( + { code => 'sub foo {}', name => 'foo' }, + { code => 'sub foo{}', name => 'foo' }, + { code => 'sub FOO {}', name => 'FOO' }, + { code => 'sub _foo {}', name => '_foo' }, + { code => 'sub _0foo {}', name => '_0foo' }, + { code => 'sub _foo0 {}', name => '_foo0' }, + { code => 'sub ___ {}', name => '___' }, + { code => 'sub bar() {}', name => 'bar' }, + { code => 'sub baz : method{}', name => 'baz' }, + { code => 'sub baz : method lvalue{}', name => 'baz' }, + { code => 'sub baz : method:lvalue{}', name => 'baz' }, + { code => 'sub baz (*) : method : lvalue{}', name => 'baz' }, + { code => 'sub x64 {}', name => 'x64' }, # should not be parsed as x operator + ) { + my $code = $test->{code}; + my $name = $test->{name}; + + my $Document = PPI::Document->new( \$code ); + isa_ok( $Document, 'PPI::Document', "$code: got document" ); + + my ( $sub_statement, $dummy ) = $Document->schildren; + isa_ok( $sub_statement, 'PPI::Statement::Sub', "$code: document child is a sub" ); + is( $dummy, undef, "$code: document has exactly one child" ); + + is( $sub_statement->name, $name, "$code: name() correct" ); + } +} + SUB_WORD_OPTIONAL: { # 'sub' is optional for these special subs. Make sure they're # recognized as subs and sub declarations. @@ -69,6 +91,57 @@ PROTOTYPE: { } } +BLOCK_AND_FORWARD: { + for my $test ( + { code => 'sub foo {1;}', block => '{1;}' }, + { code => 'sub foo{2;};', block => '{2;}' }, + { code => "sub foo\n{3;};", block => '{3;}' }, + { code => 'sub foo;', block => '' }, + { code => 'sub foo', block => '' }, + ) { + my $code = $test->{code}; + my $block = $test->{block}; + + my $Document = PPI::Document->new( \$code ); + isa_ok( $Document, 'PPI::Document', "$code: got document" ); + + my ( $sub_statement, $dummy ) = $Document->schildren(); + isa_ok( $sub_statement, 'PPI::Statement::Sub', "$code: document child is a sub" ); + is( $dummy, undef, "$code: document has exactly one child" ); + is( $sub_statement->block, $block, "$code: block matches" ); + + is( !$sub_statement->block, !!$sub_statement->forward, "$code: block and forward are opposites" ); + } +} + +RESERVED: { + for my $test ( + { code => 'sub BEGIN {}', reserved => 1 }, + { code => 'sub CHECK {}', reserved => 1 }, + { code => 'sub UNITCHECK {}', reserved => 1 }, + { code => 'sub INIT {}', reserved => 1 }, + { code => 'sub END {}', reserved => 1 }, + { code => 'sub AUTOLOAD {}', reserved => 1 }, + { code => 'sub CLONE_SKIP {}', reserved => 1 }, + { code => 'sub __SUB__ {}', reserved => 1 }, + { code => 'sub _FOO {}', reserved => 1 }, + { code => 'sub FOO9 {}', reserved => 1 }, + { code => 'sub FO9O {}', reserved => 1 }, + { code => 'sub FOo {}', reserved => 0 }, + ) { + my $code = $test->{code}; + my $reserved = $test->{reserved}; + + my $Document = PPI::Document->new( \$code ); + isa_ok( $Document, 'PPI::Document', "$code: got document" ); + + my ( $sub_statement, $dummy ) = $Document->schildren(); + isa_ok( $sub_statement, 'PPI::Statement::Sub', "$code: document child is a sub" ); + is( $dummy, undef, "$code: document has exactly one child" ); + is( !!$sub_statement->reserved, !!$reserved, "$code: reserved matches" ); + } +} + sub test_sub_as { my ( $sub, $name, $followed_by ) = @_; @@ -92,3 +165,75 @@ sub test_sub_as { return; } + +KEYWORDS_AS_SUB_NAMES: { + my @names = ( + # normal name + 'foo', + # Keywords must parse as Word and not influence lexing + # of subsequent curly braces. + keys %PPI::Token::Word::KEYWORDS, + # regression: misparsed as version string + 'v10', + # Other weird and/or special words, just in case + '__PACKAGE__', + '__FILE__', + '__LINE__', + '__SUB__', + 'AUTOLOAD', + ); + my @blocks = ( + [ ';', 'PPI::Token::Structure' ], + [ ' ;', 'PPI::Token::Structure' ], + [ '{ 1 }', 'PPI::Structure::Block' ], + [ ' { 1 }', 'PPI::Structure::Block' ], + ); + $_->[2] = strip_ws_padding( $_->[0] ) for @blocks; + + for my $name ( @names ) { + for my $block_pair ( @blocks ) { + my @test = prepare_sub_test( $block_pair, $name ); + test_subs( @test ); + } + } +} + +sub strip_ws_padding { + my ( $string ) = @_; + $string =~ s/(^\s+|\s+$)//g; + return $string; +} + +sub prepare_sub_test { + my ( $block_pair, $name ) = @_; + + my ( $block, $block_type, $block_stripped ) = @{$block_pair}; + + my $code = "sub $name $block"; + + my $expected_sub_tokens = [ + [ 'PPI::Token::Word', 'sub' ], + [ 'PPI::Token::Word', $name ], + [ $block_type, $block_stripped ], + ]; + + return ( $code, $expected_sub_tokens ); +} + +sub test_subs { + my ( $code, $expected_sub_tokens ) = @_; + + my $Document = PPI::Document->new( \"$code 999;" ); + is( $Document->schildren, 2, "$code number of statements in document" ); + isa_ok( $Document->schild(0), 'PPI::Statement::Sub', $code ); + + my $got_tokens = [ map { [ ref $_, "$_" ] } $Document->schild(0)->schildren ]; + is_deeply( $got_tokens, $expected_sub_tokens, "$code tokens as expected" ); + + # second child not swallowed up by the first + isa_ok( $Document->schild(1), 'PPI::Statement', "$code prior statement end recognized" ); + isa_ok( $Document->schild(1)->schild(0), 'PPI::Token::Number', $code ); + is( $Document->schild(1)->schild(0), '999', "$code number correct" ); + + return; +} diff --git a/t/ppi_statement_variable.t b/t/ppi_statement_variable.t index aeb5b539..c5015f6f 100644 --- a/t/ppi_statement_variable.t +++ b/t/ppi_statement_variable.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Statement::Variable -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} -use Test::More 'no_plan'; -use Test::NoWarnings; +use t::lib::PPI::Test::pragmas; +use Test::More tests => 18; + use PPI; diff --git a/t/ppi_token__quoteengine_full.t b/t/ppi_token__quoteengine_full.t index 344458e8..8ec284ea 100644 --- a/t/ppi_token__quoteengine_full.t +++ b/t/ppi_token__quoteengine_full.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Token::_QuoteEngine::Full -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 94; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_token_dashedword.t b/t/ppi_token_dashedword.t index 509e5115..12595ec6 100644 --- a/t/ppi_token_dashedword.t +++ b/t/ppi_token_dashedword.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Token::DashedWord -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 10; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_token_heredoc.t b/t/ppi_token_heredoc.t new file mode 100644 index 00000000..0056d5a1 --- /dev/null +++ b/t/ppi_token_heredoc.t @@ -0,0 +1,164 @@ +#!/usr/bin/perl + +# Unit testing for PPI::Token::HereDoc + +use t::lib::PPI::Test::pragmas; +use Test::More tests => 12; + +use PPI; +use Test::Deep; + +# List of tests to perform. Each test requires the following information: +# - 'name': the name of the test in the output. +# - 'content': the Perl string to parse using PPI. +# - 'expected': a hashref with the keys being property names on the +# PPI::Token::HereDoc object, and the values being the expected value of +# that property after the heredoc block has been parsed. +my @tests = ( + + # Tests with a carriage return after the termination marker. + { + name => 'Bareword terminator.', + content => "my \$heredoc = < { + _terminator_line => "HERE\n", + _damaged => undef, + _terminator => 'HERE', + _mode => 'interpolate', + }, + }, + { + name => 'Single-quoted bareword terminator.', + content => "my \$heredoc = <<'HERE';\nLine 1\nLine 2\nHERE\n", + expected => { + _terminator_line => "HERE\n", + _damaged => undef, + _terminator => 'HERE', + _mode => 'literal', + }, + }, + { + name => 'Double-quoted bareword terminator.', + content => "my \$heredoc = <<\"HERE\";\nLine 1\nLine 2\nHERE\n", + expected => { + _terminator_line => "HERE\n", + _damaged => undef, + _terminator => 'HERE', + _mode => 'interpolate', + }, + }, + { + name => 'Command-quoted terminator.', + content => "my \$heredoc = <<`HERE`;\nLine 1\nLine 2\nHERE\n", + expected => { + _terminator_line => "HERE\n", + _damaged => undef, + _terminator => 'HERE', + _mode => 'command', + }, + }, + { + name => 'Legacy escaped bareword terminator.', + content => "my \$heredoc = <<\\HERE;\nLine 1\nLine 2\nHERE\n", + expected => { + _terminator_line => "HERE\n", + _damaged => undef, + _terminator => 'HERE', + _mode => 'literal', + }, + }, + + # Tests without a carriage return after the termination marker. + { + name => 'Bareword terminator (no return).', + content => "my \$heredoc = < { + _terminator_line => 'HERE', + _damaged => 1, + _terminator => 'HERE', + _mode => 'interpolate', + }, + }, + { + name => 'Single-quoted bareword terminator (no return).', + content => "my \$heredoc = <<'HERE';\nLine 1\nLine 2\nHERE", + expected => { + _terminator_line => "HERE", + _damaged => 1, + _terminator => 'HERE', + _mode => 'literal', + }, + }, + { + name => 'Double-quoted bareword terminator (no return).', + content => "my \$heredoc = <<\"HERE\";\nLine 1\nLine 2\nHERE", + expected => { + _terminator_line => 'HERE', + _damaged => 1, + _terminator => 'HERE', + _mode => 'interpolate', + }, + }, + { + name => 'Command-quoted terminator (no return).', + content => "my \$heredoc = <<`HERE`;\nLine 1\nLine 2\nHERE", + expected => { + _terminator_line => 'HERE', + _damaged => 1, + _terminator => 'HERE', + _mode => 'command', + }, + }, + { + name => 'Legacy escaped bareword terminator (no return).', + content => "my \$heredoc = <<\\HERE;\nLine 1\nLine 2\nHERE", + expected => { + _terminator_line => 'HERE', + _damaged => 1, + _terminator => 'HERE', + _mode => 'literal', + }, + }, + + # Tests without a terminator. + { + name => 'Unterminated heredoc block.', + content => "my \$heredoc = < { + _terminator_line => undef, + _damaged => 1, + _terminator => 'HERE', + _mode => 'interpolate', + }, + } + +); + +for my $test ( @tests ) { + subtest( + $test->{name}, + sub { + plan tests => 6 + keys %{ $test->{expected} }; + + my $document = PPI::Document->new( \$test->{content} ); + isa_ok( $document, 'PPI::Document' ); + + my $heredocs = $document->find( 'Token::HereDoc' ); + is( ref $heredocs, 'ARRAY', 'Found heredocs.' ); + is( scalar @$heredocs, 1, 'Found 1 heredoc block.' ); + + my $heredoc = $heredocs->[0]; + isa_ok( $heredoc, 'PPI::Token::HereDoc' ); + can_ok( $heredoc, 'heredoc' ); + + my @content = $heredoc->heredoc; + is_deeply( + \@content, + [ "Line 1\n", "Line 2\n", ], + 'The returned content does not include the heredoc terminator.', + ) or diag "heredoc() returned ", explain \@content; + + is( $heredoc->{$_}, $test->{expected}{$_}, "property '$_'" ) for keys %{ $test->{expected} }; + } + ); +} diff --git a/t/ppi_token_magic.t b/t/ppi_token_magic.t index 56c8643a..9b241bf8 100644 --- a/t/ppi_token_magic.t +++ b/t/ppi_token_magic.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Token::Magic -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 39; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_token_number_version.t b/t/ppi_token_number_version.t index cda8b47a..b0a9f0cf 100644 --- a/t/ppi_token_number_version.t +++ b/t/ppi_token_number_version.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Token::Number::Version -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} -use Test::More tests => 10; -use Test::NoWarnings; +use t::lib::PPI::Test::pragmas; +use Test::More tests => 736; + use PPI; @@ -29,3 +22,82 @@ LITERAL: { is( length($literal2), 4, 'The literal length of doc1 is 4' ); is( $literal1, $literal2, 'Literals match for 1.2.3.4 vs v1.2.3.4' ); } + + +VSTRING_ENDS_CORRECTLY: { + my @tests = ( + ( + map { + { + desc=>"no . in 'v49$_', so not a version string", + code=>"v49$_", + expected=>[ 'PPI::Token::Word' => "v49$_" ], + } + } ( + 'x3', # not fooled by faux x operator with operand + 'e10', # not fooled by faux scientific notation + keys %PPI::Token::Word::KEYWORDS, + ), + ), + ( + map { + { + desc => "version string in 'v49.49$_' stops after number", + code => "v49.49$_", + expected => [ + 'PPI::Token::Number::Version' => 'v49.49', + get_class($_) => $_, + ], + }, + } ( + keys %PPI::Token::Word::KEYWORDS, + ), + ), + ( + map { + { + desc => "version string in '49.49.49$_' stops after number", + code => "49.49.49$_", + expected => [ + 'PPI::Token::Number::Version' => '49.49.49', + get_class($_) => $_, + ], + }, + } ( + keys %PPI::Token::Word::KEYWORDS, + ), + ), + { + desc => 'version string, x, and operand', + code => 'v49.49.49x3', + expected => [ + 'PPI::Token::Number::Version' => 'v49.49.49', + 'PPI::Token::Operator' => 'x', + 'PPI::Token::Number' => '3', + ], + }, + ); + for my $test ( @tests ) { + my $code = $test->{code}; + + my $d = PPI::Document->new( \$test->{code} ); + my $tokens = $d->find( sub { 1; } ); + $tokens = [ map { ref($_), $_->content() } @$tokens ]; + my $expected = $test->{expected}; + unshift @$expected, 'PPI::Statement', $test->{code}; + my $ok = is_deeply( $tokens, $expected, $test->{desc} ); + if ( !$ok ) { + diag "$test->{code} ($test->{desc})\n"; + diag explain $tokens; + diag explain $test->{expected}; + } + } +} + +sub get_class { + my ( $t ) = @_; + my $ql = $PPI::Token::Word::QUOTELIKE{$t}; + return "PPI::Token::$ql" if $ql; + return 'PPI::Token::Operator' if $PPI::Token::Word::OPERATOR{$t}; + return 'PPI::Token::Word'; +} diff --git a/t/ppi_token_operator.t b/t/ppi_token_operator.t index 86d456bc..f4283195 100644 --- a/t/ppi_token_operator.t +++ b/t/ppi_token_operator.t @@ -2,19 +2,9 @@ # Unit testing for PPI::Token::Operator -use strict; -BEGIN { - $| = 1; - select STDERR; - $| = 1; - select STDOUT; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} -use Test::More tests => 398; -use Test::NoWarnings; +use t::lib::PPI::Test::pragmas; +use Test::More tests => 1142; + use PPI; @@ -33,20 +23,6 @@ FIND_ONE_OP: { } -HEREDOC: { - my $source = '$a = <new( \$source ); - isa_ok( $doc, 'PPI::Document', "parsed '$source'" ); - my $ops = $doc->find( 'Token::HereDoc' ); - is( ref $ops, 'ARRAY', "found heredoc" ); - is( @$ops, 1, "heredoc found exactly once" ); - - $ops = $doc->find( 'Token::Operator' ); - is( ref $ops, 'ARRAY', "operator = found operators in heredoc test" ); - is( @$ops, 1, "operator = found exactly once in heredoc test" ); -} - - PARSE_ALL_OPERATORS: { foreach my $op ( sort keys %PPI::Token::Operator::OPERATOR ) { my $source = $op eq '<>' ? '<>;' : "\$foo $op 2;"; @@ -423,6 +399,17 @@ OPERATOR_X: { 'PPI::Token::Structure' => '}', ] }, + { + desc => 'label plus x', + code => 'LABEL: x64', + expected => [ + 'PPI::Statement::Compound' => 'LABEL:', + 'PPI::Token::Label' => 'LABEL:', + 'PPI::Token::Whitespace' => ' ', + 'PPI::Statement' => 'x64', + 'PPI::Token::Word' => 'x64', + ] + }, ); # Exhaustively test when a preceding operator implies following @@ -478,6 +465,45 @@ OPERATOR_X: { push @tests, { desc => $desc, code => $code, expected => \@expected }; } + + # Test that Perl builtins known to have a null prototype do not + # force a following 'x' to be a word. + my %noprotos = map { $_ => 1 } qw( + endgrent + endhostent + endnetent + endprotoent + endpwent + endservent + fork + getgrent + gethostent + getlogin + getnetent + getppid + getprotoent + getpwent + getservent + setgrent + setpwent + time + times + wait + wantarray + __SUB__ + ); + foreach my $noproto ( keys %noprotos ) { + my $code = "$noproto x3"; + my @expected = ( + 'PPI::Token::Word' => $noproto, + 'PPI::Token::Whitespace' => ' ', + 'PPI::Token::Operator' => 'x', + 'PPI::Token::Number' => '3', + ); + my $desc = "builtin $noproto does not force following x to be a word"; + push @tests, { desc => "builtin $noproto does not force following x to be a word", code => $code, expected => \@expected }; + } + foreach my $test ( @tests ) { my $d = PPI::Document->new( \$test->{code} ); my $tokens = $d->find( sub { 1; } ); @@ -495,3 +521,95 @@ OPERATOR_X: { } } + +OPERATOR_FAT_COMMA: { + my @tests = ( + { + desc => 'integer with integer', + code => '1 => 2', + expected => [ + 'PPI::Token::Number' => '1', + 'PPI::Token::Whitespace' => ' ', + 'PPI::Token::Operator' => '=>', + 'PPI::Token::Whitespace' => ' ', + 'PPI::Token::Number' => '2', + ], + }, + { + desc => 'word with integer', + code => 'foo => 2', + expected => [ + 'PPI::Token::Word' => 'foo', + 'PPI::Token::Whitespace' => ' ', + 'PPI::Token::Operator' => '=>', + 'PPI::Token::Whitespace' => ' ', + 'PPI::Token::Number' => '2', + ], + }, + { + desc => 'dashed word with integer', + code => '-foo => 2', + expected => [ + 'PPI::Token::Word' => '-foo', + 'PPI::Token::Whitespace' => ' ', + 'PPI::Token::Operator' => '=>', + 'PPI::Token::Whitespace' => ' ', + 'PPI::Token::Number' => '2', + ], + }, + ( map { { + desc=>$_, + code=>"$_=>2", + expected=>[ + 'PPI::Token::Word' => $_, + 'PPI::Token::Operator' => '=>', + 'PPI::Token::Number' => '2', + ] + } } keys %PPI::Token::Word::KEYWORDS ), + ( map { { + desc=>$_, + code=>"($_=>2)", + expected=>[ + 'PPI::Structure::List' => "($_=>2)", + 'PPI::Token::Structure' => '(', + 'PPI::Statement::Expression' => "$_=>2", + 'PPI::Token::Word' => $_, + 'PPI::Token::Operator' => '=>', + 'PPI::Token::Number' => '2', + 'PPI::Token::Structure' => ')', + ] + } } keys %PPI::Token::Word::KEYWORDS ), + ( map { { + desc=>$_, + code=>"{$_=>2}", + expected=>[ + 'PPI::Structure::Constructor' => "{$_=>2}", + 'PPI::Token::Structure' => '{', + 'PPI::Statement::Expression' => "$_=>2", + 'PPI::Token::Word' => $_, + 'PPI::Token::Operator' => '=>', + 'PPI::Token::Number' => '2', + 'PPI::Token::Structure' => '}', + ] + } } keys %PPI::Token::Word::KEYWORDS ), + ); + + for my $test ( @tests ) { + my $code = $test->{code}; + + my $d = PPI::Document->new( \$test->{code} ); + my $tokens = $d->find( sub { 1; } ); + $tokens = [ map { ref($_), $_->content() } @$tokens ]; + my $expected = $test->{expected}; + if ( $expected->[0] !~ /^PPI::Statement/ ) { + unshift @$expected, 'PPI::Statement', $test->{code}; + } + my $ok = is_deeply( $tokens, $expected, $test->{desc} ); + if ( !$ok ) { + diag "$test->{code} ($test->{desc})\n"; + diag explain $tokens; + diag explain $test->{expected}; + } + } +} + diff --git a/t/ppi_token_pod.t b/t/ppi_token_pod.t index e0c24649..96f41110 100644 --- a/t/ppi_token_pod.t +++ b/t/ppi_token_pod.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Token::Pod -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 9; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_token_prototype.t b/t/ppi_token_prototype.t index a0d4439a..8185482b 100644 --- a/t/ppi_token_prototype.t +++ b/t/ppi_token_prototype.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Token::Prototype -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 801; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_token_quote.t b/t/ppi_token_quote.t index c037fc54..e624d6c9 100644 --- a/t/ppi_token_quote.t +++ b/t/ppi_token_quote.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Token::Quote -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 16; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_token_quote_double.t b/t/ppi_token_quote_double.t index b83e9226..915fc5d0 100644 --- a/t/ppi_token_quote_double.t +++ b/t/ppi_token_quote_double.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Token::Quote::Double -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 20; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_token_quote_interpolate.t b/t/ppi_token_quote_interpolate.t index 4dde28df..094e8930 100644 --- a/t/ppi_token_quote_interpolate.t +++ b/t/ppi_token_quote_interpolate.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Token::Quote::Interpolate -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 9; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_token_quote_literal.t b/t/ppi_token_quote_literal.t index 14b71cf6..e740fbc1 100644 --- a/t/ppi_token_quote_literal.t +++ b/t/ppi_token_quote_literal.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Token::Quote::Literal -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 13; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_token_quote_single.t b/t/ppi_token_quote_single.t index 6b11da53..119f2044 100644 --- a/t/ppi_token_quote_single.t +++ b/t/ppi_token_quote_single.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Token::Quote::Single -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 25; -use Test::NoWarnings; + use PPI; diff --git a/t/ppi_token_quotelike_words.t b/t/ppi_token_quotelike_words.t index d983f092..db0d7faa 100644 --- a/t/ppi_token_quotelike_words.t +++ b/t/ppi_token_quotelike_words.t @@ -2,56 +2,137 @@ # Unit testing for PPI::Token::QuoteLike::Words -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} -use Test::More tests => 13; -use Test::NoWarnings; +use t::lib::PPI::Test::pragmas; +use Test::More tests => 1941; +use Test::Deep; + use PPI; +sub permute_test; +sub assemble_and_run; LITERAL: { - my $empty_list_document = PPI::Document->new(\<<'END_PERL'); -qw// -qw/ / -END_PERL - - isa_ok( $empty_list_document, 'PPI::Document' ); - my $empty_list_tokens = - $empty_list_document->find('PPI::Token::QuoteLike::Words'); - is( scalar @{$empty_list_tokens}, 2, 'Found expected empty word lists.' ); - foreach my $token ( @{$empty_list_tokens} ) { - my @literal = $token->literal; - is( scalar @literal, 0, qq ); - } - - my $non_empty_list_document = PPI::Document->new(\<<'END_PERL'); -qw/foo bar baz/ -qw/ foo bar baz / -qw {foo bar baz} -END_PERL - my @expected = qw/ foo bar baz /; - - isa_ok( $non_empty_list_document, 'PPI::Document' ); - my $non_empty_list_tokens = - $non_empty_list_document->find('PPI::Token::QuoteLike::Words'); - is( - scalar(@$non_empty_list_tokens), - 3, - 'Found expected non-empty word lists.', - ); - foreach my $token ( @$non_empty_list_tokens ) { - my $literal = $token->literal; - is( - $literal, - scalar @expected, - qq, - ); - is_deeply( [ $token->literal ], \@expected, '->literal matches expected' ); - } + # empty + permute_test [], '/', '/', []; + permute_test [], '"', '"', []; + permute_test [], "'", "'", []; + permute_test [], '(', ')', []; + permute_test [], '{', '}', []; + permute_test [], '[', ']', []; + permute_test [], '<', '>', []; + + # words + permute_test ['a', 'b', 'c'], '/', '/', ['a', 'b', 'c']; + permute_test ['a,', 'b', 'c,'], '/', '/', ['a,', 'b', 'c,']; + permute_test ['a', ',', '#', 'c'], '/', '/', ['a', ',', '#', 'c']; + permute_test ['f_oo', 'b_ar'], '/', '/', ['f_oo', 'b_ar']; + + # it's allowed for both delims to be closers + permute_test ['a'], ')', ')', ['a']; + permute_test ['a'], '}', '}', ['a']; + permute_test ['a'], ']', ']', ['a']; + permute_test ['a'], '>', '>', ['a']; + + # containing things that sometimes are delimiters + permute_test ['/'], '(', ')', ['/']; + permute_test ['//'], '(', ')', ['//']; + permute_test ['qw()'], '(', ')', ['qw()']; + permute_test ['qw', '()'], '(', ')', ['qw', '()']; + permute_test ['qw//'], '(', ')', ['qw//']; + + # nested delimiters + permute_test ['()'], '(', ')', ['()']; + permute_test ['{}'], '{', '}', ['{}']; + permute_test ['[]'], '[', ']', ['[]']; + permute_test ['<>'], '<', '>', ['<>']; + permute_test ['((', ')', ')'], '(', ')', ['((', ')', ')']; + permute_test ['{{', '}', '}'], '{', '}', ['{{', '}', '}']; + permute_test ['[[', ']', ']'], '[', ']', ['[[', ']', ']']; + permute_test ['<<', '>', '>'], '<', '>', ['<<', '>', '>']; + + my $bs = '\\'; # a single backslash character + + # escaped opening and closing + permute_test ["$bs)"], '(', ')', [')']; + permute_test ["$bs("], '(', ')', ['(']; + permute_test ["$bs}"], '{', '}', ['}']; + permute_test ["${bs}{"], '{', '}', ['{']; + permute_test ["$bs]"], '[', ']', [']']; + permute_test ["${bs}["], '[', ']', ['[']; + permute_test ["$bs<"], '<', '>', ['<']; + permute_test ["$bs>"], '<', '>', ['>']; + permute_test ["$bs/"], '/', '/', ['/']; + permute_test ["$bs'"], "'", "'", ["'"]; + permute_test [$bs.'"'], '"', '"', ['"']; + + # alphanum delims have to be separated from qw + assemble_and_run " ", ['a', "${bs}1"], '1', " ", " ", '1', ['a', '1']; + assemble_and_run " ", ["${bs}a"], 'a', " ", " ", 'a', ['a']; + assemble_and_run "\n", ["${bs}a"], 'a', "\n", "\n", 'a', ['a']; + + # '#' delims cannot be separated from qw + assemble_and_run '', ['a'], '#', '', ' ', '#', ['a']; + assemble_and_run '', ['a'], '#', ' ', ' ', '#', ['a']; + assemble_and_run '', ["$bs#"], '#', '', ' ', '#', ['#']; + assemble_and_run '', ["$bs#"], '#', ' ', ' ', '#', ['#']; + assemble_and_run '', ["$bs#"], '#', "\n", "\n", '#', ['#']; + + # a single backslash represents itself + assemble_and_run '', [$bs], '(', ' ', ' ', ')', [$bs]; + assemble_and_run '', [$bs], '(', "\n", ' ', ')', [$bs]; + + # a double backslash represents itself + assemble_and_run '', ["$bs$bs"], '(', ' ', ' ', ')', [$bs]; + assemble_and_run '', ["$bs$bs"], '(', "\n", ' ', ')', [$bs]; + + # even backslash can be a delimiter, in when it is, backslashes + # can't be embedded or escaped. + assemble_and_run '', [], $bs, ' ', ' ', $bs, []; + assemble_and_run '', [], $bs, "\n", "\n", $bs, []; + assemble_and_run '', ['a'], $bs, '', ' ', $bs, ['a']; + assemble_and_run ' ', ['a'], $bs, '', ' ', $bs, ['a']; + assemble_and_run "\n", ['a'], $bs, '', ' ', $bs, ['a']; +} + +sub execute_test { + my ( $code, $expected, $msg ) = @_; + + my $d = PPI::Document->new( \$code ); + isa_ok( $d, 'PPI::Document', $msg ); + my $found = $d->find( 'PPI::Token::QuoteLike::Words' ) || []; + is( @$found, 1, "$msg - exactly one qw" ); + is( $found->[0]->content, $code, "$msg content()" ); + is_deeply( [ $found->[0]->literal ], $expected, "$msg literal()" ); + + return; +} + +sub assemble_and_run { + my ( $pre_left_delim, $words_in, $left_delim, $delim_padding, $word_separator, $right_delim, $expected ) = @_; + + my $code = "qw$pre_left_delim$left_delim$delim_padding" . join(' ', @$words_in) . "$delim_padding$right_delim"; + execute_test $code, $expected, $code; + + return; +} + +sub permute_test { + my ( $words_in, $left_delim, $right_delim, $expected ) = @_; + + assemble_and_run "", $words_in, $left_delim, "", " ", $right_delim, $expected; + assemble_and_run "", $words_in, $left_delim, "", "\t", $right_delim, $expected; + assemble_and_run "", $words_in, $left_delim, "", "\n", $right_delim, $expected; + assemble_and_run "", $words_in, $left_delim, "", "\f", $right_delim, $expected; + + assemble_and_run "", $words_in, $left_delim, " ", " ", $right_delim, $expected; + assemble_and_run "", $words_in, $left_delim, "\t", "\t", $right_delim, $expected; + assemble_and_run "", $words_in, $left_delim, "\n", "\n", $right_delim, $expected; + assemble_and_run "", $words_in, $left_delim, "\f", "\f", $right_delim, $expected; + + assemble_and_run " ", $words_in, $left_delim, " ", " ", $right_delim, $expected; + assemble_and_run "\t", $words_in, $left_delim, "\t", "\t", $right_delim, $expected; + assemble_and_run "\n", $words_in, $left_delim, "\n", "\n", $right_delim, $expected; + assemble_and_run "\f", $words_in, $left_delim, "\f", "\f", $right_delim, $expected; + + return; } diff --git a/t/ppi_token_unknown.t b/t/ppi_token_unknown.t new file mode 100644 index 00000000..6f3cd4cb --- /dev/null +++ b/t/ppi_token_unknown.t @@ -0,0 +1,42 @@ +#!/usr/bin/perl + +# Unit testing for PPI::Token::Unknown + +use t::lib::PPI::Test::pragmas; +use Test::More tests => 2; + +use PPI; + + +OPERATOR_MULT_CAST: { + my @tests = ( + { + desc => 'multiply, not cast', + code => '$c{d}*$e', + expected => [ + 'PPI::Statement' => '$c{d}*$e', + 'PPI::Token::Symbol' => '$c', + 'PPI::Structure::Subscript' => '{d}', + 'PPI::Token::Structure' => '{', + 'PPI::Statement::Expression' => 'd', + 'PPI::Token::Word' => 'd', + 'PPI::Token::Structure' => '}', + 'PPI::Token::Operator' => '*', + 'PPI::Token::Symbol' => '$e', + ] + }, + ); + + for my $test ( @tests ) { + my $d = PPI::Document->new( \$test->{code} ); + my $tokens = $d->find( sub { 1 } ); + $tokens = [ map { ref $_, $_->content } @$tokens ]; + my $expected = $test->{expected}; + unshift @$expected, 'PPI::Statement', $test->{code} if $expected->[0] !~ /^PPI::Statement/; + next if is_deeply( $tokens, $expected, $test->{desc} ); + + diag "$test->{code} ($test->{desc})\n"; + diag explain $tokens; + diag explain $test->{expected}; + } +} diff --git a/t/ppi_token_word.t b/t/ppi_token_word.t index 74354265..7f2b91c8 100644 --- a/t/ppi_token_word.t +++ b/t/ppi_token_word.t @@ -2,16 +2,9 @@ # Unit testing for PPI::Token::Word -use strict; -BEGIN { - $| = 1; - $^W = 1; - no warnings 'once'; - $PPI::XS_DISABLE = 1; - $PPI::Lexer::X_TOKENIZER ||= $ENV{X_TOKENIZER}; -} +use t::lib::PPI::Test::pragmas; use Test::More tests => 1756; -use Test::NoWarnings; + use PPI; diff --git a/xt/api.t b/xt/api.t index 2aa31fb1..6a585e43 100644 --- a/xt/api.t +++ b/xt/api.t @@ -2,20 +2,16 @@ # Basic first pass API testing for PPI -use strict; +use t::lib::PPI::Test::pragmas; use Test::More; BEGIN { - $| = 1; - $PPI::XS_DISABLE = 1; - $PPI::XS_DISABLE = 1; # Prevent warning if ( $ENV{RELEASE_TESTING} ) { plan( tests => 2931 ); } else { - plan( skip_all => 'Author tests not required for installation' ); + plan( tests => 2931, skip_all => 'Author tests not required for installation' ); } } -use File::Spec::Functions ':ALL'; -use Test::NoWarnings; + use Test::ClassAPI; use PPI; use PPI::Dumper;