The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#!perl -T
use strict;
use warnings;
use Data::Dumper;
$Data::Dumper::Indent = 1; # prevent them getting out of hand.
use Test::More;
use Test::Proto::ArrayRef;

ok (1, 'ok is ok');

sub is_a_good_pass {
	# Todo: test this more
	ok($_[0]?1:0, , $_[1]) or diag Dumper $_[0];
}

sub is_a_good_fail {
	# Todo: test this more
	ok($_[0]?0:1, $_[1]) or diag Dumper $_[0];
	ok(!$_[0]->is_exception, '... and not be an exception') or diag Dumper $_[0];
}

sub is_a_good_exception {
	# Todo: test this more
	ok($_[0]?0:1, $_[1]);
	ok($_[0]->is_exception, '... and be an exception');
}


sub p { Test::Proto::Base->new(); }
sub pAr { Test::Proto::ArrayRef->new(); }

# nth
is_a_good_pass(pAr->nth(1, 'b')->validate(['a','b']), "nth: item 1 of ['a','b'] is 'b'");
is_a_good_fail(pAr->nth(1, 'a')->validate(['a','b']), "nth: item 1 of ['a','b'] is not 'a'");
is_a_good_fail(pAr->nth(2, 'b')->validate(['a','b']), "item 2 of ['a','b'] does not exist"); #~ it should fail with an out of bounds message, not be an exception

# map
is_a_good_pass(pAr->map(sub {uc shift;}, ['A','B'])->validate(['a','b']), "map passes with a transform");
is_a_good_pass(pAr->map(sub {shift;}, ['a','b'])->validate(['a','b']), "map passes with no transform");
is_a_good_fail(pAr->map(sub {uc shift;}, ['a','b'])->validate(['a','b']), "map fails when expected does not match");

# grep
is_a_good_pass(pAr->grep(sub {$_[0] eq uc $_[0]}, ['A'])->validate(['A','b']), "grep passes");
is_a_good_pass(pAr->grep(sub {$_[0] eq uc $_[0]}, [])->validate(['a','b']), "grep passes when nothing matches");
is_a_good_fail(pAr->grep(sub {$_[0] eq uc $_[0]}, ['a','b'])->validate(['A','b']), "grep fails when expected does not match");

# indexes_of
is_a_good_pass(pAr->indexes_of(sub {$_[0] eq uc $_[0]}, [0,2])->validate(['A','b', 'C']), "indexes_of passes");
is_a_good_pass(pAr->indexes_of(sub {$_[0] eq uc $_[0]}, [])->validate(['a','b']), "indexes_of passes when nothing matches");
is_a_good_fail(pAr->indexes_of(sub {$_[0] eq uc $_[0]}, [0,2])->validate(['A','b']), "indexes_of fails when expected does not match");


# grep (1 arg form)
is_a_good_pass(pAr->grep(sub {$_[0] eq uc $_[0]})->validate(['A','b']), "boolean grep passes when something matches");
is_a_good_fail(pAr->grep(sub {$_[0] eq uc $_[0]})->validate(['a','b']), "boolean grep fails when nothing matches");

# grep (1 arg form with expected)
is_a_good_pass(pAr->grep(sub {$_[0] eq uc $_[0]}, 'because')->validate(['A','b']), "boolean grep (w/reason) passes when something matches");
is_a_good_fail(pAr->grep(sub {$_[0] eq uc $_[0]}, 'because')->validate(['a','b']), "boolean grep (w/reason) fails when nothing matches");

# array_any
is_a_good_pass(pAr->array_any(sub {$_[0] eq uc $_[0]})->validate(['A','b']), "array_any passes when something matches");
is_a_good_fail(pAr->array_any(sub {$_[0] eq uc $_[0]})->validate(['a','b']), "array_any fails when nothing matches");

# array_none
is_a_good_pass(pAr->array_none(sub {$_[0] eq uc $_[0]})->validate(['a','b']), "array_none passes when nothing matches");
is_a_good_fail(pAr->array_none(sub {$_[0] eq uc $_[0]})->validate(['A','b']), "array_none fails when something matches");

