1#!/usr/bin/perl -w 2 3use strict; 4use warnings; 5 6BEGIN { 7 unshift @INC, 't/lib'; 8} 9 10use Test::More tests => 94; 11 12use EmptyParser; 13use TAP::Parser::Grammar; 14use TAP::Parser::Iterator::Array; 15 16my $GRAMMAR = 'TAP::Parser::Grammar'; 17 18# Array based iterator that we can push items in to 19package IT; 20 21sub new { 22 my $class = shift; 23 return bless [], $class; 24} 25 26sub next { 27 my $self = shift; 28 return shift @$self; 29} 30 31sub put { 32 my $self = shift; 33 unshift @$self, @_; 34} 35 36sub handle_unicode { } 37 38package main; 39 40my $iterator = IT->new; 41my $parser = EmptyParser->new; 42can_ok $GRAMMAR, 'new'; 43my $grammar = $GRAMMAR->new( { iterator => $iterator, parser => $parser } ); 44isa_ok $grammar, $GRAMMAR, '... and the object it returns'; 45 46# Note: all methods are actually class methods. See the docs for the reason 47# why. We'll still use the instance because that should be forward 48# compatible. 49 50my @V12 = sort qw(bailout comment plan simple_test test version); 51my @V13 = sort ( @V12, 'pragma', 'yaml' ); 52 53can_ok $grammar, 'token_types'; 54ok my @types = sort( $grammar->token_types ), 55 '... and calling it should succeed (v12)'; 56is_deeply \@types, \@V12, '... and return the correct token types (v12)'; 57 58$grammar->set_version(13); 59ok @types = sort( $grammar->token_types ), 60 '... and calling it should succeed (v13)'; 61is_deeply \@types, \@V13, '... and return the correct token types (v13)'; 62 63can_ok $grammar, 'syntax_for'; 64can_ok $grammar, 'handler_for'; 65 66my ( %syntax_for, %handler_for ); 67for my $type (@types) { 68 ok $syntax_for{$type} = $grammar->syntax_for($type), 69 '... and calling syntax_for() with a type name should succeed'; 70 cmp_ok ref $syntax_for{$type}, 'eq', 'Regexp', 71 '... and it should return a regex'; 72 73 ok $handler_for{$type} = $grammar->handler_for($type), 74 '... and calling handler_for() with a type name should succeed'; 75 cmp_ok ref $handler_for{$type}, 'eq', 'CODE', 76 '... and it should return a code reference'; 77} 78 79# Test the plan. Gotta have a plan. 80my $plan = '1..1'; 81like $plan, $syntax_for{'plan'}, 'A basic plan should match its syntax'; 82 83my $method = $handler_for{'plan'}; 84$plan =~ $syntax_for{'plan'}; 85ok my $plan_token = $grammar->$method($plan), 86 '... and the handler should return a token'; 87 88my $expected = { 89 'explanation' => '', 90 'directive' => '', 91 'type' => 'plan', 92 'tests_planned' => 1, 93 'raw' => '1..1', 94 'todo_list' => [], 95}; 96is_deeply $plan_token, $expected, 97 '... and it should contain the correct data'; 98 99can_ok $grammar, 'tokenize'; 100$iterator->put($plan); 101ok my $token = $grammar->tokenize, 102 '... and calling it with data should return a token'; 103is_deeply $token, $expected, 104 '... and the token should contain the correct data'; 105 106# a plan with a skip directive 107 108$plan = '1..0 # SKIP why not?'; 109like $plan, $syntax_for{'plan'}, 'a basic plan should match its syntax'; 110 111$plan =~ $syntax_for{'plan'}; 112ok $plan_token = $grammar->$method($plan), 113 '... and the handler should return a token'; 114 115$expected = { 116 'explanation' => 'why not?', 117 'directive' => 'SKIP', 118 'type' => 'plan', 119 'tests_planned' => 0, 120 'raw' => '1..0 # SKIP why not?', 121 'todo_list' => [], 122}; 123is_deeply $plan_token, $expected, 124 '... and it should contain the correct data'; 125 126$iterator->put($plan); 127ok $token = $grammar->tokenize, 128 '... and calling it with data should return a token'; 129is_deeply $token, $expected, 130 '... and the token should contain the correct data'; 131 132# implied skip 133 134$plan = '1..0'; 135like $plan, $syntax_for{'plan'}, 136 'A plan with an implied "skip all" should match its syntax'; 137 138$plan =~ $syntax_for{'plan'}; 139ok $plan_token = $grammar->$method($plan), 140 '... and the handler should return a token'; 141 142$expected = { 143 'explanation' => '', 144 'directive' => 'SKIP', 145 'type' => 'plan', 146 'tests_planned' => 0, 147 'raw' => '1..0', 148 'todo_list' => [], 149}; 150is_deeply $plan_token, $expected, 151 '... and it should contain the correct data'; 152 153$iterator->put($plan); 154ok $token = $grammar->tokenize, 155 '... and calling it with data should return a token'; 156is_deeply $token, $expected, 157 '... and the token should contain the correct data'; 158 159# bad plan 160 161$plan = '1..0 # TODO 3,4,5'; # old syntax. No longer supported 162unlike $plan, $syntax_for{'plan'}, 163 'Bad plans should not match the plan syntax'; 164 165# Bail out! 166 167my $bailout = 'Bail out!'; 168like $bailout, $syntax_for{'bailout'}, 169 'Bail out! should match a bailout syntax'; 170 171$iterator->put($bailout); 172ok $token = $grammar->tokenize, 173 '... and calling it with data should return a token'; 174$expected = { 175 'bailout' => '', 176 'type' => 'bailout', 177 'raw' => 'Bail out!' 178}; 179is_deeply $token, $expected, 180 '... and the token should contain the correct data'; 181 182$bailout = 'Bail out! some explanation'; 183like $bailout, $syntax_for{'bailout'}, 184 'Bail out! should match a bailout syntax'; 185 186$iterator->put($bailout); 187ok $token = $grammar->tokenize, 188 '... and calling it with data should return a token'; 189$expected = { 190 'bailout' => 'some explanation', 191 'type' => 'bailout', 192 'raw' => 'Bail out! some explanation' 193}; 194is_deeply $token, $expected, 195 '... and the token should contain the correct data'; 196 197# test comment 198 199my $comment = '# this is a comment'; 200like $comment, $syntax_for{'comment'}, 201 'Comments should match the comment syntax'; 202 203$iterator->put($comment); 204ok $token = $grammar->tokenize, 205 '... and calling it with data should return a token'; 206$expected = { 207 'comment' => 'this is a comment', 208 'type' => 'comment', 209 'raw' => '# this is a comment' 210}; 211is_deeply $token, $expected, 212 '... and the token should contain the correct data'; 213 214# test tests :/ 215 216my $test = 'ok 1 this is a test'; 217like $test, $syntax_for{'test'}, 'Tests should match the test syntax'; 218 219$iterator->put($test); 220ok $token = $grammar->tokenize, 221 '... and calling it with data should return a token'; 222 223$expected = { 224 'ok' => 'ok', 225 'explanation' => '', 226 'type' => 'test', 227 'directive' => '', 228 'description' => 'this is a test', 229 'test_num' => '1', 230 'raw' => 'ok 1 this is a test' 231}; 232is_deeply $token, $expected, 233 '... and the token should contain the correct data'; 234 235# TODO tests 236 237$test = 'not ok 2 this is a test # TODO whee!'; 238like $test, $syntax_for{'test'}, 'Tests should match the test syntax'; 239 240$iterator->put($test); 241ok $token = $grammar->tokenize, 242 '... and calling it with data should return a token'; 243 244$expected = { 245 'ok' => 'not ok', 246 'explanation' => 'whee!', 247 'type' => 'test', 248 'directive' => 'TODO', 249 'description' => 'this is a test', 250 'test_num' => '2', 251 'raw' => 'not ok 2 this is a test # TODO whee!' 252}; 253is_deeply $token, $expected, '... and the TODO should be parsed'; 254 255# false TODO tests 256 257# escaping that hash mark ('#') means this should *not* be a TODO test 258$test = 'ok 22 this is a test \# TODO whee!'; 259like $test, $syntax_for{'test'}, 'Tests should match the test syntax'; 260 261$iterator->put($test); 262ok $token = $grammar->tokenize, 263 '... and calling it with data should return a token'; 264 265$expected = { 266 'ok' => 'ok', 267 'explanation' => '', 268 'type' => 'test', 269 'directive' => '', 270 'description' => 'this is a test \# TODO whee!', 271 'test_num' => '22', 272 'raw' => 'ok 22 this is a test \# TODO whee!' 273}; 274is_deeply $token, $expected, 275 '... and the token should contain the correct data'; 276 277# pragmas 278 279my $pragma = 'pragma +strict'; 280like $pragma, $syntax_for{'pragma'}, 'Pragmas should match the pragma syntax'; 281 282$iterator->put($pragma); 283ok $token = $grammar->tokenize, 284 '... and calling it with data should return a token'; 285 286$expected = { 287 'type' => 'pragma', 288 'raw' => $pragma, 289 'pragmas' => ['+strict'], 290}; 291 292is_deeply $token, $expected, 293 '... and the token should contain the correct data'; 294 295$pragma = 'pragma +strict,-foo'; 296like $pragma, $syntax_for{'pragma'}, 'Pragmas should match the pragma syntax'; 297 298$iterator->put($pragma); 299ok $token = $grammar->tokenize, 300 '... and calling it with data should return a token'; 301 302$expected = { 303 'type' => 'pragma', 304 'raw' => $pragma, 305 'pragmas' => [ '+strict', '-foo' ], 306}; 307 308is_deeply $token, $expected, 309 '... and the token should contain the correct data'; 310 311$pragma = 'pragma +strict , -foo '; 312like $pragma, $syntax_for{'pragma'}, 'Pragmas should match the pragma syntax'; 313 314$iterator->put($pragma); 315ok $token = $grammar->tokenize, 316 '... and calling it with data should return a token'; 317 318$expected = { 319 'type' => 'pragma', 320 'raw' => $pragma, 321 'pragmas' => [ '+strict', '-foo' ], 322}; 323 324is_deeply $token, $expected, 325 '... and the token should contain the correct data'; 326 327# coverage tests 328 329# set_version 330 331{ 332 my @die; 333 334 eval { 335 local $SIG{__DIE__} = sub { push @die, @_ }; 336 337 $grammar->set_version('no_such_version'); 338 }; 339 340 unless ( is @die, 1, 'set_version with bad version' ) { 341 diag " >>> $_ <<<\n" for @die; 342 } 343 344 like pop @die, qr/^Unsupported syntax version: no_such_version at /, 345 '... and got expected message'; 346} 347 348# tokenize 349{ 350 my $iterator = IT->new; 351 my $parser = EmptyParser->new; 352 my $grammar 353 = $GRAMMAR->new( { iterator => $iterator, parser => $parser } ); 354 355 my $plan = ''; 356 357 $iterator->put($plan); 358 359 my $result = $grammar->tokenize(); 360 361 isa_ok $result, 'TAP::Parser::Result::Unknown'; 362} 363 364# _make_plan_token 365 366{ 367 my $parser = EmptyParser->new; 368 my $grammar = $GRAMMAR->new( { parser => $parser } ); 369 370 my $plan 371 = '1..1 # SKIP with explanation'; # trigger warning in _make_plan_token 372 373 my $method = $handler_for{'plan'}; 374 375 $plan =~ $syntax_for{'plan'}; # perform regex to populate $1, $2 376 377 my @warn; 378 379 eval { 380 local $SIG{__WARN__} = sub { push @warn, @_ }; 381 382 $grammar->$method($plan); 383 }; 384 385 is @warn, 1, 'catch warning on inconsistent plan'; 386 387 like pop @warn, 388 qr/^Specified SKIP directive in plan but more than 0 tests [(]1\.\.1 # SKIP with explanation[)]/, 389 '... and its what we expect'; 390} 391 392# _make_yaml_token 393 394SKIP: { 395 skip 'Test is broken and needs repairs', 2; 396 my $iterator = IT->new; 397 my $parser = EmptyParser->new; 398 my $grammar 399 = $GRAMMAR->new( { iterator => $iterator, parser => $parser } ); 400 401 $grammar->set_version(13); 402 403 # now this is badly formed YAML that is missing the 404 # leader padding - this is done for coverage testing 405 # the $reader code sub in _make_yaml_token, that is 406 # passed as the yaml consumer to T::P::YAMLish::Reader. 407 408 # because it isnt valid yaml, the yaml document is 409 # not done, and the _peek in the YAMLish::Reader 410 # code doesnt find the terminating '...' pattern. 411 # but we dont care as this is coverage testing, so 412 # if thats what we have to do to exercise that code, 413 # so be it. 414 my $yaml = [ ' --- ', '- 2', ' ... ', ]; 415 416 sub iter { 417 my $ar = shift; 418 return sub { 419 return shift @$ar; 420 }; 421 } 422 423 my $iter = iter($yaml); 424 425 while ( my $line = $iter->() ) { 426 $iterator->put($line); 427 } 428 429 # pad == ' ', marker == '--- ' 430 # length $pad == 3 431 # strip == pad 432 433 my @die; 434 435 eval { 436 local $SIG{__DIE__} = sub { push @die, @_ }; 437 $grammar->tokenize; 438 }; 439 440 is @die, 1, 'checking badly formed yaml for coverage testing'; 441 442 like pop @die, qr/^Missing '[.][.][.]' at end of YAMLish/, 443 '...and it died like we expect'; 444} 445 446{ 447 448 # coverage testing for TAP::Parser::Iterator::Array 449 450 my $source = [qw( a b c )]; 451 452 my $aiter = TAP::Parser::Iterator::Array->new($source); 453 454 my $first = $aiter->next_raw; 455 456 is $first, 'a', 'access raw iterator'; 457 458 is $aiter->exit, undef, '... and note we didnt exhaust the source'; 459} 460