File Coverage

blib/lib/Test/Proto/Role/ArrayRef.pm
Criterion Covered Total %
statement 338 340 99.4
branch 84 88 95.4
condition 7 12 58.3
subroutine 71 71 100.0
pod 38 38 100.0
total 538 549 98.0


line stmt bran cond sub pod time code
1             package Test::Proto::Role::ArrayRef;
2 11     11   11917 use 5.008;
  11         42  
  11         443  
3 11     11   65 use strict;
  11         22  
  11         345  
4 11     11   60 use warnings;
  11         20  
  11         303  
5 11     11   65 use Test::Proto::Common;
  11         22  
  11         1012  
6 11     11   73 use Scalar::Util qw'blessed weaken';
  11         35  
  11         648  
7 11     11   61 use Moo::Role;
  11         37  
  11         83  
8              
9             =head1 NAME
10              
11             Test::Proto::Role::ArrayRef - Role containing test case methods for array refs.
12              
13             =head1 SYNOPSIS
14              
15             package MyProtoClass;
16             use Moo;
17             with 'Test::Proto::Role::ArrayRef';
18              
19             This Moo Role provides methods to Test::Proto::ArrayRef for test case methods that apply to arrayrefs such as C. It can also be used for objects which use overload or otherwise respond to arrayref syntax.
20              
21             =head1 METHODS
22              
23             =head3 map
24              
25             pArray->map(sub { uc shift }, ['A','B'])->ok(['a','b']);
26              
27             Applies the first argument (a coderef) onto each member of the array. The resulting array is compared to the second argument.
28              
29             =cut
30              
31             sub map {
32 3     3 1 44 my ( $self, $code, $expected, $reason ) = @_;
33 3         23 $self->add_test(
34             'map',
35             {
36             code => $code,
37             expected => $expected
38             },
39             $reason
40             );
41             }
42              
43             define_test 'map' => sub {
44 3     3   6 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
45 3         8 my $subject = [ map { $data->{code}->($_) } @{ $self->subject } ];
  6         36  
  3         78  
46 3         28 return upgrade( $data->{expected} )->validate( $subject, $self );
47             };
48              
49             =head3 grep
50              
51             pArray->grep(sub { $_[0] eq uc $_[0] }, ['A'])->ok(['A','b']); # passes
52             pArray->grep(sub { $_[0] eq uc $_[0] }, [])->ok(['a','b']); # passes
53             pArray->grep(sub { $_[0] eq uc $_[0] })->ok(['a','b']); # fails - 'boolean' grep behaves like array_any
54              
55             Applies the first argument (a prototype) onto each member of the array; if it returns true, the member is added to the resulting array. The resulting array is compared to the second argument.
56              
57             =cut
58              
59             sub grep {
60 7     7 1 99 my ( $self, $code, $expected, $reason ) = @_;
61 7 100 100     55 if ( defined $expected and CORE::ref $expected ) { #~ CORE::ref used because boolean grep might have a reason
62 3         25 $self->add_test(
63             'grep',
64             {
65             match => $code,
66             expected => $expected
67             },
68             $reason
69             );
70             }
71             else {
72 4         12 $reason = $expected;
73 4         10261 $self->add_test( 'array_any', { match => $code }, $reason );
74             }
75             }
76              
77             define_test 'grep' => sub {
78 3     3   8 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
79 3         6 my $subject = [ grep { upgrade( $data->{match} )->validate($_) } @{ $self->subject } ];
  6         34  
  3         84  
80 3         19 return upgrade( $data->{expected} )->validate( $subject, $self );
81             };
82              
83             =head3 array_any
84              
85             pArray->array_any(sub { $_[0] eq uc $_[0] })->ok(['A','b']); # passes
86             pArray->array_any(sub { $_[0] eq uc $_[0] })->ok(['a','b']); # fails
87              
88             Applies the first argument (a prototype) onto each member of the array; if any member returns true, the test case succeeds.
89              
90             =cut
91              
92             sub array_any {
93 38     38 1 336 my ( $self, $expected, $reason ) = @_;
94 38         265 $self->add_test( 'array_any', { match => $expected }, $reason );
95             }
96              
97             define_test 'array_any' => sub {
98 42     42   117 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
99 42         95 my $i = 0;
100 42         87 foreach my $single_subject ( @{ $self->subject } ) {
  42         1098  
101 47 100       288 return $self->pass("Item $i matched") if upgrade( $data->{match} )->validate($single_subject);
102 28         920 $i++;
103             }
104 23         119 return $self->fail('None matched');
105             };
106              
107             =head3 array_none
108              
109             pArray->array_none(sub { $_[0] eq uc $_[0] })->ok(['a','b']); # passes
110             pArray->array_none(sub { $_[0] eq uc $_[0] })->ok(['A','b']); # fails
111              
112             Applies the first argument (a prototype) onto each member of the array; if any member returns true, the test case fails.
113              
114             =cut
115              
116             sub array_none {
117 2     2 1 32 my ( $self, $code, $reason ) = @_;
118 2         18 $self->add_test( 'array_none', { code => $code }, $reason );
119             }
120              
121             define_test 'array_none' => sub {
122 2     2   7 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
123 2         6 my $i = 0;
124 2         6 foreach my $single_subject ( @{ $self->subject } ) {
  2         347  
125 3 100       22 return $self->fail("Item $i matched") if upgrade( $data->{code} )->validate($single_subject);
126 2         46 $i++;
127             }
128 1         7 return $self->pass('None matched');
129             };
130              
131             =head3 array_all
132              
133             pArray->array_all(sub { $_[0] eq uc $_[0] })->ok(['A','B']); # passes
134             pArray->array_all(sub { $_[0] eq uc $_[0] })->ok(['A','b']); # fails
135              
136             Applies the first argument (a prototype) onto each member of the array; if any member returns false, the test case fails.
137              
138             =cut
139              
140             sub array_all {
141 2     2 1 420 my ( $self, $code, $reason ) = @_;
142 2         19 $self->add_test( 'array_all', { code => $code }, $reason );
143             }
144              
145             define_test 'array_all' => sub {
146 2     2   6 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
147 2         8 my $i = 0;
148 2         4 foreach my $single_subject ( @{ $self->subject } ) {
  2         61  
149 4 100       26 return $self->fail("Item $i did not match") unless upgrade( $data->{code} )->validate($single_subject);
150 3         92 $i++;
151             }
152 1         9 return $self->pass('All matched');
153             };
154              
155             =head3 reduce
156              
157             pArray->reduce(sub { $_[0] + $_[1] }, 6 )->ok([1,2,3]);
158              
159             Applies the first argument (a coderef) onto the first two elements of the array, and thereafter the next element and the return value of the previous calculation. Similar to List::Util::reduce.
160              
161             =cut
162              
163             sub reduce {
164 3     3 1 52 my ( $self, $code, $expected, $reason ) = @_;
165 3         28 $self->add_test(
166             'reduce',
167             {
168             code => $code,
169             expected => $expected
170             },
171             $reason
172             );
173             }
174              
175             define_test 'reduce' => sub {
176 3     3   8 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
177 3         6 my $length = $#{ $self->subject };
  3         89  
178 3 100       82 return $self->exception( 'Cannot use reduce unless the subject has at least two elements; only ' . ( $length + 1 ) . ' found' ) unless $length;
179 2         2 my $left = ${ $self->subject }[0];
  2         58  
180 2         5 my $right;
181 2         5 my $i = 1;
182 2         9 while ( $i <= $length ) {
183 4         8 $right = ${ $self->subject }[$i];
  4         119  
184 4         18 $left = $data->{code}->( $left, $right );
185 4         24 $i++;
186             }
187 2         13 return upgrade( $data->{expected} )->validate( $left, $self );
188             };
189              
190             =head3 nth
191              
192             pArray->nth(1,'b')->ok(['a','b']);
193              
194             Finds the nth item (where n is the first argument) and compares the result to the prototype provided in the second argument.
195              
196             =cut
197              
198             sub nth {
199 195     195 1 2324 my ( $self, $index, $expected, $reason ) = @_;
200 195         1573 $self->add_test(
201             'nth',
202             {
203             'index' => $index,
204             expected => $expected
205             },
206             $reason
207             );
208             }
209              
210             define_test nth => sub {
211 195     195   396 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
212 195 100       5848 if ( exists $self->subject->[ $data->{'index'} ] ) {
213 194         4934 my $subject = $self->subject->[ $data->{'index'} ];
214 194         967 return upgrade( $data->{expected} )->validate( $subject, $self );
215             }
216             else {
217 1         9 return $self->fail( 'The index ' . $data->{'index'} . ' does not exist.' );
218             }
219             };
220              
221             =head3 count_items
222              
223             pArray->count_items(2)->ok(['a','b']);
224              
225             Finds the length of the array (i.e. the number of items) and compares the result to the prototype provided in the argument.
226              
227             =cut
228              
229             sub count_items {
230 115     115 1 1164 my ( $self, $expected, $reason ) = @_;
231 115         696 $self->add_test( 'count_items', { expected => $expected }, $reason );
232             }
233              
234             define_test count_items => sub {
235 115     115   367 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
236 115         209 my $subject = scalar @{ $self->subject };
  115         3198  
237 115         657 return upgrade( $data->{expected} )->validate( $subject, $self );
238             };
239              
240             =head3 enumerated
241              
242             pArray->enumerated($tests_enumerated)->ok(['a','b']);
243              
244             Produces the indices and values of the subject as an array reference, and tests them against the prototype provided in the argument.
245              
246             In the above example, the prototype C<$tests_enumerated> should return a pass for C<[[0,'a'],[1,'b']]>.
247              
248             =cut
249              
250             sub enumerated {
251 4     4 1 40 my ( $self, $expected, $reason ) = @_;
252 4         31 $self->add_test( 'enumerated', { expected => $expected }, $reason );
253             }
254              
255             define_test 'enumerated' => sub {
256 4     4   11 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
257 4         41 my $subject = [];
258 4         8 push @$subject, [ $_, $self->subject->[$_] ] foreach ( 0 .. $#{ $self->subject } );
  4         109  
259 4         25 return upgrade( $data->{expected} )->validate( $subject, $self );
260             };
261              
262             =head3 in_groups
263              
264             pArray->in_groups(2,[['a','b'],['c','d'],['e']])->ok(['a','b','c','d','e']);
265              
266             Bundles the contents in groups of n (where n is the first argument), puts each group in an arrayref, and compares the resulting arrayref to the prototype provided in the second argument.
267              
268             =cut
269              
270             sub in_groups {
271 7     7 1 84 my ( $self, $groups, $expected, $reason ) = @_;
272 7         49 $self->add_test(
273             'in_groups',
274             {
275             'groups' => $groups,
276             expected => $expected
277             },
278             $reason
279             );
280             }
281              
282             define_test in_groups => sub {
283 7     7   18 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
284 7 100       34 return $self->exception('in_groups needs groups of 1 or more') if $data->{'groups'} < 1;
285 6         11 my $newArray = [];
286 6         14 my $i = 0;
287 6         12 my $currentGroup = [];
288 6         10 foreach my $item ( @{ $self->subject } ) {
  6         136  
289 22 100       56 if ( 0 == ( $i % $data->{'groups'} ) ) {
290 14 100       39 push @$newArray, $currentGroup if @$currentGroup;
291 14         22 $currentGroup = [];
292             }
293 22         36 push @$currentGroup, $item;
294 22         37 $i++;
295             }
296 6 100       22 push @$newArray, $currentGroup if @$currentGroup;
297 6         31 return upgrade( $data->{expected} )->validate( $newArray, $self );
298             };
299              
300             =head3 group_when
301              
302             pArray->group_when(sub {$_[eq uc $_[0]}, [['A'],['B','c','d'],['E']])->ok(['A','B','c','d','E']);
303             pArray->group_when(sub {$_[0] eq $_[0]}, [['a','b','c','d','e']])->ok(['a','b','c','d','e']);
304              
305             Bundles the contents of the test subject in groups; a new group is created when the member matches the first argument (a prototype). The resulting arrayref is compared to the second argument.
306              
307             =cut
308              
309             sub group_when {
310 3     3 1 44 my ( $self, $condition, $expected, $reason ) = @_;
311 3         24 $self->add_test(
312             'group_when',
313             {
314             'condition' => $condition,
315             expected => $expected,
316             must_match => 'value'
317             },
318             $reason
319             );
320             }
321              
322             =head3 group_when_index
323              
324             pArray->group_when_index(p(0)|p(1)|p(4), [['A'],['B','c','d'],['E']])->ok(['A','B','c','d','E']);
325             pArray->group_when_index(p->num_gt(2), [['a','b','c','d','e']])->ok(['a','b','c','d','e']);
326              
327             Bundles the contents of the test subject in groups; a new group is created when the index matches the first argument (a prototype). The resulting arrayref is compared to the second argument.
328              
329             =cut
330              
331             sub group_when_index {
332 3     3 1 10 my ( $self, $condition, $expected, $reason ) = @_;
333 3         26 $self->add_test(
334             'group_when',
335             {
336             'condition' => $condition,
337             expected => $expected,
338             must_match => 'index'
339             },
340             $reason
341             );
342             }
343              
344             define_test group_when => sub {
345 6     6   16 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
346 6         16 my $newArray = [];
347 6         15 my $currentGroup = [];
348 6         31 my $condition = upgrade( $data->{condition} );
349 6         16 my $i = 0;
350 6         11 foreach my $item ( @{ $self->subject } ) {
  6         148  
351 30         56 my $got = $item;
352 30 100       147 $got = $i if $data->{must_match} =~ /index/;
353 30 100       118 if ( $condition->validate($got) ) {
354 7 100 66     77 push @$newArray, $currentGroup if defined $currentGroup and @$currentGroup;
355 7         17 $currentGroup = [];
356             }
357 30         295 push @$currentGroup, $item;
358 30         87 $i++;
359             }
360 6 50 33     59 push @$newArray, $currentGroup if defined $currentGroup and @$currentGroup;
361 6         40 return upgrade( $data->{expected} )->validate( $newArray, $self );
362             };
363              
364             =head3 indexes_of
365              
366             pArray->indexes_of('a', [0,2])->ok(['a','b','a']);
367              
368             Finds the indexes which match the first argument, and compares that list as an arrayref with the second list.
369              
370             =cut
371              
372             sub indexes_of {
373 3     3 1 53 my ( $self, $match, $expected, $reason ) = @_;
374 3         27 $self->add_test(
375             'indexes_of',
376             {
377             match => $match,
378             expected => $expected
379             },
380             $reason
381             );
382             }
383              
384             define_test indexes_of => sub {
385 3     3   9 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
386 3         10 my $indexes = [];
387 3         7 for my $i ( 0 .. $#{ $self->subject } ) {
  3         81  
388 7 100       47 push @$indexes, $i if upgrade( $data->{match} )->validate( $self->subject->[$i], $self->subtest( status_message => "Testing index $i" ) );
389             }
390 3         23 my $result = upgrade( $data->{expected} )->validate( $indexes, $self->subtest( status_message => 'Checking indexes against expected list' ) );
391 3 100       22 return $self->pass if $result;
392 1         8 return $self->fail;
393             };
394              
395             =head3 array_eq
396              
397             pArray->array_eq(['a','b'])->ok(['a','b']);
398              
399             Compares the elements of the test subject with the elements of the first argument, using the C feature.
400              
401             =cut
402              
403             sub array_eq {
404 114     114 1 312 my ( $self, $expected, $reason ) = @_;
405 114         641 $self->add_test( 'array_eq', { expected => $expected }, $reason );
406             }
407              
408             define_test array_eq => sub {
409 111     111   292 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
410 111         196 my $length = scalar @{ $data->{expected} };
  111         352  
411 111         8089 my $length_result = Test::Proto::ArrayRef->new()->count_items($length)->validate( $self->subject, $self->subtest );
412 111 100       769 if ($length_result) {
413 99         528 foreach my $i ( 0 .. ( $length - 1 ) ) {
414              
415             #upgrade($data->{expected}->[$i])->validate($self->subject->[$i], $self);
416 192         5458 Test::Proto::ArrayRef->new()->nth( $i, $data->{expected}->[$i] )->validate( $self->subject, $self->subtest );
417             }
418             }
419 111         599 $self->done;
420             };
421              
422             =head3 range
423              
424             pArray->range('1,3..4',[9,7,6,5])->ok([10..1]);
425              
426             Finds the range specified in the first element, and compares them to the second element.
427              
428             =cut
429              
430             sub range {
431 16     16 1 215 my ( $self, $range, $expected, $reason ) = @_;
432 16         141 $self->add_test(
433             'range',
434             {
435             range => $range,
436             expected => $expected
437             },
438             $reason
439             );
440             }
441              
442             define_test range => sub {
443 16     16   41 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
444 16         54 my $range = $data->{range};
445 16         38 my $result = [];
446 16         44 my $length = scalar @{ $self->subject };
  16         436  
447 16         83 $range =~ s/-(\d+)/$length - $1/ge;
  4         23  
448 16         45 $range =~ s/\.\.$/'..' . ($length - 1)/e;
  0         0  
449 16         38 $range =~ s/^\.\./0../;
450 16 100       152 return $self->exception('Invalid range specified') unless $range =~ m/^(?:\d+|\d+..\d+)(?:,(\d+|\d+..\d+))*$/;
451 14         1375 my @range = eval("($range)"); # surely there is a better way?
452              
453 14         90 foreach my $i (@range) {
454 40 100       1159 return $self->fail("Element $i does not exist") unless exists $self->subject->[$i];
455 39         923 push( @$result, $self->subject->[$i] );
456             }
457 13         88 return upgrade( $data->{expected} )->validate( $result, $self );
458             };
459              
460             =head3 reverse
461              
462             pArray->reverse([10..1])->ok([1..10]);
463              
464             Reverses the order of elements and compares the result to the prototype given.
465              
466             =cut
467              
468             sub reverse {
469 2     2 1 25 my ( $self, $expected, $reason ) = @_;
470 2         17 $self->add_test( 'reverse', { expected => $expected }, $reason );
471             }
472              
473             define_test reverse => sub {
474 2     2   6 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
475 2         6 my $reversed = [ CORE::reverse @{ $self->subject } ];
  2         59  
476 2         12 return upgrade( $data->{expected} )->validate( $reversed, $self );
477             };
478              
479             =head3 array_before
480              
481             pArray->array_before('b',['a'])->ok(['a','b']); # passes
482              
483             Applies the first argument (a prototype) onto each member of the array; if any member returns true, the second argument is validated against a new arrayref containing all the preceding members of the array.
484              
485             =cut
486              
487             sub array_before {
488 4     4 1 39 my ( $self, $match, $expected, $reason ) = @_;
489 4         31 $self->add_test(
490             'array_before',
491             {
492             match => $match,
493             expected => $expected
494             },
495             $reason
496             );
497             }
498              
499             =head3 array_before_inclusive
500              
501             pArray->array_before_inclusive('b',['a', 'b'])->ok(['a','b', 'c']); # passes
502              
503             Applies the first argument (a prototype) onto each member of the array; if any member returns true, the second argument is validated against a new arrayref containing all the preceding members of the array, plus the element matched.
504              
505             =cut
506              
507             sub array_before_inclusive {
508 4     4 1 43 my ( $self, $match, $expected, $reason ) = @_;
509 4         40 $self->add_test(
510             'array_before',
511             {
512             match => $match,
513             expected => $expected,
514             include_self => 1
515             },
516             $reason
517             );
518             }
519              
520             define_test 'array_before' => sub {
521 8     8   25 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
522 8         26 my $i = 0;
523 8         12 foreach my $single_subject ( @{ $self->subject } ) {
  8         218  
524 22 100       124 if ( upgrade( $data->{match} )->validate($single_subject) ) {
525              
526             # $self->add_info("Item $i matched")
527 6         25 my $before = [ @{ $self->subject }[ 0 .. $i ] ];
  6         155  
528 6 100       36 pop @$before unless $data->{include_self};
529 6         32 return upgrade( $data->{expected} )->validate( $before, $self );
530             }
531 16         617 $i++;
532             }
533 2         10 return $self->fail('None matched');
534             };
535              
536             =head3 array_after
537              
538             pArray->array_after('a',['b'])->ok(['a','b']); # passes
539              
540             Applies the first argument (a prototype) onto each member of the array; if any member returns true, the second argument is validated against a new arrayref containing all the following members of the array.
541              
542             =cut
543              
544             sub array_after {
545 4     4 1 39 my ( $self, $match, $expected, $reason ) = @_;
546 4         32 $self->add_test(
547             'array_after',
548             {
549             match => $match,
550             expected => $expected
551             },
552             $reason
553             );
554             }
555              
556             =head3 array_after_inclusive
557              
558             pArray->array_after_inclusive('b',['b','c'])->ok(['a','b','c']); # passes
559              
560             Applies the first argument (a prototype) onto each member of the array; if any member returns true, the second argument is validated against a new arrayref containing the element matched, plus all the following members of the array.
561              
562             =cut
563              
564             sub array_after_inclusive {
565 4     4 1 36 my ( $self, $match, $expected, $reason ) = @_;
566 4         31 $self->add_test(
567             'array_after',
568             {
569             match => $match,
570             expected => $expected,
571             include_self => 1
572             },
573             $reason
574             );
575             }
576              
577             define_test 'array_after' => sub {
578 8     8   20 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
579 8         19 my $i = 0;
580 8         21 foreach my $single_subject ( @{ $self->subject } ) {
  8         210  
581 26 100       134 if ( upgrade( $data->{match} )->validate($single_subject) ) {
582              
583             # $self->add_info("Item $i matched")
584 6         14 my $last_index = $#{ $self->subject };
  6         153  
585 6         24 my $after = [ @{ $self->subject }[ $i .. $last_index ] ];
  6         145  
586 6 100       36 shift @$after unless $data->{include_self};
587 6         35 return upgrade( $data->{expected} )->validate( $after, $self );
588             }
589 20         685 $i++;
590             }
591 2         9 return $self->fail('None matched');
592             };
593              
594             =head3 sorted
595              
596             pArray->sorted(['a','c','e'])->ok(['a','e','c']); # passes
597             pArray->sorted([2,10,11], cNumeric)->ok([11,2,10]); # passes
598              
599             This will sort the subject and compare the result against the protoype.
600              
601             =cut
602              
603             sub sorted {
604 6     6 1 70 my ( $self, $expected, $compare, $reason ) = @_;
605 6         49 $self->add_test(
606             'sorted',
607             {
608             compare => $compare,
609             expected => $expected
610             },
611             $reason
612             );
613             }
614              
615             define_test 'sorted' => sub {
616 6     6   14 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
617 6         31 my $compare = upgrade_comparison( $data->{compare} );
618              
619             #my $got = [sort { $compare->($a, $b) } @{$self->subject}];
620 6         30 my $got = [ sort { $compare->compare( $a, $b ) } @{ $self->subject } ];
  12         132  
  6         163  
621              
622 6         40 return upgrade( $data->{expected} )->validate( $got, $self );
623             };
624              
625             =head3 ascending
626              
627             pArray->ascending->ok(['a','c','e']); # passes
628             pArray->ascending->ok(['a','c','c','e']); # passes
629             pArray->ascending(cNumeric)->ok([2,10,11]); # passes
630              
631             This will return true if the elements are already in ascending order. Elements which compare as equal as the previous element are permitted.
632              
633             =cut
634              
635             sub ascending {
636 4     4 1 30 my ( $self, $compare, $reason ) = @_;
637 4         31 $self->add_test(
638             'in_order',
639             {
640             compare => $compare,
641             dir => 'ascending'
642             },
643             $reason
644             );
645             }
646              
647             =head3 descending
648              
649             pArray->descending->ok(['e','c','a']); # passes
650             pArray->descending->ok(['e','c','c','a']); # passes
651             pArray->descending(cNumeric)->ok([11,10,2]); # passes
652              
653             This will return true if the elements are already in descending order. Elements which compare as equal as the previous element are permitted.
654              
655             =cut
656              
657             sub descending {
658 5     5 1 40 my ( $self, $compare, $reason ) = @_;
659 5         47 $self->add_test(
660             'in_order',
661             {
662             compare => $compare,
663             dir => 'descending'
664             },
665             $reason
666             );
667             }
668              
669             define_test 'in_order' => sub {
670 9     9   19 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
671 9 100       15 return $self->pass('Empty array is ascending by definition') if $#{ $self->subject } == -1;
  9         371  
672 5 50       9 return $self->pass('Single-item array is ascending by definition') if $#{ $self->subject } == 0;
  5         120  
673 5 50       22 my $dir = defined $data->{dir} ? $data->{dir} : 'ascending';
674 5         25 my $compare = upgrade_comparison( $data->{compare} );
675 5         27 my @range = 0 .. $#{ $self->subject };
  5         122  
676 5 100       19 @range = CORE::reverse(@range) if $dir eq 'descending';
677 5         13 my $prev = shift @range;
678              
679 5         12 for my $i (@range) {
680 9         29 $self->subtest->diag("Comparing items $prev and $i");
681 9         204 my $result = $compare->le( $self->subject->[$prev], $self->subject->[$i] );
682 9 100       46 return $self->fail("Item $prev > item $i") unless $result;
683 7         19 $prev = $i;
684             }
685 3         14 return $self->pass;
686             };
687              
688             =head3 array_max
689              
690             pArray->array_max('e')->ok(['a','e','c']); # passes
691             pArray->array_max(p->num_gt(10), cNumeric)->ok(['2','11','10']); # passes
692              
693             This will find the maximum value using the optional comparator in the second argument, and check it against the first argument.
694              
695             =cut
696              
697             sub array_max {
698 11     11 1 101 my ( $self, $expected, $compare, $reason ) = @_;
699 11         105 $self->add_test(
700             'array_best',
701             {
702             expected => $expected,
703             must_match => 'any',
704             compare => $compare,
705             dir => 'max'
706             },
707             $reason
708             );
709             }
710              
711             =head3 array_min
712              
713             pArray->array_min('a')->ok(['a','e','c']); # passes
714             pArray->array_min(p->num_lt(10), cNumeric)->ok(['2','11','10']); # passes
715              
716             This will find the minimum value using the optional comparator in the second argument, and check it against the first argument.
717              
718             =cut
719              
720             sub array_min {
721 11     11 1 115 my ( $self, $expected, $compare, $reason ) = @_;
722 11         115 $self->add_test(
723             'array_best',
724             {
725             expected => $expected,
726             must_match => 'any',
727             compare => $compare,
728             dir => 'min'
729             },
730             $reason
731             );
732             }
733              
734             =head3 array_index_of_max
735              
736             pArray->array_index_of_max(1)->ok(['a','e','c']); # passes
737             pArray->array_index_of_max(1, cNumeric)->ok(['2','11','10']); # passes
738              
739             This will find the index of the maximum value using the optional comparator in the second argument, and check it against the first argument.
740              
741             =cut
742              
743             sub array_index_of_max {
744 11     11 1 100 my ( $self, $expected, $compare, $reason ) = @_;
745 11         116 $self->add_test(
746             'array_best',
747             {
748             expected => $expected,
749             must_match => 'any index',
750             compare => $compare,
751             dir => 'max'
752             },
753             $reason
754             );
755             }
756              
757             =head3 array_index_of_min
758              
759             pArray->array_index_of_min(0)->ok(['a','e','c']); # passes
760             pArray->array_index_of_min(0, cNumeric)->ok(['2','11','10']); # passes
761              
762             This will find the index of the minimum value using the optional comparator in the second argument, and check it against the first argument.
763              
764             =cut
765              
766             sub array_index_of_min {
767 11     11 1 97 my ( $self, $expected, $compare, $reason ) = @_;
768 11         115 $self->add_test(
769             'array_best',
770             {
771             expected => $expected,
772             must_match => 'any index',
773             compare => $compare,
774             dir => 'min'
775             },
776             $reason
777             );
778             }
779              
780             define_test 'array_best' => sub {
781 44     44   95 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
782 44         94 my $i = 0;
783 44 100       69 return $self->fail('Empty array has no max by definition') if $#{ $self->subject } == -1;
  44         1088  
784 36         199 my $compare = upgrade_comparison( $data->{compare} );
785 36   33 42   468 my $better = ( defined $data->{dir} and $data->{dir} eq 'min' ? sub { shift() > 0 } : sub { shift() < 0 } );
  42         176  
  42         175  
786 36         925 my $best = [ $self->subject->[0] ];
787 36         118 my $best_idx = [0];
788 36         70 foreach my $single_subject ( @{ $self->subject } ) {
  36         874  
789              
790 120 100       304 if ( $i != 0 ) {
791 84         597 my $cmp_result = $compare->compare( $best->[0], $single_subject );
792 84 100       429 if ( $better->($cmp_result) ) {
    100          
793 48         122 $best = [$single_subject];
794 48         150 $best_idx = [$i];
795             }
796             elsif ( $cmp_result == 0 ) {
797 12         33 push @$best, $single_subject;
798 12         30 push @$best_idx, $i;
799             }
800             }
801 120         258 $i++;
802             }
803 36         62 my $got = $best;
804 36 100       207 $got = $best_idx if $data->{must_match} =~ 'index';
805 36 50       176 if ( $data->{must_match} =~ 'any' ) {
806 36         811 return Test::Proto::ArrayRef->new()->array_any( $data->{expected} )->validate( $got, $self );
807             }
808             else {
809 0         0 return upgrade( $data->{expected} )->validate( $got, $self );
810             }
811             };
812              
813             =head3 array_all_unique
814              
815             pArray->array_all_unique->ok(['a','b','c']); # passes
816             pArray->array_all_unique(cNumeric)->ok(['0','0e0','0.0']); # fails
817              
818             This will pass if all of the members of the array are unique, using the comparison provided (or cmp).
819              
820             =cut
821              
822             sub array_all_unique {
823 4     4 1 26 my ( $self, $compare, $reason ) = @_;
824 4         21 $self->add_test( 'array_all_unique', { compare => $compare }, $reason );
825             }
826              
827             define_test 'array_all_unique' => sub {
828 4     4   6 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
829 4         7 my $i = 0;
830 4         16 my $compare = upgrade_comparison( $data->{compare} );
831 4 100       18 return $self->pass('Empty array unique by definition') if $#{ $self->subject } == -1;
  4         85  
832 3 100       4 return $self->pass('Array with one element unique by definition') if $#{ $self->subject } == 0;
  3         70  
833 2         4 foreach my $single_subject ( @{ $self->subject } ) {
  2         45  
834 6 100       15 if ( $i != 0 ) {
835 4 100       94 return $self->fail("Item $i matches item 0") if $compare->eq( $self->subject->[0], $single_subject );
836             }
837 5         13 $i++;
838             }
839 1         11 return $self->pass('All unique');
840             };
841              
842             =head3 array_all_same
843              
844             pArray->array_all_same->ok(['a','a']); # passes
845             pArray->array_all_same(cNumeric)->ok(['0','0e0','0.0']); # passes
846             pArray->array_all_same->ok(['0','0e0','0.0']); # fails
847              
848             This will pass if all of the members of the array are the same, using the comparison provided (or cmp).
849              
850             =cut
851              
852             sub array_all_same {
853 4     4 1 25 my ( $self, $compare, $reason ) = @_;
854 4         20 $self->add_test( 'array_all_same', { compare => $compare }, $reason );
855             }
856              
857             define_test 'array_all_same' => sub {
858 4     4   6 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
859 4         8 my $i = 0;
860 4         15 my $compare = upgrade_comparison( $data->{compare} );
861 4 100       21 return $self->pass('Empty array all same by definition') if $#{ $self->subject } == -1;
  4         88  
862 3 100       4 return $self->pass('Array with one element all same by definition') if $#{ $self->subject } == 0;
  3         69  
863 2         4 foreach my $single_subject ( @{ $self->subject } ) {
  2         56  
864 6 100       19 if ( $i != 0 ) {
865 4 100       107 return $self->fail("Item $i does not match item 0") if $compare->ne( $self->subject->[0], $single_subject );
866             }
867 5         14 $i++;
868             }
869 1         7 return $self->pass('All the same');
870             };
871              
872             =head2 Unordered Comparisons
873              
874             These methods are useful for when you know what the array should contain but do not know what order the elements are in, for example when testing the keys of a hash.
875              
876             The principle is similar to the C and C tests documented L, but does not use the same implementation and does not suffer from the known bug documented there.
877              
878             =cut
879              
880             =head3 set_of
881              
882             pArray->set_of(['a','b','c'])->ok(['a','c','a','b']); # passes
883              
884             Checks that all of the elements in the test subject match at least one element in the first argument, and vice versa. Members of the test subject may be 'reused'.
885              
886             =cut
887              
888             sub set_of {
889 8     8 1 77 my ( $self, $expected, $reason ) = @_;
890 8         70 $self->add_test(
891             'unordered_comparison',
892             {
893             expected => $expected,
894             method => 'set'
895             },
896             $reason
897             );
898             }
899              
900             =head3 bag_of
901              
902             pArray->bag_of(['a','b','c'])->ok(['c','a','b']); # passes
903              
904             Checks that all of the elements in the test subject match at least one element in the first argument, and vice versa. Members may B be 'reused'.
905              
906             =cut
907              
908             sub bag_of {
909 11     11 1 109 my ( $self, $expected, $reason ) = @_;
910 11         90 $self->add_test(
911             'unordered_comparison',
912             {
913             expected => $expected,
914             method => 'bag'
915             },
916             $reason
917             );
918             }
919              
920             =head3 subset_of
921              
922             pArray->subset_of(['a','b','c'])->ok(['a','a','b']); # passes
923              
924             Checks that all of the elements in the test subject match at least one element in the first argument. Members of the test subject may be 'reused'.
925              
926             =cut
927              
928             sub subset_of {
929 8     8 1 78 my ( $self, $expected, $reason ) = @_;
930 8         66 $self->add_test(
931             'unordered_comparison',
932             {
933             expected => $expected,
934             method => 'subset'
935             },
936             $reason
937             );
938             }
939              
940             =head3 superset_of
941              
942             pArray->superset_of(['a','b','a'])->ok(['a','b','c']); # passes
943              
944             Checks that all of the elements in the first argument can validate at least one element in the test subject. Members of the test subject may be 'reused'.
945              
946             =cut
947              
948             sub superset_of {
949 8     8 1 81 my ( $self, $expected, $reason ) = @_;
950 8         67 $self->add_test(
951             'unordered_comparison',
952             {
953             expected => $expected,
954             method => 'superset'
955             },
956             $reason
957             );
958             }
959              
960             =head3 subbag_of
961              
962             pArray->subbag_of(['a','b','c'])->ok(['a','b']); # passes
963              
964             Checks that all of the elements in the test subject match at least one element in the first argument. Members of the test subject may B be 'reused'.
965              
966             =cut
967              
968             sub subbag_of {
969 8     8 1 83 my ( $self, $expected, $reason ) = @_;
970 8         74 $self->add_test(
971             'unordered_comparison',
972             {
973             expected => $expected,
974             method => 'subbag'
975             },
976             $reason
977             );
978             }
979              
980             =head3 superbag_of
981              
982             pArray->superbag_of(['a','b'])->ok(['a','b','c']); # passes
983              
984             Checks that all of the elements in the first argument can validate at least one element in the test subject. Members of the test subject may B be 'reused'.
985              
986             =cut
987              
988             sub superbag_of {
989 8     8 1 78 my ( $self, $expected, $reason ) = @_;
990 8         66 $self->add_test(
991             'unordered_comparison',
992             {
993             expected => $expected,
994             method => 'superbag'
995             },
996             $reason
997             );
998             }
999              
1000             my $machine;
1001             define_test 'unordered_comparison' => sub {
1002 51     51   172 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
1003 51         1422 return $machine->( $self, $data->{method}, $self->subject, $data->{expected} );
1004             };
1005              
1006             my ( $allocate_l, $allocate_r );
1007             $allocate_l = sub {
1008             my ( $matrix, $pairs, $bag ) = @_;
1009             my $best = $pairs;
1010             LEFT: foreach my $l ( 0 .. $#{$matrix} ) {
1011             next LEFT if grep { $_->[0] == $l } @$pairs; # skip if already allocated
1012             RIGHT: foreach my $r ( 0 .. $#{ $matrix->[$l] } ) {
1013             next RIGHT if $bag and grep { $_->[1] == $r } @$pairs; # skip if already allocated and bag logic
1014             if ( $matrix->[$l]->[$r] ) {
1015             my $result = $allocate_l->( $matrix, [ @$pairs, [ $l, $r ] ], $bag );
1016             $best = $result if ( @$result > @$best );
1017              
1018             # short circuit if length of Best == length of matrix ?
1019             }
1020             }
1021             }
1022             return $best;
1023             };
1024             $allocate_r = sub {
1025             my ( $matrix, $pairs, $bag ) = @_;
1026             my $best = $pairs;
1027             RIGHT: foreach my $r ( 0 .. $#{ $matrix->[0] } ) {
1028             next RIGHT if grep { $_->[1] == $r } @$pairs; # skip if already allocated
1029             LEFT: foreach my $l ( 0 .. $#{$matrix} ) {
1030             next LEFT if $bag and grep { $_->[0] == $l } @$pairs; # skip if already allocated and bag logic
1031             if ( $matrix->[$l]->[$r] ) {
1032             my $result = $allocate_r->( $matrix, [ @$pairs, [ $l, $r ] ], $bag );
1033             $best = $result if ( @$result > @$best );
1034             }
1035             }
1036             }
1037             return $best;
1038             };
1039             $machine = sub {
1040             my ( $runner, $method, $left, $right ) = @_;
1041             my $bag = ( $method =~ /bag$/ );
1042             my $matrix = [];
1043             my $super = ( $method =~ m/^super/ );
1044              
1045             # prepare the results matrix
1046             LEFT: foreach my $l ( 0 .. $#{$left} ) {
1047             RIGHT: foreach my $r ( 0 .. $#{$right} ) {
1048             my $result = upgrade( $right->[$r] )->validate( $left->[$l], ); #$runner->subtest("Comparing subject->[$l] and expected->[$r]"));
1049             $matrix->[$l]->[$r] = $result;
1050             }
1051             }
1052             my $pairs = [];
1053              
1054             my $allocation_l = $allocate_l->( $matrix, $pairs, $bag );
1055             my $allocation_r = $allocate_r->( $matrix, $pairs, $bag );
1056              
1057             if ( $method =~ m/^(sub|)(bag|set)$/ ) {
1058             foreach my $l ( 0 .. $#{$left} ) {
1059             unless ( grep { $_->[0] == $l } @$allocation_l ) {
1060             return $runner->fail('Not a superbag') if $bag;
1061             return $runner->fail('Not a superset');
1062             }
1063              
1064             }
1065             }
1066             if ( $method =~ m/^(super|)(bag|set)$/ ) {
1067             foreach my $r ( 0 .. $#{$right} ) {
1068             unless ( grep { $_->[1] == $r } @$allocation_r ) {
1069             return $runner->fail('Not a superbag') if $bag;
1070             return $runner->fail('Not a superset');
1071             }
1072             }
1073             }
1074             return $runner->pass("Successful");
1075             };
1076              
1077             =head2 Series Validation
1078              
1079             Sometimes you need to check an array matches a certain complex 'pattern' including multiple units of variable length, like in a regular expression or an XML DTD or Schema. Using L, L, and L, you can describe these units, and the methods below can be used to iterate over such a structure.
1080              
1081             =cut
1082              
1083             #~ Series handling
1084              
1085             =head3 contains_only
1086              
1087             pArray->contains_only(pSeries(pRepeatable(pAlternation('a', 'b'))->max(5)))->ok(['a','a','a']); # passes
1088              
1089             This passes if the series expected matches exactly the test subject, i.e. the series can legally stop at the point where the subject ends.
1090              
1091             =cut
1092              
1093             my ( $bt_core, $bt_advance, $bt_eval_step, $bt_backtrack, $bt_backtrack_to );
1094              
1095             sub contains_only {
1096 28     28 1 304 my ( $self, $expected, $reason ) = @_;
1097 28         233 $self->add_test( 'contains_only', { expected => $expected }, $reason );
1098             }
1099              
1100             define_test 'contains_only' => sub {
1101 28     28   73 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
1102 28         718 return $bt_core->( $self, $self->subject, $data->{expected} );
1103             };
1104              
1105             =head3 begins_with
1106              
1107             pArray->begins_with(pSeries('a','a',pRepeatable('a')->max(2)))->ok(['a','a','a']); # passes
1108              
1109             This passes if the full value of the series expected matches the test subject with some elements of the test subject optionally left over at the end.
1110              
1111             =cut
1112              
1113             sub begins_with {
1114 28     28 1 334 my ( $self, $expected, $reason ) = @_;
1115 28         211 $self->add_test( 'begins_with', { expected => $expected }, $reason );
1116             }
1117              
1118             define_test 'begins_with' => sub {
1119 28     28   75 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
1120 28         68 for my $i ( 0 .. $#{ $self->subject } ) {
  28         679  
1121 44         145 my $subset = [ @{ $self->subject }[ 0 .. $i ] ];
  44         3316  
1122 44 100       418 return $self->pass("Succeeded with 0..$i") if $bt_core->( $self->subtest( subject => $subset ), $subset, $data->{expected} );
1123             }
1124 7         36 return $self->fail("No subsets passed");
1125             };
1126              
1127             =head3 ends_with
1128              
1129             pArray->ends_with(pSeries('b','c')->ok(['a','b','c']); # passes
1130              
1131             This passes if the full value of the series expected matches the final items of the test subject with some elements of the test subject optionally preceding.
1132              
1133             =cut
1134              
1135             sub ends_with {
1136 28     28 1 277 my ( $self, $expected, $reason ) = @_;
1137 28         196 $self->add_test( 'ends_with', { expected => $expected }, $reason );
1138             }
1139              
1140             define_test 'ends_with' => sub {
1141 28     28   97 my ( $self, $data, $reason ) = @_; # self is the runner, NOT the prototype
1142 28         74 for my $i ( CORE::reverse( 0 .. $#{ $self->subject } ) ) {
  28         688  
1143 44         108 my $subset = [ @{ $self->subject }[ $i .. $#{ $self->subject } ] ];
  44         1041  
  44         1103  
1144 44 100       210 return $self->pass( "Succeeded with " . $i . ".." . $#{ $self->subject } ) if $bt_core->( $self->subtest( subject => $subset ), $subset, $data->{expected} );
  19         524  
1145             }
1146 9         54 return $self->fail("No subsets passed");
1147             };
1148              
1149             #~ How the backtracker works
1150             #~
1151             #~ 1. To advance a step
1152             #~
1153             #~ Find the most recent incomplete SERIES
1154             #~
1155             #~ Get its next element.
1156             #~
1157             #~ 2. To get the next alternative (backreack)
1158             #~
1159             #~ Find the most recent VARIABLE_UNIT
1160             #~
1161             #~ If a repeatable, decrease it (they begin greedy)
1162             #~
1163             #~ If an alternation, try the next alternative,
1164             #~
1165             #~ If either of those cannot legally be done, it's no longer a variable unit so keep looking
1166             #~
1167             #~ When you run out of history, fail
1168             #~
1169             #~
1170             #~ So the backtracker should do the following:
1171             #~
1172             #~
1173             #~ backtracker (runner r, subject s, expected e, history h)
1174             #~ loop
1175             #~ next_step = advance (r, s, e, h)
1176             #~ if no next_step
1177             #~ return r->pass if index of last h is length of s
1178             #~ push next step onto history
1179             #~ result = evaluate
1180             #~ if result is not ok
1181             #~ next_solution = backtrack (r, s, e, h) # modifies h
1182             #~ if no next_solution
1183             #~ return r->fail
1184             #~ # implicit else continue and redo the loop
1185             #~
1186             #~
1187             $bt_core = sub {
1188             my ( $runner, $subject, $expected, $history, $options ) = @_;
1189             $history = [] unless defined $history; #:5.8
1190             while (1) { #~ yeah, scary, I know, but better than diving headlong into recursion
1191              
1192             #~ Advance
1193             my $next_step = $bt_advance->( $runner, $subject, $expected, $history );
1194              
1195             #~ If we cannot advance, then pass if what we've matched so far meets the criteria
1196             unless ( defined $next_step ) {
1197             return $runner->pass
1198             if (
1199             ( !@{$history} and !@{$subject} ) # this oughtn't to happen
1200             or ( $history->[-1]->{index} == $#$subject )
1201             );
1202              
1203             #return $runner->fail('No next step; index reached: '.$history->[-1]->{index});
1204             $runner->subtest()->diag('No next step');
1205             }
1206              
1207             #~ Add the next step to the history
1208             push @$history, $next_step if defined $next_step;
1209              
1210             #~ Determine if the next step can be executed
1211             my $evaluation_result =
1212             defined $next_step
1213             ? $bt_eval_step->( $runner, $subject, $expected, $history )
1214             : undef;
1215             unless ($evaluation_result) {
1216             my $next_solution = $bt_backtrack->( $runner, $subject, $expected, $history );
1217             unless ( defined $next_solution ) {
1218             return $runner->fail('No more alternatve solutions');
1219             }
1220             }
1221             }
1222             };
1223              
1224             $bt_advance = sub {
1225              
1226             #~ the purpose of this to find the latest series or repeatble which has not been exhausted.
1227             #~ This method adds items to the end of the history stack, and never removes them.
1228             my ( $runner, $subject, $expected, $history ) = @_;
1229             my $l = $#$history;
1230             $runner->subtest( test_case => $history )->diag( 'Advance ' . $l . '!' );
1231             my $next_step;
1232              
1233             #~ todo: check if l == -1
1234             if ( $l == -1 ) {
1235             return {
1236             self => $expected,
1237             parent => undef,
1238             index => -1,
1239             };
1240             }
1241             for my $i ( CORE::reverse( 0 .. $l ) ) {
1242             my $step = $history->[$i];
1243             my $children;
1244             if ( ( blessed $step->{self} ) and $step->{self}->isa('Test::Proto::Series') ) {
1245             $children = $step->{children};
1246             $children = [] unless defined $children; #:5.8
1247             my $contents = $step->{self}->contents;
1248             if ( $#$children < $#$contents ) {
1249              
1250             #~ we conclude the series is not complete. Add a new step.
1251             $next_step = {
1252             self => $contents->[ $#$children + 1 ],
1253             parent => $step,
1254             element => $#$children + 1
1255             };
1256             weaken $next_step->{parent};
1257             push @{ $step->{children} }, ($next_step);
1258             }
1259             }
1260             elsif ( ( blessed $step->{self} ) and $step->{self}->isa('Test::Proto::Repeatable') ) {
1261             $children = $step->{children};
1262             $children = [] unless defined $children; #:5.8
1263             my $max = $step->{max}; #~ the maximum set by a backtrack action
1264             $max = $step->{self}->max unless defined $max; # the maximum allowed by the repeatable
1265             #~ NB: Repeatables are greedy, so go as far as they can unless a backtrack has caused them to try being less greedy.
1266             unless ( ( defined $max ) and ( $#$children + 1 >= $max ) ) {
1267              
1268             #~ we conclude the repeatable is not exhausted. Add a new step.
1269             $next_step = {
1270             self => $step->{self}->contents,
1271             parent => $step,
1272             element => $#$children + 1
1273             };
1274             weaken $next_step->{parent};
1275             push @{ $step->{children} }, $next_step;
1276             $step->{max_tried} = $#{ $step->{children} } + 1;
1277             }
1278             }
1279             elsif ( ( blessed $step->{self} ) and $step->{self}->isa('Test::Proto::Alternation') ) {
1280              
1281             #~ Pick first alternative
1282             unless ( ( defined $step->{children} ) and @{ $step->{children} } ) {
1283             my $alt = 0;
1284             $alt = $step->{alt} if defined $step->{alt};
1285             $next_step = {
1286             self => $step->{self}->alternatives->[$alt],
1287             parent => $step,
1288             element => 0
1289             };
1290             weaken $next_step->{parent};
1291             $step->{alt} = $alt;
1292             push @{ $step->{children} }, $next_step;
1293             }
1294             }
1295             if ( defined $next_step ) {
1296             return $next_step;
1297             }
1298              
1299             #~ Otherwise, next $i.
1300             }
1301             return undef;
1302             };
1303              
1304             $bt_eval_step = sub {
1305              
1306             #~ The purpose of this function is to determine if the current solution can continue at this point.
1307             #~ Specifically, if the current step (i.e. the last in the history) validates against the next item in the subject.
1308             #~ However, if the current step is a series/repeatable/altenration, then this is not an issue.
1309             my ( $runner, $subject, $expected, $history ) = @_;
1310             my $current_step = $history->[-1];
1311             my $current_index = ( ( exists $history->[1] ) ? ( defined $history->[-2]->{index} ? $history->[-2]->{index} : -1 ) : -1 ); # current_index is what has been completed
1312             $current_step->{index} = $current_index; #:jic
1313             if ( exists $subject->[ $current_index + 1 ] ) {
1314              
1315             #~ if a series, repeatable, or alternation, we're always ok, we just need to update the index
1316             #~ if a prototype, evaluate it.
1317             if ( ( ref $current_step->{self} ) and ref( $current_step->{self} ) =~ /^Test::Proto::(?:Series|Repeatable|Alternation)$/ ) {
1318             $runner->subtest( test_case => $history )->diag( 'Starting a ' . ( ref $current_step->{self} ) );
1319             $current_step->{index} = $current_index;
1320             return 1; #~ always ok
1321             }
1322             else {
1323             my $p = upgrade( $current_step->{self} );
1324             $runner->subtest( test_case => $history )->diag( 'Validating index ' . ( $current_index + 1 ) );
1325             my $result = $p->validate( $subject->[ $current_index + 1 ], $runner->subtest() );
1326             if ($result) {
1327             $current_step->{index} = $current_index + 1;
1328             }
1329             else {
1330             $current_step->{index} = $current_index; # shouldn't read this
1331             }
1332             return $result;
1333             }
1334             }
1335             else {
1336             #~...
1337             #~ We are allowed only:
1338             #~ - repeatables with zero minimum
1339             #~ - alternations
1340             #~ i.e. no prototypes or series
1341             #~ Todo: check if we're repeating interminably by seeing if any object is its own ancestor
1342             $runner->subtest()->diag('Reached end of subject, allowing only potentially empty patterns');
1343             if ( ref( $current_step->{self} ) eq 'Test::Proto::Alternation' ) {
1344             $current_step->{index} = $current_index;
1345             return 1;
1346             }
1347             elsif ( ( ( ref $current_step->{self} ) eq 'Test::Proto::Repeatable' ) and ( $current_step->{self}->min <= ( $#{ $current_step->{children} } + 1 ) ) ) {
1348             $current_step->{max} = $#{ $current_step->{children} } + 1
1349             unless defined( $current_step->{max} )
1350             and $current_step->{max} < ( $#{ $current_step->{children} } + 1 ); #~ we need to consider it complete so we don't end up in a loop of adding and removing these.
1351             $current_step->{index} = $current_index;
1352             return 1;
1353             }
1354             else {
1355             $current_step->{index} = $current_index;
1356             return 0; #~ cause a backtrack
1357             }
1358              
1359             }
1360             };
1361              
1362             $bt_backtrack = sub {
1363             my ( $runner, $subject, $expected, $history ) = @_;
1364              
1365             #~ The purpose of this to find the latest repeatable and alternation which has not had all its options exhausted.
1366             #~ This method then removes all items from the history stack after that point and increments a counter on that history item.
1367             #~ No extra steps are added.
1368             #~ Consider taking the removed slice and keeping it in a 'failed branches' slot of the repeatable/alternation.
1369             my $l = $#$history;
1370             $runner->subtest()->diag( 'Backtracking... (last history item: ' . $l . ')' );
1371              
1372             #~ todo: check if l == -1 ?
1373             for my $i ( CORE::reverse( 0 .. $l ) ) {
1374             my $step = $history->[$i];
1375             if ( ( blessed $step->{self} ) and $step->{self}->isa('Test::Proto::Repeatable') ) {
1376             my $children = $step->{children};
1377             $children = [] unless defined $children; #:5.8
1378             my $max = $step->{max}; #~ the maximum set by a backtrack action
1379             $max = $step->{self}->max unless defined $max; # the maximum allowed by the repeatable
1380             $max = $step->{max_tried} unless defined $max;
1381             my $new_max = $max - 1;
1382             unless ( $new_max < $step->{self}->min ) {
1383             $runner->subtest( test_case => ($step) )->diag("Selected a new max of $new_max at Repeatable at step $i");
1384             $step->{max} = $new_max;
1385             if ( defined $step->{children}->[0] ) { # then the advance worked
1386             $bt_backtrack_to->( $runner, $history, $step->{children}->[0] );
1387             $#{ $step->{children} } = -1;
1388             return 1;
1389             }
1390             }
1391             }
1392             elsif ( ( blessed $step->{self} ) and $step->{self}->isa('Test::Proto::Alternation') ) {
1393             if ( $step->{alt} < $#{ $step->{self}->{alternatives} } ) {
1394             $runner->subtest( test_case => ($step) )->diag( "Selected branch " . ( $step->{alt} + 1 ) . " at Alternation at step $i" );
1395             $step->{alt}++;
1396             if ( defined $step->{children}->[0] ) { # then the advance worked
1397             $bt_backtrack_to->( $runner, $history, $step->{children}->[0] );
1398             $#{ $step->{children} } = -1;
1399             return 1;
1400             }
1401             }
1402             }
1403             }
1404             return undef;
1405              
1406             };
1407              
1408             $bt_backtrack_to = sub {
1409              
1410             #~ Backtracks to the target step (inclsively, i.e. deletes the step).
1411             my ( $runner, $history, $target_step ) = @_;
1412             for my $i ( CORE::reverse( 1 .. $#$history ) ) {
1413             if ( $history->[$i] == $target_step ) {
1414             $runner->subtest( test_case => ( $history->[$i] ) )->diag("Backtracked to step $i");
1415              
1416             #~ If step $i or any step after it is a child of a parent earlier in the history, it should no longer be a child, because it will shortly no longer exist.
1417             my @delenda = $i .. $#$history;
1418             foreach my $j ( 0 .. ( $i - 1 ) ) {
1419             if ( defined $history->[$j]->{children} ) {
1420             foreach my $childIndex ( 0 .. $#{ $history->[$j]->{children} } ) {
1421             if ( grep { $history->[$j]->{children}->[$childIndex] == $history->[$_] } @delenda ) {
1422             $#{ $history->[$j]->{children} } = $childIndex - 1;
1423             last;
1424             }
1425             }
1426             }
1427             }
1428             $#$history = $i - 1;
1429             return;
1430             }
1431             }
1432             die; #~ we should never reach this point
1433             };
1434             1;
1435