# array_all
is_a_good_pass(pAr->array_all(sub {$_[0] eq uc $_[0]})->validate(['A','B']), "array_all passes when everything matches");
is_a_good_fail(pAr->array_all(sub {$_[0] eq uc $_[0]})->validate(['A','b']), "array_all fails when anything does not match");

# reduce
is_a_good_pass(pAr->reduce(sub { $_[0] + $_[1] }, 6 )->validate([1,2,3]), "reduce passes when result matches");
is_a_good_fail(pAr->reduce(sub { $_[0] + $_[1] }, 7 )->validate([1,2,3]), "reduce fails when result does not match");
is_a_good_exception(pAr->reduce(sub { $_[0] + $_[1] }, 7 )->validate([1]), "reduce is exception when subject has less than two members");

# array_eq
is_a_good_pass(pAr->array_eq(['a','b'])->validate(['a','b']), "['a','b'] is ['a','b']");
is_a_good_pass(pAr->array_eq(['a',['b']])->validate(['a',['b']]), "['a',['b']] is ['a',['b']]");
is_a_good_pass(pAr->array_eq([])->validate([]), "[] is  []");
is_a_good_fail(pAr->array_eq(['a','b'])->validate(['a']), "['a'] is not ['a','b']");
is_a_good_fail(pAr->array_eq(['a'])->validate(['a', 'b']), "['a','b'] is not ['a']");
is_a_good_fail(pAr->array_eq(['a','b'])->validate(['b','a']), "['b','a'] is not ['a','b']");

# enumerated
is_a_good_pass(pAr->enumerated([[0,'a'],[1,'b']])->validate(['a','b']), "enumerated passes correctly");
is_a_good_fail(pAr->enumerated([])->validate(['a','b']), "enumerated fails correctly");
is_a_good_pass(pAr->enumerated([])->validate([]), "enumerated passes correctly when empty");
is_a_good_fail(pAr->enumerated([[]])->validate([]), "enumerated fails correctly when empty");

# in_groups
is_a_good_pass(pAr->in_groups(2,[['a','b'],['c','d']])->validate(['a','b','c','d']), "in_groups works");
is_a_good_pass(pAr->in_groups(2,[])->validate([]), "in_groups works with empty list");
is_a_good_pass(pAr->in_groups(1,[['a'],['b'],['c'],['d']])->validate(['a','b','c','d']), "in_groups works with n=1");
is_a_good_pass(pAr->in_groups(2,[['a','b'],['c','d'],['e']])->validate(['a','b','c','d','e']), "in_groups works with remainders");
is_a_good_fail(pAr->in_groups(2,[])->validate(['a','b','c','d']), "in_groups fails when no match");
is_a_good_exception(pAr->in_groups(0,[['a'],['b'],['c'],['d']])->validate(['a','b','c','d']), "in_groups throws exceptions when n<1");

# group_when
is_a_good_pass(pAr->group_when(sub {$_[0] eq uc $_[0]}, [['A'],['B','c','d'],['E']])->validate(['A','B','c','d','E']), "group_when works");
is_a_good_pass(pAr->group_when(sub {$_[0] eq uc $_[0]}, [['a','b','c','d','e']])->validate(['a','b','c','d','e']), "group_when works when it matches nothing");
is_a_good_fail(pAr->group_when(sub {$_[0] eq uc $_[0]}, [['a','e','e','e','e']])->validate(['a','b','c','d','e']), "group_when fails appropriately");

# group_when_index
is_a_good_pass(pAr->group_when_index(p->num_gt(2), [['A','B','c'],['d'],['E']])->validate(['A','B','c','d','E']), "group_when_index works");
is_a_good_pass(pAr->group_when_index(p->num_gt(5), [['a','b','c','d','e']])->validate(['a','b','c','d','e']), "group_when_index works when it matches nothing");
is_a_good_fail(pAr->group_when_index(p->num_gt(2), [['a','e','e','e','e']])->validate(['a','b','c','d','e']), "group_when fails appropriately");

# count_items
is_a_good_pass(pAr->count_items(2)->validate(['a','b']), "count_items 2 on ['a','b'] passes");
is_a_good_fail(pAr->count_items(1)->validate(['a','b']), "count_items 1 on ['a','b'] fails");
is_a_good_pass(pAr->count_items(p->num_lt(5))->validate(['a','b']), "count_items <5 on ['a','b'] passes");
is_a_good_fail(pAr->count_items(p->num_gt(5))->validate(['a','b']), "count_items >5 on ['a','b'] fails");

