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