# range
is_a_good_pass(pAr->range(1, ['b'])->validate(['a','b']), "range: item 1 of ['a','b'] is 'b'");
is_a_good_fail(pAr->range(1, ['a'])->validate(['a','b']), "range: item 1 of ['a','b'] is not 'a'");
is_a_good_fail(pAr->range(2, ['a'])->validate(['a','b']), "range: item 2 of ['a','b'] does not exist");
is_a_good_exception(pAr->range('', ['a'])->validate(['a','b','c','d']), "range: exception if range is empty");
is_a_good_exception(pAr->range('a', ['a'])->validate(['a','b','c','d']), "range: exception if range is not a range");
is_a_good_pass(pAr->range('1,3', ['b','d'])->validate(['a','b','c','d']), "range: comma");
is_a_good_pass(pAr->range('0,1,3', ['a','b','d'])->validate(['a','b','c','d']), "range: multiple commas");
is_a_good_pass(pAr->range('1,1,3', ['b','b','d'])->validate(['a','b','c','d']), "range: repeated elements");
is_a_good_pass(pAr->range('1,-4,-1', ['b','a','d'])->validate(['a','b','c','d']), "range: negative");
is_a_good_pass(pAr->range('0..2', ['a','b','c'])->validate(['a','b','c','d']), "range: .. operator from beginning");
is_a_good_pass(pAr->range('1..3', ['b','c','d'])->validate(['a','b','c','d']), "range: .. operator to end");
is_a_good_pass(pAr->range('1..-1', ['b','c','d'])->validate(['a','b','c','d']), "range: .. operator to -1");
is_a_good_pass(pAr->range('0..1,3', ['a','b','d'])->validate(['a','b','c','d']), "range: .. and comma");
is_a_good_pass(pAr->range('0..1,2..3', ['a','b','c','d'])->validate(['a','b','c','d']), "range: multiple ..");
is_a_good_pass(pAr->range('0..1,1..3', ['a','b','b','c','d'])->validate(['a','b','c','d']), "range: overlapping ..");
is_a_good_pass(pAr->range('0..1,1..-1', ['a','b','b','c','d'])->validate(['a','b','c','d']), "range: overlapping .. with negatives");

# reverse
is_a_good_pass(pAr->reverse([qw (d c b a)])->validate(['a','b','c','d']), "reverse passes when expected matches");
is_a_good_fail(pAr->reverse([qw (a b c d)])->validate(['a','b','c','d']), "reverse fails when expected does not match");

# array_before
is_a_good_pass(pAr->array_before('c', ['a','b'])->validate(['a','b','c','d']), "array_before passes when expected matches");
is_a_good_pass(pAr->array_before('a',[])->validate(['a','b','c','d']), "array_before passes when expected matches and is empty");
is_a_good_fail(pAr->array_before('x',[])->validate(['a','b','c','d']), "array_before fails when match does not match");
is_a_good_fail(pAr->array_before('c',[])->validate(['a','b','c','d']), "array_before fails when expected does not match");

# array_before_inclusive
is_a_good_pass(pAr->array_before_inclusive('c', ['a','b','c'])->validate(['a','b','c','d']), "array_before_inclusive passes when expected matches");
is_a_good_pass(pAr->array_before_inclusive('a',['a'])->validate(['a','b','c','d']), "array_before_inclusive passes when expected matches and is alone");
is_a_good_fail(pAr->array_before_inclusive('x',[])->validate(['a','b','c','d']), "array_before_inclusive fails when match does not match");
is_a_good_fail(pAr->array_before_inclusive('c',[])->validate(['a','b','c','d']), "array_before_inclusive fails when expected does not match");

# array_after
is_a_good_pass(pAr->array_after('b', ['c','d'])->validate(['a','b','c','d']), "array_after passes when expected matches");
is_a_good_pass(pAr->array_after('d',[])->validate(['a','b','c','d']), "array_after passes when expected matches and is empty");
is_a_good_fail(pAr->array_after('x',[])->validate(['a','b','c','d']), "array_after fails when match does not match");
is_a_good_fail(pAr->array_after('c',[])->validate(['a','b','c','d']), "array_after fails when expected does not match");

# array_after
is_a_good_pass(pAr->array_after_inclusive('b', ['b','c','d'])->validate(['a','b','c','d']), "array_after_inclusive passes when expected matches");
is_a_good_pass(pAr->array_after_inclusive('d',['d'])->validate(['a','b','c','d']), "array_after_inclusive passes when expected matches and is alone");
is_a_good_fail(pAr->array_after_inclusive('x',[])->validate(['a','b','c','d']), "array_after_inclusive fails when match does not match");
is_a_good_fail(pAr->array_after_inclusive('c',[])->validate(['a','b','c','d']), "array_after_inclusive fails when expected does not match");

# functions requiring comparison
use Test::Proto::Compare;
my $cmp_rev = Test::Proto::Compare->new()->reverse;
my $cmp_lc = sub {lc shift cmp lc shift};

# sorted
is_a_good_pass(pAr->sorted(['a','c','e'])->validate(['a','e','c']), "sorted passes correctly");
is_a_good_fail(pAr->sorted(['a','e','c'])->validate(['a','e','c']), "sorted fails correctly");
is_a_good_pass(pAr->sorted([])->validate([]), "sorted passes correctly on empty array");

is_a_good_pass(pAr->sorted(['e','c','a'], $cmp_rev)->validate(['a','e','c']), "sorted passes correctly in reverse");
is_a_good_fail(pAr->sorted(['a','e','c'], $cmp_rev)->validate(['a','e','c']), "sorted fails correctly in reverse");
is_a_good_pass(pAr->sorted([], $cmp_rev)->validate([]), "sorted passes correctly on empty array in reverse");

# ascending

is_a_good_pass(pAr->ascending->validate(['a','c','e']), "ascending passes correctly");
is_a_good_fail(pAr->ascending->validate(['a','e','c']), "ascending fails correctly");
is_a_good_pass(pAr->ascending->validate([]), "ascending passes correctly on empty array");
is_a_good_pass(pAr->ascending->validate([]), "ascending passes correctly on single item array");

# descending

is_a_good_pass(pAr->descending->validate(['e','c','a']), "descending passes correctly");
is_a_good_pass(pAr->descending($cmp_lc)->validate(['e','C','a']), "descending passes correctly with lc");
is_a_good_fail(pAr->descending->validate(['e','a','c']), "descending fails correctly");
is_a_good_pass(pAr->descending->validate([]), "descending passes correctly on empty array");
is_a_good_pass(pAr->descending->validate([]), "descending passes correctly on single item array");

# array_max
is_a_good_pass(pAr->array_max('e')->validate(['a','e','c']), "array_max passes correctly");
is_a_good_fail(pAr->array_max('e')->validate(['a','e','f']), "array_max fails correctly when a better candidate exists");
is_a_good_fail(pAr->array_max('f')->validate(['a','e','c']), "array_max fails correctly when prototype fails");
is_a_good_fail(pAr->array_max('e')->validate([]), "array_max fails correctly for []");

is_a_good_pass(pAr->array_max('E', $cmp_lc)->validate(['a','E','c']), "array_max passes correctly with lc");
is_a_good_pass(pAr->array_max('E', $cmp_lc)->validate(['a','e','E','c']), "array_max passes correctly with lc - 2 winners");
is_a_good_pass(pAr->array_max('E', $cmp_lc)->validate(['a','E','e','c']), "array_max passes correctly with lc - 2 winners, reversed");
is_a_good_fail(pAr->array_max('E', $cmp_lc)->validate(['a','E','f']), "array_max fails correctly when a better candidate exists with lc");
is_a_good_fail(pAr->array_max('E', $cmp_lc)->validate(['a','e','E','f']), "array_max fails correctly when a better candidate exists with lc");
is_a_good_fail(pAr->array_max('f', $cmp_lc)->validate(['a','E','c']), "array_max fails correctly when prototype fails with lc");
is_a_good_fail(pAr->array_max('E', $cmp_lc)->validate([]), "array_max fails correctly for [] with lc");

# array_index_of_max
is_a_good_pass(pAr->array_index_of_max(1)->validate(['a','e','c']), "array_index_of_max passes correctly");
is_a_good_fail(pAr->array_index_of_max(1)->validate(['a','e','f']), "array_index_of_max fails correctly when a better candidate exists");
is_a_good_fail(pAr->array_index_of_max(2)->validate(['a','e','c']), "array_index_of_max fails correctly when prototype fails");
is_a_good_fail(pAr->array_index_of_max(1)->validate([]), "array_index_of_max fails correctly for []");

is_a_good_pass(pAr->array_index_of_max(1, $cmp_lc)->validate(['a','E','c']), "array_index_of_max passes correctly with lc");
is_a_good_pass(pAr->array_index_of_max(1, $cmp_lc)->validate(['a','e','E','c']), "array_index_of_max passes correctly with lc - 2 winners");
is_a_good_pass(pAr->array_index_of_max(1, $cmp_lc)->validate(['a','E','e','c']), "array_index_of_max passes correctly with lc - 2 winners, reversed");
is_a_good_fail(pAr->array_index_of_max(1, $cmp_lc)->validate(['a','E','f']), "array_index_of_max fails correctly when a better candidate exists with lc");
is_a_good_fail(pAr->array_index_of_max(1, $cmp_lc)->validate(['a','e','E','f']), "array_index_of_max fails correctly when a better candidate exists with lc");
is_a_good_fail(pAr->array_index_of_max(2, $cmp_lc)->validate(['a','E','c']), "array_index_of_max fails correctly when prototype fails with lc");
is_a_good_fail(pAr->array_index_of_max(1, $cmp_lc)->validate([]), "array_index_of_max fails correctly for [] with lc");


# array_min
is_a_good_pass(pAr->array_min('e')->validate(['g','e','h']), "array_min passes correctly");
is_a_good_fail(pAr->array_min('e')->validate(['g','e','d']), "array_min fails correctly when a better candidate exists");
is_a_good_fail(pAr->array_min('f')->validate(['g','e','h']), "array_min fails correctly when prototype fails");
is_a_good_fail(pAr->array_min('e')->validate([]), "array_min fails correctly for []");

is_a_good_pass(pAr->array_min('E', $cmp_lc)->validate(['g','E','h']), "array_min passes correctly with lc");
is_a_good_pass(pAr->array_min('E', $cmp_lc)->validate(['g','e','E','h']), "array_min passes correctly with lc - 2 winners");
is_a_good_pass(pAr->array_min('E', $cmp_lc)->validate(['g','E','e','h']), "array_min passes correctly with lc - 2 winners, reversed");
is_a_good_fail(pAr->array_min('E', $cmp_lc)->validate(['g','E','d']), "array_min fails correctly when a better candidate exists with lc");
is_a_good_fail(pAr->array_min('E', $cmp_lc)->validate(['g','e','E','d']), "array_min fails correctly when a better candidate exists with lc");
is_a_good_fail(pAr->array_min('f', $cmp_lc)->validate(['g','E','h']), "array_min fails correctly when prototype fails with lc");
is_a_good_fail(pAr->array_min('E', $cmp_lc)->validate([]), "array_min fails correctly for [] with lc");

# array_index_of_min
is_a_good_pass(pAr->array_index_of_min(1)->validate(['g','e','h']), "array_index_of_min passes correctly");
is_a_good_fail(pAr->array_index_of_min(1)->validate(['g','e','d']), "array_index_of_min fails correctly when a better candidate exists");
is_a_good_fail(pAr->array_index_of_min(2)->validate(['g','e','h']), "array_index_of_min fails correctly when prototype fails");
is_a_good_fail(pAr->array_index_of_min(1)->validate([]), "array_index_of_min fails correctly for []");

is_a_good_pass(pAr->array_index_of_min(1, $cmp_lc)->validate(['g','E','h']), "array_index_of_min passes correctly with lc");
is_a_good_pass(pAr->array_index_of_min(1, $cmp_lc)->validate(['g','e','E','h']), "array_index_of_min passes correctly with lc - 2 winners");
is_a_good_pass(pAr->array_index_of_min(1, $cmp_lc)->validate(['g','E','e','h']), "array_index_of_min passes correctly with lc - 2 winners, reversed");
is_a_good_fail(pAr->array_index_of_min(1, $cmp_lc)->validate(['g','E','d']), "array_index_of_min fails correctly when a better candidate exists with lc");
is_a_good_fail(pAr->array_index_of_min(1, $cmp_lc)->validate(['g','e','E','d']), "array_index_of_min fails correctly when a better candidate exists with lc");
is_a_good_fail(pAr->array_index_of_min(2, $cmp_lc)->validate(['g','E','h']), "array_index_of_min fails correctly when prototype fails with lc");
is_a_good_fail(pAr->array_index_of_min(1, $cmp_lc)->validate([]), "array_index_of_min fails correctly for [] with lc");


# array_all_unique
is_a_good_pass(pAr->array_all_unique->validate(['a','b','c','d']), "array_all_unique passes correctly");
is_a_good_pass(pAr->array_all_unique->validate([]), "array_all_unique passes correctly for []");
is_a_good_pass(pAr->array_all_unique->validate(['a']), "array_all_unique passes correctly for ['a']");
is_a_good_fail(pAr->array_all_unique->validate(['a','a','a','a']), "array_all_unique fails correctly");

# array_all_same
is_a_good_pass(pAr->array_all_same->validate(['a','a','a','a']), "array_all_same passes correctly");
is_a_good_pass(pAr->array_all_same->validate([]), "array_all_same passes correctly for []");
is_a_good_pass(pAr->array_all_same->validate(['a']), "array_all_same passes correctly for ['a']");
is_a_good_fail(pAr->array_all_same->validate(['a','b','c','d']), "array_all_same fails correctly");

# subset_of, superset_of, subbag_of, superbag_of

my $testCases = [
	{
		type=>'bag,set,subset,subbag,superset,superbag',
		comment => 'Equal',
		left => [1,2,3],
		right => [1,2,3],
	},
	{
		type  =>'superset,superbag',
		comment => 'Left > Right',
		left  => [1,2,3],
		right => [1,2],
	},
	{
		type=>'subset,subbag',
		comment => 'Right > Left',
		left => [1,2],
		right => [1,2,3],
	},
	{
		type  =>'set,subset,superset,superbag',
		comment => 'Left > Right (but setwise equal)',
		left  => [1,1,2],
		right => [1,2],
	},
	{
		type  =>'set,subset,superset,subbag',
		comment => 'Right > Left (but setwise equal)',
		left  => [1,2],
		right => [1,1,2],
	},
	{
		type  =>'set,superset,subset,superbag',
		comment => '[1,2] vs [p]',
		left  => [1,2],
		right => [p],
	},
	{
		type  =>'bag,set,superset,subset,superbag,subbag',
		comment => '[1,2] vs [2,p]',
		left  => [1,2],
		right => [2,p],
	},
	{
		type  =>'bag,set,superset,subset,superbag,subbag',
		comment => '[1,2,3] vs [p,p,1]',
		left  => [1,2,3],
		right => [p,p,1],
	},
];
my $machine = sub {
	my ($method, $left, $right) = @_;
	my $fullMethod = $method.'_of';
	pAr->$fullMethod($right)->validate($left);
};
foreach my $testCase (@$testCases) {
	foreach my $method (qw(bag set superset subset superbag subbag)) {
		if ($testCase->{type} =~ /\b$method\b/) {
			ok ($machine->($method,$testCase->{left}, $testCase->{right}), "$method should pass with these arguments - ".$testCase->{comment});
		}
		else {
			ok (!$machine->($method,$testCase->{left}, $testCase->{right}), "$method should fail with these arguments - ".$testCase->{comment});
		}
	}
}

use Test::Proto::Series;
use Test::Proto::Repeatable;
use Test::Proto::Alternation;

sub pSeries { Test::Proto::Series->new(@_); }
sub pAlternation { Test::Proto::Alternation->new(@_); }
sub pRepeatable { Test::Proto::Repeatable->new(@_); }
my $rpt = pRepeatable(p);
$rpt->max(2);
isa_ok (pRepeatable, 'Test::Proto::Repeatable');
isa_ok (pRepeatable->max(2), 'Test::Proto::Repeatable'); #~ test if method chaining works
my $seriesTests = [

{ # 1
	prototype  => pSeries('a'),
	subject    => ['a'],
	value      => 1,
},
{ # 2
	prototype  => pSeries('b'),
	subject    => ['a'],
	value      => 0,
},
{ # 3
	prototype  => pSeries('a','b'),
	subject    => ['a','b'],
	value      => 1,
},
{ # 4
	prototype  => pSeries('a','b'),
	subject    => ['a','c'],
	value      => 0,
},
{ # 5
	prototype  => pSeries('a','b','c'),
	subject    => ['a','b'],
	value      => 0,
},
{ # 6
	prototype  => pSeries('a','b'),
	subject    => ['a','b','c'],
	value      => 'begins_with',
},
{ # 7
	prototype  => $rpt,
	subject    => ['a','b'],
	value      => 1,
},
{ # 8
	prototype  => $rpt,
	subject    => ['a','b','c'],
	value      => 'begins_with ends_with',
},
{ # 9
	prototype  => $rpt,
	subject    => [0],
	value      => 1,
},
{ # 10
	prototype  => pSeries(pSeries('a','b')),
	subject    => ['a','b'],
	value      => 1,
},
{ # 11
	prototype  => pAlternation(pSeries('a','b')),
	subject    => ['a','b'],
	value      => 1,
},
{ # 12
	prototype  => pAlternation(pSeries('a','b')),
	subject    => ['a'],
	value      => 0,
},
{ # 13
	prototype  => pAlternation(pSeries('a')),
	subject    => ['a', 'b'],
	value      => 'begins_with',
},
{ # 14
	prototype  => pAlternation(pSeries('a'),pRepeatable('a'),pSeries('a')),
	subject    => ['a', 'a'],
	value      => 1,
},
{ # 15
	prototype  => pAlternation(pSeries('a'),pSeries('a','b')),
	subject    => ['a','b'],
	value      => 1,
},
{ # 16
	prototype  => pSeries(pSeries('a'),pSeries('b')),
	subject    => ['a','b'],
	value      => 1,
},
{ # 17
	prototype  => pSeries(pSeries('a', 'b'),pSeries('b')),
	subject    => ['a','b'],
	value      => 0,
},
{ # 18
	prototype  => pSeries(pAlternation('a', 'b'),pSeries('b')),
	subject    => ['a','b'],
	value      => 1,
},
{ # 19
	prototype  => pSeries(pRepeatable('a'),pSeries('b')),
	subject    => ['a','b'],
	value      => 1,
},
{ # 20
	prototype  => pSeries(pRepeatable(pAlternation('a','b'))),
	subject    => ['a','b'],
	value      => 1,
},
{ # 21
	prototype  => pSeries(pRepeatable(pAlternation('a','b')), 'b'),
	subject    => ['a','b'],
	value      => 1,
},
{ # 22
	prototype  => pSeries(pRepeatable(pAlternation('a','b')), 'a','b'),
	subject    => ['a','b'],
	value      => 1,
},
{ # 23
	prototype  => pSeries('a','b', pRepeatable(pAlternation('a','b'))),
	subject    => ['a','b'],
	value      => 1,
},
{ # 24
	prototype  => pSeries('a','b', pRepeatable(pAlternation('a','b')), 'c'),
	subject    => ['a','b'],
	value      => 0,
},
{ # 25
	prototype  => pAlternation(pSeries('b'),pSeries('a','b')),
	subject    => ['a','b'],
	value      => 1,
},
{ # 26
	prototype  => pAlternation(pSeries('a'),pSeries('b')),
	subject    => ['a','b'],
	value      => 'ends_with begins_with',
},
{ # 27
	prototype  => pRepeatable('a','b'),
	subject    => ['a','b','a','b'],
	value      => 1,
},
{ # 28
	prototype  => pRepeatable('a','b'),
	subject    => ['a'],
	value      => 0,
},
];

my $i = 0;

foreach my $t (@$seriesTests){
	$i++;
	foreach my $method (qw(begins_with contains_only ends_with)){
		if ( ($t->{value} eq 1) or $t->{value} =~ /$method/ ) {
			is_a_good_pass( pAr->$method($t->{prototype})->validate($t->{subject}), "Series Test $i must pass $method");
		}
		else {
			is_a_good_fail( pAr->$method($t->{prototype})->validate($t->{subject}), "Series Test $i must fail" );
		}
	}
}





done_testing;