Import tc tests
gfx [Mon, 19 Oct 2009 08:39:08 +0000 (17:39 +0900)]
35 files changed:
t/040_type_constraints/failing/001_util_type_constraints.t [new file with mode: 0644]
t/040_type_constraints/failing/002_util_type_constraints_export.t [new file with mode: 0644]
t/040_type_constraints/failing/003_util_std_type_constraints.t [new file with mode: 0644]
t/040_type_constraints/failing/004_util_find_type_constraint.t [new file with mode: 0644]
t/040_type_constraints/failing/005_util_type_coercion.t [new file with mode: 0644]
t/040_type_constraints/failing/006_util_type_reloading.t [new file with mode: 0644]
t/040_type_constraints/failing/007_util_more_type_coercion.t [new file with mode: 0644]
t/040_type_constraints/failing/008_union_types.t [new file with mode: 0644]
t/040_type_constraints/failing/009_union_types_and_coercions.t [new file with mode: 0644]
t/040_type_constraints/failing/010_misc_type_tests.t [new file with mode: 0644]
t/040_type_constraints/failing/011_container_type_constraint.t [new file with mode: 0644]
t/040_type_constraints/failing/012_container_type_coercion.t [new file with mode: 0644]
t/040_type_constraints/failing/013_advanced_type_creation.t [new file with mode: 0644]
t/040_type_constraints/failing/014_type_notation_parser.t [new file with mode: 0644]
t/040_type_constraints/failing/015_enum.t [new file with mode: 0644]
t/040_type_constraints/failing/016_subtyping_parameterized_types.t [new file with mode: 0644]
t/040_type_constraints/failing/017_subtyping_union_types.t [new file with mode: 0644]
t/040_type_constraints/failing/018_custom_parameterized_types.t [new file with mode: 0644]
t/040_type_constraints/failing/019_coerced_parameterized_types.t [new file with mode: 0644]
t/040_type_constraints/failing/020_class_type_constraint.t [new file with mode: 0644]
t/040_type_constraints/failing/021_maybe_type_constraint.t [new file with mode: 0644]
t/040_type_constraints/failing/022_custom_type_errors.t [new file with mode: 0644]
t/040_type_constraints/failing/023_types_and_undef.t [new file with mode: 0644]
t/040_type_constraints/failing/024_role_type_constraint.t [new file with mode: 0644]
t/040_type_constraints/failing/025_type_coersion_on_lazy_attributes.t [new file with mode: 0644]
t/040_type_constraints/failing/026_normalize_type_name.t [new file with mode: 0644]
t/040_type_constraints/failing/027_parameterize_from.t [new file with mode: 0644]
t/040_type_constraints/failing/029_define_type_twice_throws.t [new file with mode: 0644]
t/040_type_constraints/failing/030_class_subtypes.t [new file with mode: 0644]
t/040_type_constraints/failing/031_subtype_auto_vivify_parent.t [new file with mode: 0644]
t/040_type_constraints/failing/032_throw_error.t [new file with mode: 0644]
t/040_type_constraints/failing/033_type_names.t [new file with mode: 0644]
t/040_type_constraints/failing/034_duck_types.t [new file with mode: 0644]
t/040_type_constraints/failing/035_duck_type_handles.t [new file with mode: 0644]
t/040_type_constraints/failing/036_match_type_operator.t [new file with mode: 0644]

diff --git a/t/040_type_constraints/failing/001_util_type_constraints.t b/t/040_type_constraints/failing/001_util_type_constraints.t
new file mode 100644 (file)
index 0000000..a928ff5
--- /dev/null
@@ -0,0 +1,229 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 85;
+use Test::Exception;
+
+use Scalar::Util ();
+
+use lib 't/lib';
+use Test::Mouse;
+use Mouse::Util::TypeConstraints;
+
+
+type Number => where { Scalar::Util::looks_like_number($_) };
+type String
+    => where { !ref($_) && !Number($_) }
+    => message { "This is not a string ($_)" };
+
+subtype Natural
+        => as Number
+        => where { $_ > 0 };
+
+subtype NaturalLessThanTen
+        => as Natural
+        => where { $_ < 10 }
+        => message { "The number '$_' is not less than 10" };
+
+Mouse::Util::TypeConstraints->export_type_constraints_as_functions();
+
+ok(Number(5), '... this is a Num');
+ok(!defined(Number('Foo')), '... this is not a Num');
+{
+    my $number_tc = Mouse::Util::TypeConstraints::find_type_constraint('Number');
+    is("$number_tc", 'Number', '... type constraint stringifies to name');
+}
+
+ok(String('Foo'), '... this is a Str');
+ok(!defined(String(5)), '... this is not a Str');
+
+ok(Natural(5), '... this is a Natural');
+is(Natural(-5), undef, '... this is not a Natural');
+is(Natural('Foo'), undef, '... this is not a Natural');
+
+ok(NaturalLessThanTen(5), '... this is a NaturalLessThanTen');
+is(NaturalLessThanTen(12), undef, '... this is not a NaturalLessThanTen');
+is(NaturalLessThanTen(-5), undef, '... this is not a NaturalLessThanTen');
+is(NaturalLessThanTen('Foo'), undef, '... this is not a NaturalLessThanTen');
+
+# anon sub-typing
+
+my $negative = subtype Number => where  { $_ < 0 };
+ok(defined $negative, '... got a value back from negative');
+isa_ok($negative, 'Mouse::Meta::TypeConstraint');
+
+ok($negative->check(-5), '... this is a negative number');
+ok(!defined($negative->check(5)), '... this is not a negative number');
+is($negative->check('Foo'), undef, '... this is not a negative number');
+
+ok($negative->is_subtype_of('Number'), '... $negative is a subtype of Number');
+ok(!$negative->is_subtype_of('String'), '... $negative is not a subtype of String');
+
+my $negative2 = subtype Number => where { $_ < 0 } => message {"$_ is not a negative number"};
+
+ok(defined $negative2, '... got a value back from negative');
+isa_ok($negative2, 'Mouse::Meta::TypeConstraint');
+
+ok($negative2->check(-5), '... this is a negative number');
+ok(!defined($negative2->check(5)), '... this is not a negative number');
+is($negative2->check('Foo'), undef, '... this is not a negative number');
+
+ok($negative2->is_subtype_of('Number'), '... $negative2 is a subtype of Number');
+ok(!$negative2->is_subtype_of('String'), '... $negative is not a subtype of String');
+
+ok($negative2->has_message, '... it has a message');
+is($negative2->validate(2),
+   '2 is not a negative number',
+   '... validated unsuccessfully (got error)');
+
+# check some meta-details
+
+my $natural_less_than_ten = find_type_constraint('NaturalLessThanTen');
+isa_ok($natural_less_than_ten, 'Mouse::Meta::TypeConstraint');
+
+ok($natural_less_than_ten->is_subtype_of('Natural'), '... NaturalLessThanTen is subtype of Natural');
+ok($natural_less_than_ten->is_subtype_of('Number'), '... NaturalLessThanTen is subtype of Number');
+ok(!$natural_less_than_ten->is_subtype_of('String'), '... NaturalLessThanTen is not subtype of String');
+
+ok($natural_less_than_ten->has_message, '... it has a message');
+
+ok(!defined($natural_less_than_ten->validate(5)), '... validated successfully (no error)');
+
+is($natural_less_than_ten->validate(15),
+   "The number '15' is not less than 10",
+   '... validated unsuccessfully (got error)');
+
+my $natural = find_type_constraint('Natural');
+isa_ok($natural, 'Mouse::Meta::TypeConstraint');
+
+ok($natural->is_subtype_of('Number'), '... Natural is a subtype of Number');
+ok(!$natural->is_subtype_of('String'), '... Natural is not a subtype of String');
+
+ok(!$natural->has_message, '... it does not have a message');
+
+ok(!defined($natural->validate(5)), '... validated successfully (no error)');
+
+is($natural->validate(-5),
+  "Validation failed for 'Natural' failed with value -5",
+  '... validated unsuccessfully (got error)');
+
+my $string = find_type_constraint('String');
+isa_ok($string, 'Mouse::Meta::TypeConstraint');
+
+ok($string->has_message, '... it does have a message');
+
+ok(!defined($string->validate("Five")), '... validated successfully (no error)');
+
+is($string->validate(5),
+"This is not a string (5)",
+'... validated unsuccessfully (got error)');
+
+lives_ok { Mouse::Meta::Attribute->new('bob', isa => 'Spong') }
+  'meta-attr construction ok even when type constraint utils loaded first';
+
+# Test type constraint predicate return values.
+
+foreach my $predicate (qw/equals is_subtype_of is_a_type_of/) {
+    ok( !defined $string->$predicate('DoesNotExist'), "$predicate predicate returns undef for non existant constraint");
+}
+
+# Test adding things which don't look like types to the registry throws an exception
+
+my $r = Mouse::Util::TypeConstraints->get_type_constraint_registry;
+throws_ok {$r->add_type_constraint()} qr/not a valid type constraint/, '->add_type_constraint(undef) throws';
+throws_ok {$r->add_type_constraint('foo')} qr/not a valid type constraint/, '->add_type_constraint("foo") throws';
+throws_ok {$r->add_type_constraint(bless {}, 'SomeClass')} qr/not a valid type constraint/, '->add_type_constraint(SomeClass->new) throws';
+
+# Test some specific things that in the past did not work,
+# specifically weird variations on anon subtypes.
+
+{
+    my $subtype = subtype as 'Str';
+    isa_ok( $subtype, 'Mouse::Meta::TypeConstraint', 'got an anon subtype' );
+    is( $subtype->parent->name, 'Str', 'parent is Str' );
+    # This test sucks but is the best we can do
+    is( $subtype->constraint->(), 1,
+        'subtype has the null constraint' );
+    ok( ! $subtype->has_message, 'subtype has no message' );
+}
+
+{
+    my $subtype = subtype as 'ArrayRef[Num|Str]';
+    isa_ok( $subtype, 'Mouse::Meta::TypeConstraint', 'got an anon subtype' );
+    is( $subtype->parent->name, 'ArrayRef[Num|Str]', 'parent is ArrayRef[Num|Str]' );
+    ok( ! $subtype->has_message, 'subtype has no message' );
+}
+
+{
+    my $subtype = subtype 'ArrayRef[Num|Str]' => message { 'foo' };
+    isa_ok( $subtype, 'Mouse::Meta::TypeConstraint', 'got an anon subtype' );
+    is( $subtype->parent->name, 'ArrayRef[Num|Str]', 'parent is ArrayRef[Num|Str]' );
+    ok( $subtype->has_message, 'subtype does have a message' );
+}
+
+# alternative sugar-less calling style which is documented as legit:
+{
+    my $subtype = subtype( 'MyStr', { as => 'Str' } );
+    isa_ok( $subtype, 'Mouse::Meta::TypeConstraint', 'got a subtype' );
+    is( $subtype->name, 'MyStr', 'name is MyStr' );
+    is( $subtype->parent->name, 'Str', 'parent is Str' );
+}
+
+{
+    my $subtype = subtype( { as => 'Str' } );
+    isa_ok( $subtype, 'Mouse::Meta::TypeConstraint', 'got a subtype' );
+    is( $subtype->name, '__ANON__', 'name is __ANON__' );
+    is( $subtype->parent->name, 'Str', 'parent is Str' );
+}
+
+{
+    my $subtype = subtype( { as => 'Str', where => sub { /X/ } } );
+    isa_ok( $subtype, 'Mouse::Meta::TypeConstraint', 'got a subtype' );
+    is( $subtype->name, '__ANON__', 'name is __ANON__' );
+    is( $subtype->parent->name, 'Str', 'parent is Str' );
+    ok( $subtype->check('FooX'), 'constraint accepts FooX' );
+    ok( ! $subtype->check('Foo'), 'constraint reject Foo' );
+}
+
+{
+    throws_ok { subtype 'Foo' } qr/cannot consist solely of a name/,
+        'Cannot call subtype with a single string argument';
+}
+
+# Back-compat for being called without sugar. Previously, calling with
+# sugar was indistinguishable from calling directly.
+
+{
+    my $type = type( 'Number2', sub { Scalar::Util::looks_like_number($_) } );
+
+    ok( $type->check(5), '... this is a Num' );
+    ok( ! $type->check('Foo'), '... this is not a Num' );
+}
+
+{
+    # anon subtype
+    my $subtype = subtype( 'Number2', sub { $_ > 0 } );
+
+    ok( $subtype->check(5), '... this is a Natural');
+    ok( ! $subtype->check(-5), '... this is not a Natural');
+    ok( ! $subtype->check('Foo'), '... this is not a Natural');
+}
+
+{
+    my $subtype = subtype( 'Natural2', 'Number2', sub { $_ > 0 } );
+
+    ok( $subtype->check(5), '... this is a Natural');
+    ok( ! $subtype->check(-5), '... this is not a Natural');
+    ok( ! $subtype->check('Foo'), '... this is not a Natural');
+}
+
+{
+    my $subtype = subtype( 'Natural3', 'Number2' );
+
+    ok( $subtype->check(5), '... this is a Natural');
+    ok( $subtype->check(-5), '... this is a Natural');
+    ok( ! $subtype->check('Foo'), '... this is not a Natural');
+}
+
diff --git a/t/040_type_constraints/failing/002_util_type_constraints_export.t b/t/040_type_constraints/failing/002_util_type_constraints_export.t
new file mode 100644 (file)
index 0000000..5d5612c
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 4;
+use Test::Exception;
+
+{
+    package Foo;
+
+    use Mouse::Util::TypeConstraints;
+
+    eval {
+        type MyRef => where { ref($_) };
+    };
+    ::ok( !$@, '... successfully exported &type to Foo package' );
+
+    eval {
+        subtype MyArrayRef => as MyRef => where { ref($_) eq 'ARRAY' };
+    };
+    ::ok( !$@, '... successfully exported &subtype to Foo package' );
+
+    Mouse::Util::TypeConstraints->export_type_constraints_as_functions();
+
+    ::ok( MyRef( {} ), '... Ref worked correctly' );
+    ::ok( MyArrayRef( [] ), '... ArrayRef worked correctly' );
+}
diff --git a/t/040_type_constraints/failing/003_util_std_type_constraints.t b/t/040_type_constraints/failing/003_util_std_type_constraints.t
new file mode 100644 (file)
index 0000000..6ba81b7
--- /dev/null
@@ -0,0 +1,358 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 297;
+use Test::Exception;
+
+use Scalar::Util ();
+
+BEGIN {
+    use_ok('Mouse::Util::TypeConstraints');
+}
+
+my $SCALAR_REF = \(my $var);
+
+no warnings 'once'; # << I *hates* that warning ...
+my $GLOB       = *GLOB_REF;
+my $GLOB_REF   = \$GLOB;
+
+my $fh;
+open($fh, '<', $0) || die "Could not open $0 for the test";
+
+my $fh_obj = bless {}, "IO::Handle"; # not really
+
+Mouse::Util::TypeConstraints->export_type_constraints_as_functions();
+
+ok(defined Any(0),               '... Any accepts anything');
+ok(defined Any(100),             '... Any accepts anything');
+ok(defined Any(''),              '... Any accepts anything');
+ok(defined Any('Foo'),           '... Any accepts anything');
+ok(defined Any([]),              '... Any accepts anything');
+ok(defined Any({}),              '... Any accepts anything');
+ok(defined Any(sub {}),          '... Any accepts anything');
+ok(defined Any($SCALAR_REF),     '... Any accepts anything');
+ok(defined Any($GLOB),           '... Any accepts anything');
+ok(defined Any($GLOB_REF),       '... Any accepts anything');
+ok(defined Any($fh),             '... Any accepts anything');
+ok(defined Any(qr/../),          '... Any accepts anything');
+ok(defined Any(bless {}, 'Foo'), '... Any accepts anything');
+ok(defined Any(undef),           '... Any accepts anything');
+
+ok(defined Item(0),               '... Item is the base type, so accepts anything');
+ok(defined Item(100),             '... Item is the base type, so accepts anything');
+ok(defined Item(''),              '... Item is the base type, so accepts anything');
+ok(defined Item('Foo'),           '... Item is the base type, so accepts anything');
+ok(defined Item([]),              '... Item is the base type, so accepts anything');
+ok(defined Item({}),              '... Item is the base type, so accepts anything');
+ok(defined Item(sub {}),          '... Item is the base type, so accepts anything');
+ok(defined Item($SCALAR_REF),     '... Item is the base type, so accepts anything');
+ok(defined Item($GLOB),           '... Item is the base type, so accepts anything');
+ok(defined Item($GLOB_REF),       '... Item is the base type, so accepts anything');
+ok(defined Item($fh),             '... Item is the base type, so accepts anything');
+ok(defined Item(qr/../),          '... Item is the base type, so accepts anything');
+ok(defined Item(bless {}, 'Foo'), '... Item is the base type, so accepts anything');
+ok(defined Item(undef),           '... Item is the base type, so accepts anything');
+
+ok(defined Defined(0),               '... Defined accepts anything which is defined');
+ok(defined Defined(100),             '... Defined accepts anything which is defined');
+ok(defined Defined(''),              '... Defined accepts anything which is defined');
+ok(defined Defined('Foo'),           '... Defined accepts anything which is defined');
+ok(defined Defined([]),              '... Defined accepts anything which is defined');
+ok(defined Defined({}),              '... Defined accepts anything which is defined');
+ok(defined Defined(sub {}),          '... Defined accepts anything which is defined');
+ok(defined Defined($SCALAR_REF),     '... Defined accepts anything which is defined');
+ok(defined Defined($GLOB),           '... Defined accepts anything which is defined');
+ok(defined Defined($GLOB_REF),       '... Defined accepts anything which is defined');
+ok(defined Defined($fh),             '... Defined accepts anything which is defined');
+ok(defined Defined(qr/../),          '... Defined accepts anything which is defined');
+ok(defined Defined(bless {}, 'Foo'), '... Defined accepts anything which is defined');
+ok(!defined Defined(undef),          '... Defined accepts anything which is defined');
+
+ok(!defined Undef(0),               '... Undef accepts anything which is not defined');
+ok(!defined Undef(100),             '... Undef accepts anything which is not defined');
+ok(!defined Undef(''),              '... Undef accepts anything which is not defined');
+ok(!defined Undef('Foo'),           '... Undef accepts anything which is not defined');
+ok(!defined Undef([]),              '... Undef accepts anything which is not defined');
+ok(!defined Undef({}),              '... Undef accepts anything which is not defined');
+ok(!defined Undef(sub {}),          '... Undef accepts anything which is not defined');
+ok(!defined Undef($SCALAR_REF),     '... Undef accepts anything which is not defined');
+ok(!defined Undef($GLOB),           '... Undef accepts anything which is not defined');
+ok(!defined Undef($GLOB_REF),       '... Undef accepts anything which is not defined');
+ok(!defined Undef($fh),             '... Undef accepts anything which is not defined');
+ok(!defined Undef(qr/../),          '... Undef accepts anything which is not defined');
+ok(!defined Undef(bless {}, 'Foo'), '... Undef accepts anything which is not defined');
+ok(defined Undef(undef),            '... Undef accepts anything which is not defined');
+
+ok(defined Bool(0),                 '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(defined Bool(1),                 '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(!defined Bool(100),              '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(defined Bool(''),                '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(!defined Bool('Foo'),            '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(!defined Bool([]),               '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(!defined Bool({}),               '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(!defined Bool(sub {}),           '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(!defined Bool($SCALAR_REF),      '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(!defined Bool($GLOB),            '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(!defined Bool($GLOB_REF),        '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(!defined Bool($fh),              '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(!defined Bool(qr/../),           '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(!defined Bool(bless {}, 'Foo'),  '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+ok(defined Bool(undef),             '... Bool rejects anything which is not a 1 or 0 or "" or undef');
+
+ok(defined Value(0),                 '... Value accepts anything which is not a Ref');
+ok(defined Value(100),               '... Value accepts anything which is not a Ref');
+ok(defined Value(''),                '... Value accepts anything which is not a Ref');
+ok(defined Value('Foo'),             '... Value accepts anything which is not a Ref');
+ok(!defined Value([]),               '... Value rejects anything which is not a Value');
+ok(!defined Value({}),               '... Value rejects anything which is not a Value');
+ok(!defined Value(sub {}),           '... Value rejects anything which is not a Value');
+ok(!defined Value($SCALAR_REF),      '... Value rejects anything which is not a Value');
+ok(defined Value($GLOB),             '... Value accepts anything which is not a Ref');
+ok(!defined Value($GLOB_REF),        '... Value rejects anything which is not a Value');
+ok(!defined Value($fh),              '... Value rejects anything which is not a Value');
+ok(!defined Value(qr/../),           '... Value rejects anything which is not a Value');
+ok(!defined Value(bless {}, 'Foo'),  '... Value rejects anything which is not a Value');
+ok(!defined Value(undef),            '... Value rejects anything which is not a Value');
+
+ok(!defined Ref(0),               '... Ref accepts anything which is not a Value');
+ok(!defined Ref(100),             '... Ref accepts anything which is not a Value');
+ok(!defined Ref(''),              '... Ref accepts anything which is not a Value');
+ok(!defined Ref('Foo'),           '... Ref accepts anything which is not a Value');
+ok(defined Ref([]),               '... Ref rejects anything which is not a Ref');
+ok(defined Ref({}),               '... Ref rejects anything which is not a Ref');
+ok(defined Ref(sub {}),           '... Ref rejects anything which is not a Ref');
+ok(defined Ref($SCALAR_REF),      '... Ref rejects anything which is not a Ref');
+ok(!defined Ref($GLOB),           '... Ref accepts anything which is not a Value');
+ok(defined Ref($GLOB_REF),        '... Ref rejects anything which is not a Ref');
+ok(defined Ref($fh),              '... Ref rejects anything which is not a Ref');
+ok(defined Ref(qr/../),           '... Ref rejects anything which is not a Ref');
+ok(defined Ref(bless {}, 'Foo'),  '... Ref rejects anything which is not a Ref');
+ok(!defined Ref(undef),           '... Ref rejects anything which is not a Ref');
+
+ok(defined Int(0),                 '... Int accepts anything which is an Int');
+ok(defined Int(100),               '... Int accepts anything which is an Int');
+ok(!defined Int(0.5),              '... Int accepts anything which is not a Int');
+ok(!defined Int(100.01),           '... Int accepts anything which is not a Int');
+ok(!defined Int(''),               '... Int rejects anything which is not a Int');
+ok(!defined Int('Foo'),            '... Int rejects anything which is not a Int');
+ok(!defined Int([]),               '... Int rejects anything which is not a Int');
+ok(!defined Int({}),               '... Int rejects anything which is not a Int');
+ok(!defined Int(sub {}),           '... Int rejects anything which is not a Int');
+ok(!defined Int($SCALAR_REF),      '... Int rejects anything which is not a Int');
+ok(!defined Int($GLOB),            '... Int rejects anything which is not a Int');
+ok(!defined Int($GLOB_REF),        '... Int rejects anything which is not a Int');
+ok(!defined Int($fh),              '... Int rejects anything which is not a Int');
+ok(!defined Int(qr/../),           '... Int rejects anything which is not a Int');
+ok(!defined Int(bless {}, 'Foo'),  '... Int rejects anything which is not a Int');
+ok(!defined Int(undef),            '... Int rejects anything which is not a Int');
+
+ok(defined Num(0),                 '... Num accepts anything which is an Num');
+ok(defined Num(100),               '... Num accepts anything which is an Num');
+ok(defined Num(0.5),               '... Num accepts anything which is an Num');
+ok(defined Num(100.01),            '... Num accepts anything which is an Num');
+ok(!defined Num(''),               '... Num rejects anything which is not a Num');
+ok(!defined Num('Foo'),            '... Num rejects anything which is not a Num');
+ok(!defined Num([]),               '... Num rejects anything which is not a Num');
+ok(!defined Num({}),               '... Num rejects anything which is not a Num');
+ok(!defined Num(sub {}),           '... Num rejects anything which is not a Num');
+ok(!defined Num($SCALAR_REF),      '... Num rejects anything which is not a Num');
+ok(!defined Num($GLOB),            '... Num rejects anything which is not a Num');
+ok(!defined Num($GLOB_REF),        '... Num rejects anything which is not a Num');
+ok(!defined Num($fh),              '... Num rejects anything which is not a Num');
+ok(!defined Num(qr/../),           '... Num rejects anything which is not a Num');
+ok(!defined Num(bless {}, 'Foo'),  '... Num rejects anything which is not a Num');
+ok(!defined Num(undef),            '... Num rejects anything which is not a Num');
+
+ok(defined Str(0),                 '... Str accepts anything which is a Str');
+ok(defined Str(100),               '... Str accepts anything which is a Str');
+ok(defined Str(''),                '... Str accepts anything which is a Str');
+ok(defined Str('Foo'),             '... Str accepts anything which is a Str');
+ok(!defined Str([]),               '... Str rejects anything which is not a Str');
+ok(!defined Str({}),               '... Str rejects anything which is not a Str');
+ok(!defined Str(sub {}),           '... Str rejects anything which is not a Str');
+ok(!defined Str($SCALAR_REF),      '... Str rejects anything which is not a Str');
+ok(!defined Str($fh),              '... Str rejects anything which is not a Str');
+ok(!defined Str($GLOB),            '... Str rejects anything which is not a Str');
+ok(!defined Str($GLOB_REF),        '... Str rejects anything which is not a Str');
+ok(!defined Str(qr/../),           '... Str rejects anything which is not a Str');
+ok(!defined Str(bless {}, 'Foo'),  '... Str rejects anything which is not a Str');
+ok(!defined Str(undef),            '... Str rejects anything which is not a Str');
+
+ok(!defined ScalarRef(0),                '... ScalarRef rejects anything which is not a ScalarRef');
+ok(!defined ScalarRef(100),              '... ScalarRef rejects anything which is not a ScalarRef');
+ok(!defined ScalarRef(''),               '... ScalarRef rejects anything which is not a ScalarRef');
+ok(!defined ScalarRef('Foo'),            '... ScalarRef rejects anything which is not a ScalarRef');
+ok(!defined ScalarRef([]),               '... ScalarRef rejects anything which is not a ScalarRef');
+ok(!defined ScalarRef({}),               '... ScalarRef rejects anything which is not a ScalarRef');
+ok(!defined ScalarRef(sub {}),           '... ScalarRef rejects anything which is not a ScalarRef');
+ok(defined ScalarRef($SCALAR_REF),       '... ScalarRef accepts anything which is a ScalarRef');
+ok(!defined ScalarRef($GLOB),            '... ScalarRef rejects anything which is not a ScalarRef');
+ok(!defined ScalarRef($GLOB_REF),        '... ScalarRef rejects anything which is not a ScalarRef');
+ok(!defined ScalarRef($fh),              '... ScalarRef rejects anything which is not a ScalarRef');
+ok(!defined ScalarRef(qr/../),           '... ScalarRef rejects anything which is not a ScalarRef');
+ok(!defined ScalarRef(bless {}, 'Foo'),  '... ScalarRef rejects anything which is not a ScalarRef');
+ok(!defined ScalarRef(undef),            '... ScalarRef rejects anything which is not a ScalarRef');
+
+ok(!defined ArrayRef(0),                '... ArrayRef rejects anything which is not a ArrayRef');
+ok(!defined ArrayRef(100),              '... ArrayRef rejects anything which is not a ArrayRef');
+ok(!defined ArrayRef(''),               '... ArrayRef rejects anything which is not a ArrayRef');
+ok(!defined ArrayRef('Foo'),            '... ArrayRef rejects anything which is not a ArrayRef');
+ok(defined ArrayRef([]),                '... ArrayRef accepts anything which is a ArrayRef');
+ok(!defined ArrayRef({}),               '... ArrayRef rejects anything which is not a ArrayRef');
+ok(!defined ArrayRef(sub {}),           '... ArrayRef rejects anything which is not a ArrayRef');
+ok(!defined ArrayRef($SCALAR_REF),      '... ArrayRef rejects anything which is not a ArrayRef');
+ok(!defined ArrayRef($GLOB),            '... ArrayRef rejects anything which is not a ArrayRef');
+ok(!defined ArrayRef($GLOB_REF),        '... ArrayRef rejects anything which is not a ArrayRef');
+ok(!defined ArrayRef($fh),              '... ArrayRef rejects anything which is not a ArrayRef');
+ok(!defined ArrayRef(qr/../),           '... ArrayRef rejects anything which is not a ArrayRef');
+ok(!defined ArrayRef(bless {}, 'Foo'),  '... ArrayRef rejects anything which is not a ArrayRef');
+ok(!defined ArrayRef(undef),            '... ArrayRef rejects anything which is not a ArrayRef');
+
+ok(!defined HashRef(0),                '... HashRef rejects anything which is not a HashRef');
+ok(!defined HashRef(100),              '... HashRef rejects anything which is not a HashRef');
+ok(!defined HashRef(''),               '... HashRef rejects anything which is not a HashRef');
+ok(!defined HashRef('Foo'),            '... HashRef rejects anything which is not a HashRef');
+ok(!defined HashRef([]),               '... HashRef rejects anything which is not a HashRef');
+ok(defined HashRef({}),                '... HashRef accepts anything which is a HashRef');
+ok(!defined HashRef(sub {}),           '... HashRef rejects anything which is not a HashRef');
+ok(!defined HashRef($SCALAR_REF),      '... HashRef rejects anything which is not a HashRef');
+ok(!defined HashRef($GLOB),            '... HashRef rejects anything which is not a HashRef');
+ok(!defined HashRef($GLOB_REF),        '... HashRef rejects anything which is not a HashRef');
+ok(!defined HashRef($fh),              '... HashRef rejects anything which is not a HashRef');
+ok(!defined HashRef(qr/../),           '... HashRef rejects anything which is not a HashRef');
+ok(!defined HashRef(bless {}, 'Foo'),  '... HashRef rejects anything which is not a HashRef');
+ok(!defined HashRef(undef),            '... HashRef rejects anything which is not a HashRef');
+
+ok(!defined CodeRef(0),                '... CodeRef rejects anything which is not a CodeRef');
+ok(!defined CodeRef(100),              '... CodeRef rejects anything which is not a CodeRef');
+ok(!defined CodeRef(''),               '... CodeRef rejects anything which is not a CodeRef');
+ok(!defined CodeRef('Foo'),            '... CodeRef rejects anything which is not a CodeRef');
+ok(!defined CodeRef([]),               '... CodeRef rejects anything which is not a CodeRef');
+ok(!defined CodeRef({}),               '... CodeRef rejects anything which is not a CodeRef');
+ok(defined CodeRef(sub {}),            '... CodeRef accepts anything which is a CodeRef');
+ok(!defined CodeRef($SCALAR_REF),      '... CodeRef rejects anything which is not a CodeRef');
+ok(!defined CodeRef($GLOB),            '... CodeRef rejects anything which is not a CodeRef');
+ok(!defined CodeRef($GLOB_REF),        '... CodeRef rejects anything which is not a CodeRef');
+ok(!defined CodeRef($fh),              '... CodeRef rejects anything which is not a CodeRef');
+ok(!defined CodeRef(qr/../),           '... CodeRef rejects anything which is not a CodeRef');
+ok(!defined CodeRef(bless {}, 'Foo'),  '... CodeRef rejects anything which is not a CodeRef');
+ok(!defined CodeRef(undef),            '... CodeRef rejects anything which is not a CodeRef');
+
+ok(!defined RegexpRef(0),                '... RegexpRef rejects anything which is not a RegexpRef');
+ok(!defined RegexpRef(100),              '... RegexpRef rejects anything which is not a RegexpRef');
+ok(!defined RegexpRef(''),               '... RegexpRef rejects anything which is not a RegexpRef');
+ok(!defined RegexpRef('Foo'),            '... RegexpRef rejects anything which is not a RegexpRef');
+ok(!defined RegexpRef([]),               '... RegexpRef rejects anything which is not a RegexpRef');
+ok(!defined RegexpRef({}),               '... RegexpRef rejects anything which is not a RegexpRef');
+ok(!defined RegexpRef(sub {}),           '... RegexpRef rejects anything which is not a RegexpRef');
+ok(!defined RegexpRef($SCALAR_REF),      '... RegexpRef rejects anything which is not a RegexpRef');
+ok(!defined RegexpRef($GLOB),            '... RegexpRef rejects anything which is not a RegexpRef');
+ok(!defined RegexpRef($GLOB_REF),        '... RegexpRef rejects anything which is not a RegexpRef');
+ok(!defined RegexpRef($fh),              '... RegexpRef rejects anything which is not a RegexpRef');
+ok(defined RegexpRef(qr/../),            '... RegexpRef accepts anything which is a RegexpRef');
+ok(!defined RegexpRef(bless {}, 'Foo'),  '... RegexpRef rejects anything which is not a RegexpRef');
+ok(!defined RegexpRef(undef),            '... RegexpRef rejects anything which is not a RegexpRef');
+
+ok(!defined GlobRef(0),                '... GlobRef rejects anything which is not a GlobRef');
+ok(!defined GlobRef(100),              '... GlobRef rejects anything which is not a GlobRef');
+ok(!defined GlobRef(''),               '... GlobRef rejects anything which is not a GlobRef');
+ok(!defined GlobRef('Foo'),            '... GlobRef rejects anything which is not a GlobRef');
+ok(!defined GlobRef([]),               '... GlobRef rejects anything which is not a GlobRef');
+ok(!defined GlobRef({}),               '... GlobRef rejects anything which is not a GlobRef');
+ok(!defined GlobRef(sub {}),           '... GlobRef rejects anything which is not a GlobRef');
+ok(!defined GlobRef($SCALAR_REF),      '... GlobRef rejects anything which is not a GlobRef');
+ok(!defined GlobRef($GLOB),            '... GlobRef rejects anything which is not a GlobRef');
+ok(defined GlobRef($GLOB_REF),         '... GlobRef accepts anything which is a GlobRef');
+ok(defined GlobRef($fh),               '... GlobRef accepts anything which is a GlobRef');
+ok(!defined GlobRef($fh_obj),          '... GlobRef rejects anything which is not a GlobRef');
+ok(!defined GlobRef(qr/../),           '... GlobRef rejects anything which is not a GlobRef');
+ok(!defined GlobRef(bless {}, 'Foo'),  '... GlobRef rejects anything which is not a GlobRef');
+ok(!defined GlobRef(undef),            '... GlobRef rejects anything which is not a GlobRef');
+
+ok(!defined FileHandle(0),                '... FileHandle rejects anything which is not a FileHandle');
+ok(!defined FileHandle(100),              '... FileHandle rejects anything which is not a FileHandle');
+ok(!defined FileHandle(''),               '... FileHandle rejects anything which is not a FileHandle');
+ok(!defined FileHandle('Foo'),            '... FileHandle rejects anything which is not a FileHandle');
+ok(!defined FileHandle([]),               '... FileHandle rejects anything which is not a FileHandle');
+ok(!defined FileHandle({}),               '... FileHandle rejects anything which is not a FileHandle');
+ok(!defined FileHandle(sub {}),           '... FileHandle rejects anything which is not a FileHandle');
+ok(!defined FileHandle($SCALAR_REF),      '... FileHandle rejects anything which is not a FileHandle');
+ok(!defined FileHandle($GLOB),            '... FileHandle rejects anything which is not a FileHandle');
+ok(!defined FileHandle($GLOB_REF),        '... FileHandle rejects anything which is not a FileHandle');
+ok(defined FileHandle($fh),               '... FileHandle accepts anything which is a FileHandle');
+ok(defined FileHandle($fh_obj),           '... FileHandle accepts anything which is a FileHandle');
+ok(!defined FileHandle(qr/../),           '... FileHandle rejects anything which is not a FileHandle');
+ok(!defined FileHandle(bless {}, 'Foo'),  '... FileHandle rejects anything which is not a FileHandle');
+ok(!defined FileHandle(undef),            '... FileHandle rejects anything which is not a FileHandle');
+
+ok(!defined Object(0),                '... Object rejects anything which is not blessed');
+ok(!defined Object(100),              '... Object rejects anything which is not blessed');
+ok(!defined Object(''),               '... Object rejects anything which is not blessed');
+ok(!defined Object('Foo'),            '... Object rejects anything which is not blessed');
+ok(!defined Object([]),               '... Object rejects anything which is not blessed');
+ok(!defined Object({}),               '... Object rejects anything which is not blessed');
+ok(!defined Object(sub {}),           '... Object rejects anything which is not blessed');
+ok(!defined Object($SCALAR_REF),      '... Object rejects anything which is not blessed');
+ok(!defined Object($GLOB),            '... Object rejects anything which is not blessed');
+ok(!defined Object($GLOB_REF),        '... Object rejects anything which is not blessed');
+ok(!defined Object($fh),              '... Object rejects anything which is not blessed');
+ok(!defined Object(qr/../),           '... Object rejects anything which is not blessed');
+ok(defined Object(bless {}, 'Foo'),   '... Object accepts anything which is blessed');
+ok(!defined Object(undef),             '... Object accepts anything which is blessed');
+
+ok(!defined ClassName(0),               '... ClassName rejects anything which is not a ClassName');
+ok(!defined ClassName(100),             '... ClassName rejects anything which is not a ClassName');
+ok(!defined ClassName(''),              '... ClassName rejects anything which is not a ClassName');
+ok(!defined ClassName('Baz'),           '... ClassName rejects anything which is not a ClassName');
+
+{
+  package Quux::Wibble; # this makes Quux symbol table exist
+
+  sub foo {}
+}
+
+ok(!defined ClassName('Quux'),           '... ClassName rejects anything which is not a ClassName');
+ok(!defined ClassName([]),              '... ClassName rejects anything which is not a ClassName');
+ok(!defined ClassName({}),              '... ClassName rejects anything which is not a ClassName');
+ok(!defined ClassName(sub {}),          '... ClassName rejects anything which is not a ClassName');
+ok(!defined ClassName($SCALAR_REF),     '... ClassName rejects anything which is not a ClassName');
+ok(!defined ClassName($fh),             '... ClassName rejects anything which is not a ClassName');
+ok(!defined ClassName($GLOB),           '... ClassName rejects anything which is not a ClassName');
+ok(!defined ClassName($GLOB_REF),       '... ClassName rejects anything which is not a ClassName');
+ok(!defined ClassName(qr/../),          '... ClassName rejects anything which is not a ClassName');
+ok(!defined ClassName(bless {}, 'Foo'), '... ClassName rejects anything which is not a ClassName');
+ok(!defined ClassName(undef),           '... ClassName rejects anything which is not a ClassName');
+ok(defined ClassName('UNIVERSAL'),      '... ClassName accepts anything which is a ClassName');
+ok(defined ClassName('Quux::Wibble'),      '... ClassName accepts anything which is a ClassName');
+ok(defined ClassName('Mouse::Meta::TypeConstraint'), '... ClassName accepts anything which is a ClassName');
+
+ok(!defined RoleName(0),               '... RoleName rejects anything which is not a RoleName');
+ok(!defined RoleName(100),             '... RoleName rejects anything which is not a RoleName');
+ok(!defined RoleName(''),              '... RoleName rejects anything which is not a RoleName');
+ok(!defined RoleName('Baz'),           '... RoleName rejects anything which is not a RoleName');
+
+{
+  package Quux::Wibble::Role; # this makes Quux symbol table exist
+  use Mouse::Role;
+  sub foo {}
+}
+
+ok(!defined RoleName('Quux'),           '... RoleName rejects anything which is not a RoleName');
+ok(!defined RoleName([]),              '... Rolename rejects anything which is not a RoleName');
+ok(!defined RoleName({}),              '... Rolename rejects anything which is not a RoleName');
+ok(!defined RoleName(sub {}),          '... Rolename rejects anything which is not a RoleName');
+ok(!defined RoleName($SCALAR_REF),     '... Rolename rejects anything which is not a RoleName');
+ok(!defined RoleName($fh),             '... Rolename rejects anything which is not a RoleName');
+ok(!defined RoleName($GLOB),           '... Rolename rejects anything which is not a RoleName');
+ok(!defined RoleName($GLOB_REF),       '... Rolename rejects anything which is not a RoleName');
+ok(!defined RoleName(qr/../),          '... Rolename rejects anything which is not a RoleName');
+ok(!defined RoleName(bless {}, 'Foo'), '... Rolename rejects anything which is not a RoleName');
+ok(!defined RoleName(undef),           '... Rolename rejects anything which is not a RoleName');
+ok(!defined RoleName('UNIVERSAL'),     '... Rolename rejects anything which is not a RoleName');
+ok(!defined RoleName('Quux::Wibble'),  '... Rolename rejects anything which is not a RoleName');
+ok(!defined RoleName('Mouse::Meta::TypeConstraint'),  '... RoleName accepts anything which is a RoleName');
+ok(defined RoleName('Quux::Wibble::Role'),      '... RoleName accepts anything which is a RoleName');
+
+close($fh) || die "Could not close the filehandle $0 for test";
diff --git a/t/040_type_constraints/failing/004_util_find_type_constraint.t b/t/040_type_constraints/failing/004_util_find_type_constraint.t
new file mode 100644 (file)
index 0000000..f7dcf1a
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 18;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Mouse::Util::TypeConstraints');
+}
+
+foreach my $type_name (qw(
+    Any
+    Item
+        Bool
+        Undef
+        Defined
+            Value
+                Num
+                  Int
+                Str
+            Ref
+                ScalarRef
+                ArrayRef
+                HashRef
+                CodeRef
+                RegexpRef
+                Object
+                    Role
+    )) {
+    is(find_type_constraint($type_name)->name,
+       $type_name,
+       '... got the right name for ' . $type_name);
+}
+
+# TODO:
+# add tests for is_subtype_of which confirm the hierarchy
diff --git a/t/040_type_constraints/failing/005_util_type_coercion.t b/t/040_type_constraints/failing/005_util_type_coercion.t
new file mode 100644 (file)
index 0000000..8b5a77a
--- /dev/null
@@ -0,0 +1,102 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 26;
+use Test::Exception;
+
+BEGIN {
+       use_ok('Mouse::Util::TypeConstraints');
+}
+
+{
+    package HTTPHeader;
+    use Mouse;
+
+    has 'array' => (is => 'ro');
+    has 'hash'  => (is => 'ro');
+}
+
+subtype Header =>
+    => as Object
+    => where { $_->isa('HTTPHeader') };
+
+coerce Header
+    => from ArrayRef
+        => via { HTTPHeader->new(array => $_[0]) }
+    => from HashRef
+        => via { HTTPHeader->new(hash => $_[0]) };
+
+
+Mouse::Util::TypeConstraints->export_type_constraints_as_functions();
+
+my $header = HTTPHeader->new();
+isa_ok($header, 'HTTPHeader');
+
+ok(Header($header), '... this passed the type test');
+ok(!Header([]), '... this did not pass the type test');
+ok(!Header({}), '... this did not pass the type test');
+
+my $anon_type = subtype Object => where { $_->isa('HTTPHeader') };
+
+lives_ok {
+    coerce $anon_type
+        => from ArrayRef
+            => via { HTTPHeader->new(array => $_[0]) }
+        => from HashRef
+            => via { HTTPHeader->new(hash => $_[0]) };
+} 'coercion of anonymous subtype succeeds';
+
+foreach my $coercion (
+    find_type_constraint('Header')->coercion,
+    $anon_type->coercion
+    ) {
+
+    isa_ok($coercion, 'Mouse::Meta::TypeCoercion');
+
+    {
+        my $coerced = $coercion->coerce([ 1, 2, 3 ]);
+        isa_ok($coerced, 'HTTPHeader');
+
+        is_deeply(
+            $coerced->array(),
+            [ 1, 2, 3 ],
+            '... got the right array');
+        is($coerced->hash(), undef, '... nothing assigned to the hash');
+    }
+
+    {
+        my $coerced = $coercion->coerce({ one => 1, two => 2, three => 3 });
+        isa_ok($coerced, 'HTTPHeader');
+
+        is_deeply(
+            $coerced->hash(),
+            { one => 1, two => 2, three => 3 },
+            '... got the right hash');
+        is($coerced->array(), undef, '... nothing assigned to the array');
+    }
+
+    {
+        my $scalar_ref = \(my $var);
+        my $coerced = $coercion->coerce($scalar_ref);
+        is($coerced, $scalar_ref, '... got back what we put in');
+    }
+
+    {
+        my $coerced = $coercion->coerce("Foo");
+        is($coerced, "Foo", '... got back what we put in');
+    }
+}
+
+subtype 'StrWithTrailingX'
+    => as 'Str'
+    => where { /X$/ };
+
+coerce 'StrWithTrailingX'
+    => from 'Str'
+    => via { $_ . 'X' };
+
+my $tc = find_type_constraint('StrWithTrailingX');
+is($tc->coerce("foo"), "fooX", "coerce when needed");
+is($tc->coerce("fooX"), "fooX", "do not coerce when unneeded");
diff --git a/t/040_type_constraints/failing/006_util_type_reloading.t b/t/040_type_constraints/failing/006_util_type_reloading.t
new file mode 100644 (file)
index 0000000..4cde153
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib 't/lib', 'lib';
+
+use Test::More tests => 4;
+use Test::Exception;
+
+
+
+$SIG{__WARN__} = sub { 0 };
+
+eval { require Foo; };
+ok(!$@, '... loaded Foo successfully') || diag $@;
+
+delete $INC{'Foo.pm'};
+
+eval { require Foo; };
+ok(!$@, '... re-loaded Foo successfully') || diag $@;
+
+eval { require Bar; };
+ok(!$@, '... loaded Bar successfully') || diag $@;
+
+delete $INC{'Bar.pm'};
+
+eval { require Bar; };
+ok(!$@, '... re-loaded Bar successfully') || diag $@;
\ No newline at end of file
diff --git a/t/040_type_constraints/failing/007_util_more_type_coercion.t b/t/040_type_constraints/failing/007_util_more_type_coercion.t
new file mode 100644 (file)
index 0000000..1cfa831
--- /dev/null
@@ -0,0 +1,117 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 25;
+use Test::Exception;
+
+
+
+{
+    package HTTPHeader;
+    use Mouse;
+    use Mouse::Util::TypeConstraints;
+
+    coerce 'HTTPHeader'
+        => from ArrayRef
+            => via { HTTPHeader->new(array => $_[0]) };
+
+    coerce 'HTTPHeader'
+        => from HashRef
+            => via { HTTPHeader->new(hash => $_[0]) };
+
+    has 'array' => (is => 'ro');
+    has 'hash'  => (is => 'ro');
+
+    package Engine;
+    use strict;
+    use warnings;
+    use Mouse;
+
+    has 'header' => (is => 'rw', isa => 'HTTPHeader', coerce => 1);
+}
+
+{
+    my $engine = Engine->new();
+    isa_ok($engine, 'Engine');
+
+    # try with arrays
+
+    lives_ok {
+        $engine->header([ 1, 2, 3 ]);
+    } '... type was coerced without incident';
+    isa_ok($engine->header, 'HTTPHeader');
+
+    is_deeply(
+        $engine->header->array,
+        [ 1, 2, 3 ],
+        '... got the right array value of the header');
+    ok(!defined($engine->header->hash), '... no hash value set');
+
+    # try with hash
+
+    lives_ok {
+        $engine->header({ one => 1, two => 2, three => 3 });
+    } '... type was coerced without incident';
+    isa_ok($engine->header, 'HTTPHeader');
+
+    is_deeply(
+        $engine->header->hash,
+        { one => 1, two => 2, three => 3 },
+        '... got the right hash value of the header');
+    ok(!defined($engine->header->array), '... no array value set');
+
+    dies_ok {
+       $engine->header("Foo");
+    } '... dies with the wrong type, even after coercion';
+
+    lives_ok {
+       $engine->header(HTTPHeader->new);
+    } '... lives with the right type, even after coercion';
+}
+
+{
+    my $engine = Engine->new(header => [ 1, 2, 3 ]);
+    isa_ok($engine, 'Engine');
+
+    isa_ok($engine->header, 'HTTPHeader');
+
+    is_deeply(
+        $engine->header->array,
+        [ 1, 2, 3 ],
+        '... got the right array value of the header');
+    ok(!defined($engine->header->hash), '... no hash value set');
+}
+
+{
+    my $engine = Engine->new(header => { one => 1, two => 2, three => 3 });
+    isa_ok($engine, 'Engine');
+
+    isa_ok($engine->header, 'HTTPHeader');
+
+    is_deeply(
+        $engine->header->hash,
+        { one => 1, two => 2, three => 3 },
+        '... got the right hash value of the header');
+    ok(!defined($engine->header->array), '... no array value set');
+}
+
+{
+    my $engine = Engine->new(header => HTTPHeader->new());
+    isa_ok($engine, 'Engine');
+
+    isa_ok($engine->header, 'HTTPHeader');
+
+    ok(!defined($engine->header->hash), '... no hash value set');
+    ok(!defined($engine->header->array), '... no array value set');
+}
+
+dies_ok {
+    Engine->new(header => 'Foo');
+} '... dies correctly with bad params';
+
+dies_ok {
+    Engine->new(header => \(my $var));
+} '... dies correctly with bad params';
+
diff --git a/t/040_type_constraints/failing/008_union_types.t b/t/040_type_constraints/failing/008_union_types.t
new file mode 100644 (file)
index 0000000..c0c9ce0
--- /dev/null
@@ -0,0 +1,80 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 35;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Mouse::Util::TypeConstraints');
+}
+
+my $Str = find_type_constraint('Str');
+isa_ok($Str, 'Mouse::Meta::TypeConstraint');
+
+my $Undef = find_type_constraint('Undef');
+isa_ok($Undef, 'Mouse::Meta::TypeConstraint');
+
+ok(!$Str->check(undef), '... Str cannot accept an Undef value');
+ok($Str->check('String'), '... Str can accept an String value');
+ok(!$Undef->check('String'), '... Undef cannot accept an Str value');
+ok($Undef->check(undef), '... Undef can accept an Undef value');
+
+my $Str_or_Undef = Mouse::Meta::TypeConstraint::Union->new(type_constraints => [$Str, $Undef]);
+isa_ok($Str_or_Undef, 'Mouse::Meta::TypeConstraint::Union');
+
+ok($Str_or_Undef->check(undef), '... (Str | Undef) can accept an Undef value');
+ok($Str_or_Undef->check('String'), '... (Str | Undef) can accept a String value');
+
+ok($Str_or_Undef->is_a_type_of($Str), "subtype of Str");
+ok($Str_or_Undef->is_a_type_of($Undef), "subtype of Undef");
+
+ok( !$Str_or_Undef->equals($Str), "not equal to Str" );
+ok( $Str_or_Undef->equals($Str_or_Undef), "equal to self" );
+ok( $Str_or_Undef->equals(Mouse::Meta::TypeConstraint::Union->new(type_constraints => [ $Str, $Undef ])), "equal to clone" );
+ok( $Str_or_Undef->equals(Mouse::Meta::TypeConstraint::Union->new(type_constraints => [ $Undef, $Str ])), "equal to reversed clone" );
+
+ok( !$Str_or_Undef->is_a_type_of("ThisTypeDoesNotExist"), "not type of non existant type" );
+ok( !$Str_or_Undef->is_subtype_of("ThisTypeDoesNotExist"), "not subtype of non existant type" );
+
+# another ....
+
+my $ArrayRef = find_type_constraint('ArrayRef');
+isa_ok($ArrayRef, 'Mouse::Meta::TypeConstraint');
+
+my $HashRef = find_type_constraint('HashRef');
+isa_ok($HashRef, 'Mouse::Meta::TypeConstraint');
+
+ok($ArrayRef->check([]), '... ArrayRef can accept an [] value');
+ok(!$ArrayRef->check({}), '... ArrayRef cannot accept an {} value');
+ok($HashRef->check({}), '... HashRef can accept an {} value');
+ok(!$HashRef->check([]), '... HashRef cannot accept an [] value');
+
+my $HashOrArray = Mouse::Meta::TypeConstraint::Union->new(type_constraints => [$ArrayRef, $HashRef]);
+isa_ok($HashOrArray, 'Mouse::Meta::TypeConstraint::Union');
+
+ok($HashOrArray->check([]), '... (ArrayRef | HashRef) can accept []');
+ok($HashOrArray->check({}), '... (ArrayRef | HashRef) can accept {}');
+
+ok(!$HashOrArray->check(\(my $var1)), '... (ArrayRef | HashRef) cannot accept scalar refs');
+ok(!$HashOrArray->check(sub {}), '... (ArrayRef | HashRef) cannot accept code refs');
+ok(!$HashOrArray->check(50), '... (ArrayRef | HashRef) cannot accept Numbers');
+
+diag $HashOrArray->validate([]);
+
+ok(!defined($HashOrArray->validate([])), '... (ArrayRef | HashRef) can accept []');
+ok(!defined($HashOrArray->validate({})), '... (ArrayRef | HashRef) can accept {}');
+
+like($HashOrArray->validate(\(my $var2)),
+qr/Validation failed for \'ArrayRef\' failed with value SCALAR\(0x.+?\) and Validation failed for \'HashRef\' failed with value SCALAR\(0x.+?\) in \(ArrayRef\|HashRef\)/,
+'... (ArrayRef | HashRef) cannot accept scalar refs');
+
+like($HashOrArray->validate(sub {}),
+qr/Validation failed for \'ArrayRef\' failed with value CODE\(0x.+?\) and Validation failed for \'HashRef\' failed with value CODE\(0x.+?\) in \(ArrayRef\|HashRef\)/,
+'... (ArrayRef | HashRef) cannot accept code refs');
+
+is($HashOrArray->validate(50),
+'Validation failed for \'ArrayRef\' failed with value 50 and Validation failed for \'HashRef\' failed with value 50 in (ArrayRef|HashRef)',
+'... (ArrayRef | HashRef) cannot accept Numbers');
+
diff --git a/t/040_type_constraints/failing/009_union_types_and_coercions.t b/t/040_type_constraints/failing/009_union_types_and_coercions.t
new file mode 100644 (file)
index 0000000..5dd13ea
--- /dev/null
@@ -0,0 +1,162 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use Test::Exception;
+
+BEGIN {
+    eval "use IO::String; use IO::File;";
+    plan skip_all => "IO::String and IO::File are required for this test" if $@;
+    plan tests => 28;
+}
+
+
+
+{
+    package Email::Mouse;
+    use Mouse;
+    use Mouse::Util::TypeConstraints;
+
+    use IO::String;
+
+    our $VERSION = '0.01';
+
+    # create subtype for IO::String
+
+    subtype 'IO::String'
+        => as 'Object'
+        => where { $_->isa('IO::String') };
+
+    coerce 'IO::String'
+        => from 'Str'
+            => via { IO::String->new($_) },
+        => from 'ScalarRef',
+            => via { IO::String->new($_) };
+
+    # create subtype for IO::File
+
+    subtype 'IO::File'
+        => as 'Object'
+        => where { $_->isa('IO::File') };
+
+    coerce 'IO::File'
+        => from 'FileHandle'
+            => via { bless $_, 'IO::File' };
+
+    # create the alias
+
+    subtype 'IO::StringOrFile' => as 'IO::String | IO::File';
+
+    # attributes
+
+    has 'raw_body' => (
+        is      => 'rw',
+        isa     => 'IO::StringOrFile',
+        coerce  => 1,
+        default => sub { IO::String->new() },
+    );
+
+    sub as_string {
+        my ($self) = @_;
+        my $fh = $self->raw_body();
+        return do { local $/; <$fh> };
+    }
+}
+
+{
+    my $email = Email::Mouse->new;
+    isa_ok($email, 'Email::Mouse');
+
+    isa_ok($email->raw_body, 'IO::String');
+
+    is($email->as_string, undef, '... got correct empty string');
+}
+
+{
+    my $email = Email::Mouse->new(raw_body => '... this is my body ...');
+    isa_ok($email, 'Email::Mouse');
+
+    isa_ok($email->raw_body, 'IO::String');
+
+    is($email->as_string, '... this is my body ...', '... got correct string');
+
+    lives_ok {
+        $email->raw_body('... this is the next body ...');
+    } '... this will coerce correctly';
+
+    isa_ok($email->raw_body, 'IO::String');
+
+    is($email->as_string, '... this is the next body ...', '... got correct string');
+}
+
+{
+    my $str = '... this is my body (ref) ...';
+
+    my $email = Email::Mouse->new(raw_body => \$str);
+    isa_ok($email, 'Email::Mouse');
+
+    isa_ok($email->raw_body, 'IO::String');
+
+    is($email->as_string, $str, '... got correct string');
+
+    my $str2 = '... this is the next body (ref) ...';
+
+    lives_ok {
+        $email->raw_body(\$str2);
+    } '... this will coerce correctly';
+
+    isa_ok($email->raw_body, 'IO::String');
+
+    is($email->as_string, $str2, '... got correct string');
+}
+
+{
+    my $io_str = IO::String->new('... this is my body (IO::String) ...');
+
+    my $email = Email::Mouse->new(raw_body => $io_str);
+    isa_ok($email, 'Email::Mouse');
+
+    isa_ok($email->raw_body, 'IO::String');
+    is($email->raw_body, $io_str, '... and it is the one we expected');
+
+    is($email->as_string, '... this is my body (IO::String) ...', '... got correct string');
+
+    my $io_str2 = IO::String->new('... this is the next body (IO::String) ...');
+
+    lives_ok {
+        $email->raw_body($io_str2);
+    } '... this will coerce correctly';
+
+    isa_ok($email->raw_body, 'IO::String');
+    is($email->raw_body, $io_str2, '... and it is the one we expected');
+
+    is($email->as_string, '... this is the next body (IO::String) ...', '... got correct string');
+}
+
+{
+    my $fh;
+
+    open($fh, '<', $0) || die "Could not open $0";
+
+    my $email = Email::Mouse->new(raw_body => $fh);
+    isa_ok($email, 'Email::Mouse');
+
+    isa_ok($email->raw_body, 'IO::File');
+
+    close($fh);
+}
+
+{
+    my $fh = IO::File->new($0);
+
+    my $email = Email::Mouse->new(raw_body => $fh);
+    isa_ok($email, 'Email::Mouse');
+
+    isa_ok($email->raw_body, 'IO::File');
+    is($email->raw_body, $fh, '... and it is the one we expected');
+}
+
+
+
diff --git a/t/040_type_constraints/failing/010_misc_type_tests.t b/t/040_type_constraints/failing/010_misc_type_tests.t
new file mode 100644 (file)
index 0000000..43fcebc
--- /dev/null
@@ -0,0 +1,67 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 11;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Mouse::Util::TypeConstraints');
+}
+
+# subtype 'aliasing' ...
+
+lives_ok {
+    subtype 'Numb3rs' => as 'Num';
+} '... create bare subtype fine';
+
+my $numb3rs = find_type_constraint('Numb3rs');
+isa_ok($numb3rs, 'Mouse::Meta::TypeConstraint');
+
+# subtype with unions
+
+{
+    package Test::Mouse::Meta::TypeConstraint::Union;
+
+    use overload '""' => sub {'Broken|Test'}, fallback => 1;
+    use Mouse;
+
+    extends 'Mouse::Meta::TypeConstraint';
+}
+
+my $dummy_instance = Test::Mouse::Meta::TypeConstraint::Union->new;
+
+ok $dummy_instance => "Created Instance";
+
+isa_ok $dummy_instance,
+    'Test::Mouse::Meta::TypeConstraint::Union' => 'isa correct type';
+
+is "$dummy_instance", "Broken|Test" =>
+    'Got expected stringification result';
+
+my $subtype1 = subtype 'New1' => as $dummy_instance;
+
+ok $subtype1 => 'made a subtype from our type object';
+
+my $subtype2 = subtype 'New2' => as $subtype1;
+
+ok $subtype2 => 'made a subtype of our subtype';
+
+# assert_valid
+
+{
+    my $type = find_type_constraint('Num');
+
+    my $ok_1 = eval { $type->assert_valid(1); };
+    ok($ok_1, "we can assert_valid that 1 is of type $type");
+
+    my $ok_2 = eval { $type->assert_valid('foo'); };
+    my $error = $@;
+    ok(! $ok_2, "'foo' is not of type $type");
+    like(
+        $error,
+        qr{validation failed for .\Q$type\E.}i,
+        "correct error thrown"
+    );
+}
diff --git a/t/040_type_constraints/failing/011_container_type_constraint.t b/t/040_type_constraints/failing/011_container_type_constraint.t
new file mode 100644 (file)
index 0000000..82f1b99
--- /dev/null
@@ -0,0 +1,73 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 24;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Mouse::Util::TypeConstraints');
+    use_ok('Mouse::Meta::TypeConstraint::Parameterized');
+}
+
+# Array of Ints
+
+my $array_of_ints = Mouse::Meta::TypeConstraint::Parameterized->new(
+    name           => 'ArrayRef[Int]',
+    parent         => find_type_constraint('ArrayRef'),
+    type_parameter => find_type_constraint('Int'),
+);
+isa_ok($array_of_ints, 'Mouse::Meta::TypeConstraint::Parameterized');
+isa_ok($array_of_ints, 'Mouse::Meta::TypeConstraint');
+
+ok($array_of_ints->check([ 1, 2, 3, 4 ]), '... [ 1, 2, 3, 4 ] passed successfully');
+ok(!$array_of_ints->check([qw/foo bar baz/]), '... [qw/foo bar baz/] failed successfully');
+ok(!$array_of_ints->check([ 1, 2, 3, qw/foo bar/]), '... [ 1, 2, 3, qw/foo bar/] failed successfully');
+
+ok(!$array_of_ints->check(1), '... 1 failed successfully');
+ok(!$array_of_ints->check({}), '... {} failed successfully');
+ok(!$array_of_ints->check(sub { () }), '... sub { () } failed successfully');
+
+# Hash of Ints
+
+my $hash_of_ints = Mouse::Meta::TypeConstraint::Parameterized->new(
+    name           => 'HashRef[Int]',
+    parent         => find_type_constraint('HashRef'),
+    type_parameter => find_type_constraint('Int'),
+);
+isa_ok($hash_of_ints, 'Mouse::Meta::TypeConstraint::Parameterized');
+isa_ok($hash_of_ints, 'Mouse::Meta::TypeConstraint');
+
+ok($hash_of_ints->check({ one => 1, two => 2, three => 3 }), '... { one => 1, two => 2, three => 3 } passed successfully');
+ok(!$hash_of_ints->check({ 1 => 'one', 2 => 'two', 3 => 'three' }), '... { 1 => one, 2 => two, 3 => three } failed successfully');
+ok(!$hash_of_ints->check({ 1 => 'one', 2 => 'two', three => 3 }), '... { 1 => one, 2 => two, three => 3 } failed successfully');
+
+ok(!$hash_of_ints->check(1), '... 1 failed successfully');
+ok(!$hash_of_ints->check([]), '... [] failed successfully');
+ok(!$hash_of_ints->check(sub { () }), '... sub { () } failed successfully');
+
+# Array of Array of Ints
+
+my $array_of_array_of_ints = Mouse::Meta::TypeConstraint::Parameterized->new(
+    name           => 'ArrayRef[ArrayRef[Int]]',
+    parent         => find_type_constraint('ArrayRef'),
+    type_parameter => $array_of_ints,
+);
+isa_ok($array_of_array_of_ints, 'Mouse::Meta::TypeConstraint::Parameterized');
+isa_ok($array_of_array_of_ints, 'Mouse::Meta::TypeConstraint');
+
+ok($array_of_array_of_ints->check(
+    [[ 1, 2, 3 ], [ 4, 5, 6 ]]
+), '... [[ 1, 2, 3 ], [ 4, 5, 6 ]] passed successfully');
+ok(!$array_of_array_of_ints->check(
+    [[ 1, 2, 3 ], [ qw/foo bar/ ]]
+), '... [[ 1, 2, 3 ], [ qw/foo bar/ ]] failed successfully');
+
+{
+    my $anon_type = Mouse::Util::TypeConstraints::find_or_parse_type_constraint('ArrayRef[Foo]');
+    isa_ok( $anon_type, 'Mouse::Meta::TypeConstraint::Parameterized' );
+
+    my $param_type = $anon_type->type_parameter;
+    isa_ok( $param_type, 'Mouse::Meta::TypeConstraint::Class' );
+}
diff --git a/t/040_type_constraints/failing/012_container_type_coercion.t b/t/040_type_constraints/failing/012_container_type_coercion.t
new file mode 100644 (file)
index 0000000..e344587
--- /dev/null
@@ -0,0 +1,68 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 10;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Mouse::Util::TypeConstraints');
+    use_ok('Mouse::Meta::TypeConstraint::Parameterized');
+}
+
+my $r = Mouse::Util::TypeConstraints->get_type_constraint_registry;
+
+# Array of Ints
+
+my $array_of_ints = Mouse::Meta::TypeConstraint::Parameterized->new(
+    name           => 'ArrayRef[Int]',
+    parent         => find_type_constraint('ArrayRef'),
+    type_parameter => find_type_constraint('Int'),
+);
+isa_ok($array_of_ints, 'Mouse::Meta::TypeConstraint::Parameterized');
+isa_ok($array_of_ints, 'Mouse::Meta::TypeConstraint');
+
+$r->add_type_constraint($array_of_ints);
+
+is(find_type_constraint('ArrayRef[Int]'), $array_of_ints, '... found the type we just added');
+
+# Hash of Ints
+
+my $hash_of_ints = Mouse::Meta::TypeConstraint::Parameterized->new(
+    name           => 'HashRef[Int]',
+    parent         => find_type_constraint('HashRef'),
+    type_parameter => find_type_constraint('Int'),
+);
+isa_ok($hash_of_ints, 'Mouse::Meta::TypeConstraint::Parameterized');
+isa_ok($hash_of_ints, 'Mouse::Meta::TypeConstraint');
+
+$r->add_type_constraint($hash_of_ints);
+
+is(find_type_constraint('HashRef[Int]'), $hash_of_ints, '... found the type we just added');
+
+## now attempt a coercion
+
+{
+    package Foo;
+    use Mouse;
+    use Mouse::Util::TypeConstraints;
+
+    coerce 'ArrayRef[Int]'
+        => from 'HashRef[Int]'
+            => via { [ values %$_ ] };
+
+    has 'bar' => (
+        is     => 'ro',
+        isa    => 'ArrayRef[Int]',
+        coerce => 1,
+    );
+
+}
+
+my $foo = Foo->new(bar => { one => 1, two => 2, three => 3 });
+isa_ok($foo, 'Foo');
+
+is_deeply([ sort @{$foo->bar} ], [ 1, 2, 3 ], '... our coercion worked!');
+
+
diff --git a/t/040_type_constraints/failing/013_advanced_type_creation.t b/t/040_type_constraints/failing/013_advanced_type_creation.t
new file mode 100644 (file)
index 0000000..7610baa
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 33;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Mouse::Util::TypeConstraints');
+    use_ok('Mouse::Meta::TypeConstraint::Parameterized');
+}
+
+my $r = Mouse::Util::TypeConstraints->get_type_constraint_registry;
+
+## Containers in unions ...
+
+# Array of Ints or Strings
+
+my $array_of_ints_or_strings = Mouse::Util::TypeConstraints::create_parameterized_type_constraint('ArrayRef[Int|Str]');
+isa_ok($array_of_ints_or_strings, 'Mouse::Meta::TypeConstraint::Parameterized');
+
+ok($array_of_ints_or_strings->check([ 1, 'two', 3 ]), '... this passed the type check');
+ok($array_of_ints_or_strings->check([ 1, 2, 3 ]), '... this passed the type check');
+ok($array_of_ints_or_strings->check([ 'one', 'two', 'three' ]), '... this passed the type check');
+
+ok(!$array_of_ints_or_strings->check([ 1, [], 'three' ]), '... this didnt pass the type check');
+
+$r->add_type_constraint($array_of_ints_or_strings);
+
+# Array of Ints or HashRef
+
+my $array_of_ints_or_hash_ref = Mouse::Util::TypeConstraints::create_parameterized_type_constraint('ArrayRef[Int | HashRef]');
+isa_ok($array_of_ints_or_hash_ref, 'Mouse::Meta::TypeConstraint::Parameterized');
+
+ok($array_of_ints_or_hash_ref->check([ 1, {}, 3 ]), '... this passed the type check');
+ok($array_of_ints_or_hash_ref->check([ 1, 2, 3 ]), '... this passed the type check');
+ok($array_of_ints_or_hash_ref->check([ {}, {}, {} ]), '... this passed the type check');
+
+ok(!$array_of_ints_or_hash_ref->check([ {}, [], 3 ]), '... this didnt pass the type check');
+
+$r->add_type_constraint($array_of_ints_or_hash_ref);
+
+# union of Arrays of Str | Int or Arrays of Int | Hash
+
+# we can't build this using the simplistic parser
+# we have, so we have to do it by hand - SL
+
+my $pure_insanity = Mouse::Util::TypeConstraints::create_type_constraint_union('ArrayRef[Int|Str] | ArrayRef[Int | HashRef]');
+isa_ok($pure_insanity, 'Mouse::Meta::TypeConstraint::Union');
+
+ok($pure_insanity->check([ 1, {}, 3 ]), '... this passed the type check');
+ok($pure_insanity->check([ 1, 'Str', 3 ]), '... this passed the type check');
+
+ok(!$pure_insanity->check([ 1, {}, 'foo' ]), '... this didnt pass the type check');
+ok(!$pure_insanity->check([ [], {}, 1 ]), '... this didnt pass the type check');
+
+## Nested Containers ...
+
+# Array of Ints
+
+my $array_of_ints = Mouse::Util::TypeConstraints::create_parameterized_type_constraint('ArrayRef[Int]');
+isa_ok($array_of_ints, 'Mouse::Meta::TypeConstraint::Parameterized');
+isa_ok($array_of_ints, 'Mouse::Meta::TypeConstraint');
+
+ok($array_of_ints->check([ 1, 2, 3, 4 ]), '... [ 1, 2, 3, 4 ] passed successfully');
+ok(!$array_of_ints->check([qw/foo bar baz/]), '... [qw/foo bar baz/] failed successfully');
+ok(!$array_of_ints->check([ 1, 2, 3, qw/foo bar/]), '... [ 1, 2, 3, qw/foo bar/] failed successfully');
+
+ok(!$array_of_ints->check(1), '... 1 failed successfully');
+ok(!$array_of_ints->check({}), '... {} failed successfully');
+ok(!$array_of_ints->check(sub { () }), '... sub { () } failed successfully');
+
+# Array of Array of Ints
+
+my $array_of_array_of_ints = Mouse::Util::TypeConstraints::create_parameterized_type_constraint('ArrayRef[ArrayRef[Int]]');
+isa_ok($array_of_array_of_ints, 'Mouse::Meta::TypeConstraint::Parameterized');
+isa_ok($array_of_array_of_ints, 'Mouse::Meta::TypeConstraint');
+
+ok($array_of_array_of_ints->check(
+    [[ 1, 2, 3 ], [ 4, 5, 6 ]]
+), '... [[ 1, 2, 3 ], [ 4, 5, 6 ]] passed successfully');
+ok(!$array_of_array_of_ints->check(
+    [[ 1, 2, 3 ], [ qw/foo bar/ ]]
+), '... [[ 1, 2, 3 ], [ qw/foo bar/ ]] failed successfully');
+
+# Array of Array of Array of Ints
+
+my $array_of_array_of_array_of_ints = Mouse::Util::TypeConstraints::create_parameterized_type_constraint('ArrayRef[ArrayRef[ArrayRef[Int]]]');
+isa_ok($array_of_array_of_array_of_ints, 'Mouse::Meta::TypeConstraint::Parameterized');
+isa_ok($array_of_array_of_array_of_ints, 'Mouse::Meta::TypeConstraint');
+
+ok($array_of_array_of_array_of_ints->check(
+    [[[ 1, 2, 3 ], [ 4, 5, 6 ]], [[ 7, 8, 9 ]]]
+), '... [[[ 1, 2, 3 ], [ 4, 5, 6 ]], [[ 7, 8, 9 ]]] passed successfully');
+ok(!$array_of_array_of_array_of_ints->check(
+    [[[ 1, 2, 3 ]], [[ qw/foo bar/ ]]]
+), '... [[[ 1, 2, 3 ]], [[ qw/foo bar/ ]]] failed successfully');
+
+
+
diff --git a/t/040_type_constraints/failing/014_type_notation_parser.t b/t/040_type_constraints/failing/014_type_notation_parser.t
new file mode 100644 (file)
index 0000000..b2821c1
--- /dev/null
@@ -0,0 +1,105 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 41;
+
+BEGIN {
+    use_ok("Mouse::Util::TypeConstraints");
+}
+
+=pod
+
+This is a good candidate for LectroTest
+Volunteers welcome :)
+
+=cut
+
+## check the containers
+
+ok(Mouse::Util::TypeConstraints::_detect_parameterized_type_constraint($_),
+   '... this correctly detected a container (' . $_ . ')')
+    for (
+    'ArrayRef[Foo]',
+    'ArrayRef[Foo | Int]',
+    'ArrayRef[ArrayRef[Int]]',
+    'ArrayRef[ArrayRef[Int | Foo]]',
+    'ArrayRef[ArrayRef[Int|Str]]',
+);
+
+ok(!Mouse::Util::TypeConstraints::_detect_parameterized_type_constraint($_),
+   '... this correctly detected a non-container (' . $_ . ')')
+    for (
+    'ArrayRef[]',
+    'ArrayRef[Foo]Bar',
+);
+
+{
+    my %split_tests = (
+        'ArrayRef[Foo]'                 => [ 'ArrayRef', 'Foo' ],
+        'ArrayRef[Foo | Int]'           => [ 'ArrayRef', 'Foo | Int' ],
+        'ArrayRef[Foo|Int]'             => [ 'ArrayRef', 'Foo|Int' ],
+        # these will get processed with recusion,
+        # so we only need to detect it once
+        'ArrayRef[ArrayRef[Int]]'       => [ 'ArrayRef', 'ArrayRef[Int]' ],
+        'ArrayRef[ArrayRef[Int | Foo]]' => [ 'ArrayRef', 'ArrayRef[Int | Foo]' ],
+        'ArrayRef[ArrayRef[Int|Str]]'   => [ 'ArrayRef', 'ArrayRef[Int|Str]' ],
+    );
+
+    is_deeply(
+        [ Mouse::Util::TypeConstraints::_parse_parameterized_type_constraint($_) ],
+        $split_tests{$_},
+        '... this correctly split the container (' . $_ . ')'
+    ) for keys %split_tests;
+}
+
+## now for the unions
+
+ok(Mouse::Util::TypeConstraints::_detect_type_constraint_union($_),
+   '... this correctly detected union (' . $_ . ')')
+    for (
+    'Int | Str',
+    'Int|Str',
+    'ArrayRef[Foo] | Int',
+    'ArrayRef[Foo]|Int',
+    'Int | ArrayRef[Foo]',
+    'Int|ArrayRef[Foo]',
+    'ArrayRef[Foo | Int] | Str',
+    'ArrayRef[Foo|Int]|Str',
+    'Str | ArrayRef[Foo | Int]',
+    'Str|ArrayRef[Foo|Int]',
+    'Some|Silly|Name|With|Pipes | Int',
+    'Some|Silly|Name|With|Pipes|Int',
+);
+
+ok(!Mouse::Util::TypeConstraints::_detect_type_constraint_union($_),
+   '... this correctly detected a non-union (' . $_ . ')')
+    for (
+    'Int',
+    'ArrayRef[Foo | Int]',
+    'ArrayRef[Foo|Int]',
+);
+
+{
+    my %split_tests = (
+        'Int | Str'                        => [ 'Int', 'Str' ],
+        'Int|Str'                          => [ 'Int', 'Str' ],
+        'ArrayRef[Foo] | Int'              => [ 'ArrayRef[Foo]', 'Int' ],
+        'ArrayRef[Foo]|Int'                => [ 'ArrayRef[Foo]', 'Int' ],
+        'Int | ArrayRef[Foo]'              => [ 'Int', 'ArrayRef[Foo]' ],
+        'Int|ArrayRef[Foo]'                => [ 'Int', 'ArrayRef[Foo]' ],
+        'ArrayRef[Foo | Int] | Str'        => [ 'ArrayRef[Foo | Int]', 'Str' ],
+        'ArrayRef[Foo|Int]|Str'            => [ 'ArrayRef[Foo|Int]', 'Str' ],
+        'Str | ArrayRef[Foo | Int]'        => [ 'Str', 'ArrayRef[Foo | Int]' ],
+        'Str|ArrayRef[Foo|Int]'            => [ 'Str', 'ArrayRef[Foo|Int]' ],
+        'Some|Silly|Name|With|Pipes | Int' => [ 'Some', 'Silly', 'Name', 'With', 'Pipes', 'Int' ],
+        'Some|Silly|Name|With|Pipes|Int'   => [ 'Some', 'Silly', 'Name', 'With', 'Pipes', 'Int' ],
+    );
+
+    is_deeply(
+        [ Mouse::Util::TypeConstraints::_parse_type_constraint_union($_) ],
+        $split_tests{$_},
+        '... this correctly split the union (' . $_ . ')'
+    ) for keys %split_tests;
+}
diff --git a/t/040_type_constraints/failing/015_enum.t b/t/040_type_constraints/failing/015_enum.t
new file mode 100644 (file)
index 0000000..940a341
--- /dev/null
@@ -0,0 +1,68 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+
+use Scalar::Util ();
+
+use Mouse::Util::TypeConstraints;
+
+enum Letter => 'a'..'z', 'A'..'Z';
+enum Language => 'Perl 5', 'Perl 6', 'PASM', 'PIR'; # any others? ;)
+enum Metacharacter => ['*', '+', '?', '.', '|', '(', ')', '[', ']', '\\'];
+
+my @valid_letters = ('a'..'z', 'A'..'Z');
+
+my @invalid_letters = qw/ab abc abcd/;
+push @invalid_letters, qw/0 4 9 ~ @ $ %/;
+push @invalid_letters, qw/l33t st3v4n 3num/;
+
+my @valid_languages = ('Perl 5', 'Perl 6', 'PASM', 'PIR');
+my @invalid_languages = ('perl 5', 'Python', 'Ruby', 'Perl 666', 'PASM++');
+# note that "perl 5" is invalid because case now matters
+
+my @valid_metacharacters = (qw/* + ? . | ( ) [ ] /, '\\');
+my @invalid_metacharacters = qw/< > & % $ @ ! ~ `/;
+push @invalid_metacharacters, qw/.* fish(sticks)? atreides/;
+push @invalid_metacharacters, '^1?$|^(11+?)\1+$';
+
+plan tests => @valid_letters        + @invalid_letters
+            + @valid_languages      + @invalid_languages
+            + @valid_metacharacters + @invalid_metacharacters
+            + @valid_languages      + 10;
+
+Mouse::Util::TypeConstraints->export_type_constraints_as_functions();
+
+ok(Letter($_), "'$_' is a letter") for @valid_letters;
+ok(!Letter($_), "'$_' is not a letter") for @invalid_letters;
+
+ok(Language($_), "'$_' is a language") for @valid_languages;
+ok(!Language($_), "'$_' is not a language") for @invalid_languages;
+
+ok(Metacharacter($_), "'$_' is a metacharacter") for @valid_metacharacters;
+ok(!Metacharacter($_), "'$_' is not a metacharacter")
+    for @invalid_metacharacters;
+
+# check anon enums
+
+my $anon_enum = enum \@valid_languages;
+isa_ok($anon_enum, 'Mouse::Meta::TypeConstraint');
+
+is($anon_enum->name, '__ANON__', '... got the right name');
+is($anon_enum->parent->name, 'Str', '... got the right parent name');
+
+ok($anon_enum->check($_), "'$_' is a language") for @valid_languages;
+
+
+ok( !$anon_enum->equals( enum [qw(foo bar)] ), "doesn't equal a diff enum" );
+ok( $anon_enum->equals( $anon_enum ), "equals itself" );
+ok( $anon_enum->equals( enum \@valid_languages ), "equals duplicate" );
+
+ok( !$anon_enum->is_subtype_of('Object'), 'enum not a subtype of Object');
+ok( !$anon_enum->is_a_type_of('Object'), 'enum not type of Object');
+
+ok( !$anon_enum->is_subtype_of('ThisTypeDoesNotExist'), 'enum not a subtype of nonexistant type');
+ok( !$anon_enum->is_a_type_of('ThisTypeDoesNotExist'), 'enum not type of nonexistant type');
+
diff --git a/t/040_type_constraints/failing/016_subtyping_parameterized_types.t b/t/040_type_constraints/failing/016_subtyping_parameterized_types.t
new file mode 100644 (file)
index 0000000..2fa5f60
--- /dev/null
@@ -0,0 +1,121 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 39;
+use Test::Exception;
+
+BEGIN {
+    use_ok("Mouse::Util::TypeConstraints");
+}
+
+lives_ok {
+    subtype 'MySpecialHash' => as 'HashRef[Int]';
+} '... created the subtype special okay';
+
+{
+    my $t = find_type_constraint('MySpecialHash');
+    isa_ok($t, 'Mouse::Meta::TypeConstraint');
+
+    is($t->name, 'MySpecialHash', '... name is correct');
+
+    my $p = $t->parent;
+    isa_ok($p, 'Mouse::Meta::TypeConstraint::Parameterized');
+    isa_ok($p, 'Mouse::Meta::TypeConstraint');
+
+    is($p->name, 'HashRef[Int]', '... parent name is correct');
+
+    ok($t->check({ one => 1, two => 2 }), '... validated {one=>1, two=>2} correctly');
+    ok(!$t->check({ one => "ONE", two => "TWO" }), '... validated it correctly');
+
+    ok( $t->equals($t), "equals to self" );
+    ok( !$t->equals( $t->parent ), "not equal to parent" );
+    ok( $t->parent->equals( $t->parent ), "parent equals to self" );
+
+    ok( !$t->is_a_type_of("ThisTypeDoesNotExist"), "not a non existant type" );
+    ok( !$t->is_subtype_of("ThisTypeDoesNotExist"), "not a subtype of a non existant type" );
+}
+
+lives_ok {
+    subtype 'MySpecialHashExtended'
+        => as 'HashRef[Int]'
+        => where {
+            # all values are less then 10
+            (scalar grep { $_ < 10 } values %{$_}) ? 1 : undef
+        };
+} '... created the subtype special okay';
+
+{
+    my $t = find_type_constraint('MySpecialHashExtended');
+    isa_ok($t, 'Mouse::Meta::TypeConstraint');
+
+    is($t->name, 'MySpecialHashExtended', '... name is correct');
+
+    my $p = $t->parent;
+    isa_ok($p, 'Mouse::Meta::TypeConstraint::Parameterized');
+    isa_ok($p, 'Mouse::Meta::TypeConstraint');
+
+    is($p->name, 'HashRef[Int]', '... parent name is correct');
+
+    ok($t->check({ one => 1, two => 2 }), '... validated it correctly');
+    ok(!$t->check({ zero => 10, one => 11, two => 12 }), '... validated { zero => 10, one => 11, two => 12 } correctly');
+    ok(!$t->check({ one => "ONE", two => "TWO" }), '... validated it correctly');
+}
+
+lives_ok {
+    subtype 'MyNonSpecialHash'
+        => as "HashRef"
+        => where { keys %$_ == 3 };
+};
+
+{
+    my $t = find_type_constraint('MyNonSpecialHash');
+
+    isa_ok($t, 'Mouse::Meta::TypeConstraint');
+    isa_ok($t, 'Mouse::Meta::TypeConstraint::Parameterizable');
+
+    ok( $t->check({ one => 1, two => "foo", three => [] }), "validated" );
+    ok( !$t->check({ one => 1 }), "failed" );
+}
+
+{
+    my $t = Mouse::Util::TypeConstraints::find_or_parse_type_constraint('MyNonSpecialHash[Int]');
+
+    isa_ok($t, 'Mouse::Meta::TypeConstraint');
+
+    ok( $t->check({ one => 1, two => 2, three => 3 }), "validated" );
+    ok( !$t->check({ one => 1, two => "foo", three => [] }), "failed" );
+    ok( !$t->check({ one => 1 }), "failed" );
+}
+
+{
+    ## Because to throw errors in M:M:Parameterizable needs Mouse loaded in
+    ## order to throw errors.  In theory the use Mouse belongs to that class
+    ## but when I put it there causes all sorts or trouble.  In theory this is
+    ## never a real problem since you are likely to use Mouse somewhere when you
+    ## are creating type constraints.
+    use Mouse ();
+
+    my $MyArrayRefInt =  subtype 'MyArrayRefInt',
+        as 'ArrayRef[Int]';
+
+    my $BiggerInt = subtype 'BiggerInt',
+        as 'Int',
+        where {$_>10};
+
+    my $SubOfMyArrayRef = subtype 'SubOfMyArrayRef',
+        as 'MyArrayRefInt[BiggerInt]';
+
+    ok $MyArrayRefInt->check([1,2,3]), '[1,2,3] is okay';
+    ok ! $MyArrayRefInt->check(["a","b"]), '["a","b"] is not';
+    ok $BiggerInt->check(100), '100 is  big enough';
+    ok ! $BiggerInt->check(5), '5 is  big enough';
+    ok $SubOfMyArrayRef->check([15,20,25]), '[15,20,25] is a bunch of big ints';
+    ok ! $SubOfMyArrayRef->check([15,5,25]), '[15,5,25] is NOT a bunch of big ints';
+
+    throws_ok sub {
+        my $SubOfMyArrayRef = subtype 'SubSubOfMyArrayRef',
+            as 'SubOfMyArrayRef[Str]';
+    }, qr/Str is not a subtype of BiggerInt/, 'Failed to parameterize with a bad type parameter';
+}
diff --git a/t/040_type_constraints/failing/017_subtyping_union_types.t b/t/040_type_constraints/failing/017_subtyping_union_types.t
new file mode 100644 (file)
index 0000000..85768ac
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 21;
+use Test::Exception;
+
+BEGIN {
+    use_ok("Mouse::Util::TypeConstraints");
+}
+
+lives_ok {
+    subtype 'MyCollections' => as 'ArrayRef | HashRef';
+} '... created the subtype special okay';
+
+{
+    my $t = find_type_constraint('MyCollections');
+    isa_ok($t, 'Mouse::Meta::TypeConstraint');
+
+    is($t->name, 'MyCollections', '... name is correct');
+
+    my $p = $t->parent;
+    isa_ok($p, 'Mouse::Meta::TypeConstraint::Union');
+    isa_ok($p, 'Mouse::Meta::TypeConstraint');
+
+    is($p->name, 'ArrayRef|HashRef', '... parent name is correct');
+
+    ok($t->check([]), '... validated it correctly');
+    ok($t->check({}), '... validated it correctly');
+    ok(!$t->check(1), '... validated it correctly');
+}
+
+lives_ok {
+    subtype 'MyCollectionsExtended'
+        => as 'ArrayRef|HashRef'
+        => where {
+            if (ref($_) eq 'ARRAY') {
+                return if scalar(@$_) < 2;
+            }
+            elsif (ref($_) eq 'HASH') {
+                return if scalar(keys(%$_)) < 2;
+            }
+            1;
+        };
+} '... created the subtype special okay';
+
+{
+    my $t = find_type_constraint('MyCollectionsExtended');
+    isa_ok($t, 'Mouse::Meta::TypeConstraint');
+
+    is($t->name, 'MyCollectionsExtended', '... name is correct');
+
+    my $p = $t->parent;
+    isa_ok($p, 'Mouse::Meta::TypeConstraint::Union');
+    isa_ok($p, 'Mouse::Meta::TypeConstraint');
+
+    is($p->name, 'ArrayRef|HashRef', '... parent name is correct');
+
+    ok(!$t->check([]), '... validated it correctly');
+    ok($t->check([1, 2]), '... validated it correctly');
+
+    ok(!$t->check({}), '... validated it correctly');
+    ok($t->check({ one => 1, two => 2 }), '... validated it correctly');
+
+    ok(!$t->check(1), '... validated it correctly');
+}
+
+
diff --git a/t/040_type_constraints/failing/018_custom_parameterized_types.t b/t/040_type_constraints/failing/018_custom_parameterized_types.t
new file mode 100644 (file)
index 0000000..c00bda9
--- /dev/null
@@ -0,0 +1,86 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 28;
+use Test::Exception;
+
+BEGIN {
+    use_ok("Mouse::Util::TypeConstraints");
+    use_ok('Mouse::Meta::TypeConstraint::Parameterized');
+}
+
+lives_ok {
+    subtype 'AlphaKeyHash' => as 'HashRef'
+        => where {
+            # no keys match non-alpha
+            (grep { /[^a-zA-Z]/ } keys %$_) == 0
+        };
+} '... created the subtype special okay';
+
+lives_ok {
+    subtype 'Trihash' => as 'AlphaKeyHash'
+        => where {
+            keys(%$_) == 3
+        };
+} '... created the subtype special okay';
+
+lives_ok {
+    subtype 'Noncon' => as 'Item';
+} '... created the subtype special okay';
+
+{
+    my $t = find_type_constraint('AlphaKeyHash');
+    isa_ok($t, 'Mouse::Meta::TypeConstraint');
+
+    is($t->name, 'AlphaKeyHash', '... name is correct');
+
+    my $p = $t->parent;
+    isa_ok($p, 'Mouse::Meta::TypeConstraint');
+
+    is($p->name, 'HashRef', '... parent name is correct');
+
+    ok($t->check({ one => 1, two => 2 }), '... validated it correctly');
+    ok(!$t->check({ one1 => 1, two2 => 2 }), '... validated it correctly');
+
+    ok( $t->equals($t), "equals to self" );
+    ok( !$t->equals($t->parent), "not equal to parent" );
+}
+
+my $hoi = Mouse::Util::TypeConstraints::find_or_parse_type_constraint('AlphaKeyHash[Int]');
+
+ok($hoi->check({ one => 1, two => 2 }), '... validated it correctly');
+ok(!$hoi->check({ one1 => 1, two2 => 2 }), '... validated it correctly');
+ok(!$hoi->check({ one => 'uno', two => 'dos' }), '... validated it correctly');
+ok(!$hoi->check({ one1 => 'un', two2 => 'deux' }), '... validated it correctly');
+
+ok( $hoi->equals($hoi), "equals to self" );
+ok( !$hoi->equals($hoi->parent), "equals to self" );
+ok( !$hoi->equals(find_type_constraint('AlphaKeyHash')), "not equal to unparametrized self" );
+ok( $hoi->equals( Mouse::Meta::TypeConstraint::Parameterized->new( name => "Blah", parent => find_type_constraint("AlphaKeyHash"), type_parameter => find_type_constraint("Int") ) ), "equal to clone" );
+ok( !$hoi->equals( Mouse::Meta::TypeConstraint::Parameterized->new( name => "Oink", parent => find_type_constraint("AlphaKeyHash"), type_parameter => find_type_constraint("Str") ) ), "not equal to different parameter" );
+
+my $th = Mouse::Util::TypeConstraints::find_or_parse_type_constraint('Trihash[Bool]');
+
+ok(!$th->check({ one => 1, two => 1 }), '... validated it correctly');
+ok($th->check({ one => 1, two => 0, three => 1 }), '... validated it correctly');
+ok(!$th->check({ one => 1, two => 2, three => 1 }), '... validated it correctly');
+ok(!$th->check({foo1 => 1, bar2 => 0, baz3 => 1}), '... validated it correctly');
+
+dies_ok {
+    Mouse::Meta::TypeConstraint::Parameterized->new(
+        name           => 'Str[Int]',
+        parent         => find_type_constraint('Str'),
+        type_parameter => find_type_constraint('Int'),
+    );
+} 'non-containers cannot be parameterized';
+
+dies_ok {
+    Mouse::Meta::TypeConstraint::Parameterized->new(
+        name           => 'Noncon[Int]',
+        parent         => find_type_constraint('Noncon'),
+        type_parameter => find_type_constraint('Int'),
+    );
+} 'non-containers cannot be parameterized';
+
diff --git a/t/040_type_constraints/failing/019_coerced_parameterized_types.t b/t/040_type_constraints/failing/019_coerced_parameterized_types.t
new file mode 100644 (file)
index 0000000..5b57ad3
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 11;
+use Test::Exception;
+
+BEGIN {
+    use_ok("Mouse::Util::TypeConstraints");
+    use_ok('Mouse::Meta::TypeConstraint::Parameterized');
+}
+
+BEGIN {
+    package MyList;
+    sub new {
+        my $class = shift;
+        bless { items => \@_ }, $class;
+    }
+
+    sub items {
+        my $self = shift;
+        return @{ $self->{items} };
+    }
+}
+
+subtype 'MyList' => as 'Object' => where { $_->isa('MyList') };
+
+lives_ok {
+    coerce 'ArrayRef'
+        => from 'MyList'
+            => via { [ $_->items ] }
+} '... created the coercion okay';
+
+my $mylist = Mouse::Util::TypeConstraints::find_or_parse_type_constraint('MyList[Int]');
+
+ok($mylist->check(MyList->new(10, 20, 30)), '... validated it correctly (pass)');
+ok(!$mylist->check(MyList->new(10, "two")), '... validated it correctly (fail)');
+ok(!$mylist->check([10]), '... validated it correctly (fail)');
+
+subtype 'EvenList' => as 'MyList' => where { $_->items % 2 == 0 };
+
+# XXX: get this to work *without* the declaration. I suspect it'll be a new
+# method in Mouse::Meta::TypeCoercion that will look at the parents of the
+# coerced type as well. but will that be too "action at a distance"-ey?
+lives_ok {
+    coerce 'ArrayRef'
+        => from 'EvenList'
+            => via { [ $_->items ] }
+} '... created the coercion okay';
+
+my $evenlist = Mouse::Util::TypeConstraints::find_or_parse_type_constraint('EvenList[Int]');
+
+ok(!$evenlist->check(MyList->new(10, 20, 30)), '... validated it correctly (fail)');
+ok($evenlist->check(MyList->new(10, 20, 30, 40)), '... validated it correctly (pass)');
+ok(!$evenlist->check(MyList->new(10, "two")), '... validated it correctly (fail)');
+ok(!$evenlist->check([10, 20]), '... validated it correctly (fail)');
+
diff --git a/t/040_type_constraints/failing/020_class_type_constraint.t b/t/040_type_constraints/failing/020_class_type_constraint.t
new file mode 100644 (file)
index 0000000..05a9320
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 20;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Mouse::Util::TypeConstraints');
+}
+
+{
+    package Gorch;
+    use Mouse;
+
+    package Bar;
+    use Mouse;
+
+    package Foo;
+    use Mouse;
+
+    extends qw(Bar Gorch);
+
+}
+
+lives_ok { class_type 'Beep' } 'class_type keywork works';
+lives_ok { class_type('Boop', message { "${_} is not a Boop" }) }
+  'class_type keywork works with message';
+
+my $type = find_type_constraint("Foo");
+
+is( $type->class, "Foo", "class attribute" );
+
+ok( $type->is_subtype_of("Gorch"), "subtype of gorch" );
+
+ok( $type->is_subtype_of("Bar"), "subtype of bar" );
+
+ok( $type->is_subtype_of("Object"), "subtype of Object" );
+
+ok( !$type->is_subtype_of("ThisTypeDoesNotExist"), "not subtype of undefined type" );
+ok( !$type->is_a_type_of("ThisTypeDoesNotExist"), "not type of undefined type" );
+
+ok( find_type_constraint("Bar")->check(Foo->new), "Foo passes Bar" );
+ok( find_type_constraint("Bar")->check(Bar->new), "Bar passes Bar" );
+ok( !find_type_constraint("Gorch")->check(Bar->new), "but Bar doesn't pass Gorch");
+
+ok( find_type_constraint("Beep")->check( bless {} => 'Beep' ), "Beep passes Beep" );
+my $boop = find_type_constraint("Boop");
+ok( $boop->has_message, 'Boop has a message');
+my $error = $boop->get_message(Foo->new);
+like( $error, qr/is not a Boop/,  'boop gives correct error message');
+
+
+ok( $type->equals($type), "equals self" );
+ok( $type->equals(Mouse::Meta::TypeConstraint::Class->new( name => "__ANON__", class => "Foo" )), "equals anon constraint of same value" );
+ok( $type->equals(Mouse::Meta::TypeConstraint::Class->new( name => "Oink", class => "Foo" )), "equals differently named constraint of same value" );
+ok( !$type->equals(Mouse::Meta::TypeConstraint::Class->new( name => "__ANON__", class => "Bar" )), "doesn't equal other anon constraint" );
+ok( $type->is_subtype_of(Mouse::Meta::TypeConstraint::Class->new( name => "__ANON__", class => "Bar" )), "subtype of other anon constraint" );
+
diff --git a/t/040_type_constraints/failing/021_maybe_type_constraint.t b/t/040_type_constraints/failing/021_maybe_type_constraint.t
new file mode 100644 (file)
index 0000000..85fcff9
--- /dev/null
@@ -0,0 +1,133 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 36;
+use Test::Exception;
+
+use Mouse::Util::TypeConstraints;
+
+my $type = Mouse::Util::TypeConstraints::find_or_parse_type_constraint('Maybe[Int]');
+isa_ok($type, 'Mouse::Meta::TypeConstraint');
+isa_ok($type, 'Mouse::Meta::TypeConstraint::Parameterized');
+
+ok( $type->equals($type), "equals self" );
+ok( !$type->equals($type->parent), "not equal to parent" );
+ok( !$type->equals(find_type_constraint("Maybe")), "not equal to Maybe" );
+ok( $type->parent->equals(find_type_constraint("Maybe")), "parent is Maybe" );
+ok( $type->equals( Mouse::Meta::TypeConstraint::Parameterized->new( name => "__ANON__", parent => find_type_constraint("Maybe"), type_parameter => find_type_constraint("Int") ) ), "equal to clone" );
+ok( !$type->equals( Mouse::Meta::TypeConstraint::Parameterized->new( name => "__ANON__", parent => find_type_constraint("Maybe"), type_parameter => find_type_constraint("Str") ) ), "not equal to clone with diff param" );
+ok( !$type->equals( Mouse::Util::TypeConstraints::find_or_parse_type_constraint('Maybe[Str]') ), "not equal to declarative version of diff param" );
+
+ok($type->check(10), '... checked type correctly (pass)');
+ok($type->check(undef), '... checked type correctly (pass)');
+ok(!$type->check('Hello World'), '... checked type correctly (fail)');
+ok(!$type->check([]), '... checked type correctly (fail)');
+
+{
+    package Bar;
+    use Mouse;
+
+    package Foo;
+    use Mouse;
+    use Mouse::Util::TypeConstraints;
+
+    has 'arr' => (is => 'rw', isa => 'Maybe[ArrayRef]', required => 1);
+    has 'bar' => (is => 'rw', isa => class_type('Bar'));
+    has 'maybe_bar' => (is => 'rw', isa => maybe_type(class_type('Bar')));
+}
+
+lives_ok {
+    Foo->new(arr => [], bar => Bar->new);
+} '... Bar->new isa Bar';
+
+dies_ok {
+    Foo->new(arr => [], bar => undef);
+} '... undef isnta Bar';
+
+lives_ok {
+    Foo->new(arr => [], maybe_bar => Bar->new);
+} '... Bar->new isa maybe(Bar)';
+
+lives_ok {
+    Foo->new(arr => [], maybe_bar => undef);
+} '... undef isa maybe(Bar)';
+
+dies_ok {
+    Foo->new(arr => [], maybe_bar => 1);
+} '... 1 isnta maybe(Bar)';
+
+lives_ok {
+    Foo->new(arr => []);
+} '... it worked!';
+
+lives_ok {
+    Foo->new(arr => undef);
+} '... it worked!';
+
+dies_ok {
+    Foo->new(arr => 100);
+} '... failed the type check';
+
+dies_ok {
+    Foo->new(arr => 'hello world');
+} '... failed the type check';
+
+
+{
+    package Test::MouseX::Types::Maybe;
+    use Mouse;
+
+    has 'Maybe_Int' => (is=>'rw', isa=>'Maybe[Int]');
+    has 'Maybe_ArrayRef' => (is=>'rw', isa=>'Maybe[ArrayRef]');
+    has 'Maybe_HashRef' => (is=>'rw', isa=>'Maybe[HashRef]');
+    has 'Maybe_ArrayRefInt' => (is=>'rw', isa=>'Maybe[ArrayRef[Int]]');
+    has 'Maybe_HashRefInt' => (is=>'rw', isa=>'Maybe[HashRef[Int]]');
+}
+
+ok my $obj = Test::MouseX::Types::Maybe->new
+ => 'Create good test object';
+
+##  Maybe[Int]
+
+ok my $Maybe_Int  = Mouse::Util::TypeConstraints::find_or_parse_type_constraint('Maybe[Int]')
+ => 'made TC Maybe[Int]';
+
+ok $Maybe_Int->check(1)
+ => 'passed (1)';
+
+ok $obj->Maybe_Int(1)
+ => 'assigned (1)';
+
+ok $Maybe_Int->check()
+ => 'passed ()';
+
+ok $obj->Maybe_Int()
+ => 'assigned ()';
+
+ok $Maybe_Int->check(0)
+ => 'passed (0)';
+
+ok defined $obj->Maybe_Int(0)
+ => 'assigned (0)';
+
+ok $Maybe_Int->check(undef)
+ => 'passed (undef)';
+
+ok sub {$obj->Maybe_Int(undef); 1}->()
+ => 'assigned (undef)';
+
+ok !$Maybe_Int->check("")
+ => 'failed ("")';
+
+throws_ok sub { $obj->Maybe_Int("") },
+ qr/Attribute \(Maybe_Int\) does not pass the type constraint/
+ => 'failed assigned ("")';
+
+ok !$Maybe_Int->check("a")
+ => 'failed ("a")';
+
+throws_ok sub { $obj->Maybe_Int("a") },
+ qr/Attribute \(Maybe_Int\) does not pass the type constraint/
+ => 'failed assigned ("a")';
diff --git a/t/040_type_constraints/failing/022_custom_type_errors.t b/t/040_type_constraints/failing/022_custom_type_errors.t
new file mode 100644 (file)
index 0000000..38757e7
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 9;
+use Test::Exception;
+
+{
+    package Animal;
+    use Mouse;
+    use Mouse::Util::TypeConstraints;
+
+    subtype 'Natural' => as 'Int' => where { $_ > 0 } =>
+        message {"This number ($_) is not a positive integer!"};
+
+    subtype 'NaturalLessThanTen' => as 'Natural' => where { $_ < 10 } =>
+        message {"This number ($_) is not less than ten!"};
+
+    has leg_count => (
+        is      => 'rw',
+        isa     => 'NaturalLessThanTen',
+        lazy    => 1,
+        default => 0,
+    );
+}
+
+lives_ok { my $goat = Animal->new( leg_count => 4 ) }
+'... no errors thrown, value is good';
+lives_ok { my $spider = Animal->new( leg_count => 8 ) }
+'... no errors thrown, value is good';
+
+throws_ok { my $fern = Animal->new( leg_count => 0 ) }
+qr/This number \(0\) is not less than ten!/,
+    'gave custom supertype error message on new';
+
+throws_ok { my $centipede = Animal->new( leg_count => 30 ) }
+qr/This number \(30\) is not less than ten!/,
+    'gave custom subtype error message on new';
+
+my $chimera;
+lives_ok { $chimera = Animal->new( leg_count => 4 ) }
+'... no errors thrown, value is good';
+
+throws_ok { $chimera->leg_count(0) }
+qr/This number \(0\) is not less than ten!/,
+    'gave custom supertype error message on set to 0';
+
+throws_ok { $chimera->leg_count(16) }
+qr/This number \(16\) is not less than ten!/,
+    'gave custom subtype error message on set to 16';
+
+my $gimp = eval { Animal->new() };
+is( $@, '', '... no errors thrown, value is good' );
+
+throws_ok { $gimp->leg_count }
+qr/This number \(0\) is not less than ten!/,
+    'gave custom supertype error message on lazy set to 0';
+
diff --git a/t/040_type_constraints/failing/023_types_and_undef.t b/t/040_type_constraints/failing/023_types_and_undef.t
new file mode 100644 (file)
index 0000000..e504eb3
--- /dev/null
@@ -0,0 +1,113 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 54;
+use Test::Exception;
+
+
+
+{
+    package Foo;
+    use Mouse;
+    use Mouse::Util::TypeConstraints;
+
+    use Scalar::Util ();
+
+    type Number
+        => where { defined($_) && !ref($_) && Scalar::Util::looks_like_number($_) };
+
+    type String
+        => where { defined($_) && !ref($_) && !Scalar::Util::looks_like_number($_) };
+
+    has vUndef   => ( is => 'rw', isa => 'Undef'   );
+    has vDefined => ( is => 'rw', isa => 'Defined' );
+    has vInt     => ( is => 'rw', isa => 'Int'     );
+    has vNumber  => ( is => 'rw', isa => 'Number'  );
+    has vStr     => ( is => 'rw', isa => 'Str'     );
+    has vString  => ( is => 'rw', isa => 'String'  );
+
+    has v_lazy_Undef   => ( is => 'rw', lazy => 1,  default => sub { undef }, isa => 'Undef'   );
+    has v_lazy_Defined => ( is => 'rw', lazy => 1,  default => sub { undef }, isa => 'Defined' );
+    has v_lazy_Int     => ( is => 'rw', lazy => 1,  default => sub { undef }, isa => 'Int'     );
+    has v_lazy_Number  => ( is => 'rw', lazy => 1,  default => sub { undef }, isa => 'Number'  );
+    has v_lazy_Str     => ( is => 'rw', lazy => 1,  default => sub { undef }, isa => 'Str'     );
+    has v_lazy_String  => ( is => 'rw', lazy => 1,  default => sub { undef }, isa => 'String'  );
+}
+
+#    EXPORT TYPE CONSTRAINTS
+#
+Mouse::Util::TypeConstraints->export_type_constraints_as_functions;
+
+ok( Undef(undef),   '... undef is a Undef');
+ok(!Defined(undef), '... undef is NOT a Defined');
+ok(!Int(undef),     '... undef is NOT a Int');
+ok(!Number(undef),  '... undef is NOT a Number');
+ok(!Str(undef),     '... undef is NOT a Str');
+ok(!String(undef),  '... undef is NOT a String');
+
+ok(!Undef(5),  '... 5 is a NOT a Undef');
+ok(Defined(5), '... 5 is a Defined');
+ok(Int(5),     '... 5 is a Int');
+ok(Number(5),  '... 5 is a Number');
+ok(Str(5),     '... 5 is a Str');
+ok(!String(5), '... 5 is NOT a String');
+
+ok(!Undef(0.5),  '... 0.5 is a NOT a Undef');
+ok(Defined(0.5), '... 0.5 is a Defined');
+ok(!Int(0.5),    '... 0.5 is NOT a Int');
+ok(Number(0.5),  '... 0.5 is a Number');
+ok(Str(0.5),     '... 0.5 is a Str');
+ok(!String(0.5), '... 0.5 is NOT a String');
+
+ok(!Undef('Foo'),  '... "Foo" is NOT a Undef');
+ok(Defined('Foo'), '... "Foo" is a Defined');
+ok(!Int('Foo'),    '... "Foo" is NOT a Int');
+ok(!Number('Foo'), '... "Foo" is NOT a Number');
+ok(Str('Foo'),     '... "Foo" is a Str');
+ok(String('Foo'),  '... "Foo" is a String');
+
+
+my $foo = Foo->new;
+
+lives_ok { $foo->vUndef(undef) } '... undef is a Foo->Undef';
+dies_ok { $foo->vDefined(undef) } '... undef is NOT a Foo->Defined';
+dies_ok { $foo->vInt(undef) } '... undef is NOT a Foo->Int';
+dies_ok { $foo->vNumber(undef) } '... undef is NOT a Foo->Number';
+dies_ok { $foo->vStr(undef) } '... undef is NOT a Foo->Str';
+dies_ok { $foo->vString(undef) } '... undef is NOT a Foo->String';
+
+dies_ok { $foo->vUndef(5) } '... 5 is NOT a Foo->Undef';
+lives_ok { $foo->vDefined(5) } '... 5 is a Foo->Defined';
+lives_ok { $foo->vInt(5) } '... 5 is a Foo->Int';
+lives_ok { $foo->vNumber(5) } '... 5 is a Foo->Number';
+lives_ok { $foo->vStr(5) } '... 5 is a Foo->Str';
+dies_ok { $foo->vString(5) } '... 5 is NOT a Foo->String';
+
+dies_ok { $foo->vUndef(0.5) } '... 0.5 is NOT a Foo->Undef';
+lives_ok { $foo->vDefined(0.5) } '... 0.5 is a Foo->Defined';
+dies_ok { $foo->vInt(0.5) } '... 0.5 is NOT a Foo->Int';
+lives_ok { $foo->vNumber(0.5) } '... 0.5 is a Foo->Number';
+lives_ok { $foo->vStr(0.5) } '... 0.5 is a Foo->Str';
+dies_ok { $foo->vString(0.5) } '... 0.5 is NOT a Foo->String';
+
+dies_ok { $foo->vUndef('Foo') } '... "Foo" is NOT a Foo->Undef';
+lives_ok { $foo->vDefined('Foo') } '... "Foo" is a Foo->Defined';
+dies_ok { $foo->vInt('Foo') } '... "Foo" is NOT a Foo->Int';
+dies_ok { $foo->vNumber('Foo') } '... "Foo" is NOT a Foo->Number';
+lives_ok { $foo->vStr('Foo') } '... "Foo" is a Foo->Str';
+lives_ok { $foo->vString('Foo') } '... "Foo" is a Foo->String';
+
+# the lazy tests
+
+lives_ok { $foo->v_lazy_Undef() } '... undef is a Foo->Undef';
+dies_ok { $foo->v_lazy_Defined() } '... undef is NOT a Foo->Defined';
+dies_ok { $foo->v_lazy_Int() } '... undef is NOT a Foo->Int';
+dies_ok { $foo->v_lazy_Number() } '... undef is NOT a Foo->Number';
+dies_ok { $foo->v_lazy_Str() } '... undef is NOT a Foo->Str';
+dies_ok { $foo->v_lazy_String() } '... undef is NOT a Foo->String';
+
+
+
+
diff --git a/t/040_type_constraints/failing/024_role_type_constraint.t b/t/040_type_constraints/failing/024_role_type_constraint.t
new file mode 100644 (file)
index 0000000..df04adc
--- /dev/null
@@ -0,0 +1,66 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 18;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Mouse::Util::TypeConstraints');
+}
+
+{
+    package Gorch;
+    use Mouse::Role;
+
+    package Bar;
+    use Mouse::Role;
+
+    package Foo;
+    use Mouse::Role;
+
+    with qw(Bar Gorch);
+
+    package FooC;
+    use Mouse;
+    with qw(Foo);
+
+    package BarC;
+    use Mouse;
+    with qw(Bar);
+
+}
+
+lives_ok { role_type('Boop', message { "${_} is not a Boop" }) }
+  'role_type keywork works with message';
+
+my $type = find_type_constraint("Foo");
+
+is( $type->role, "Foo", "role attribute" );
+
+ok( $type->is_subtype_of("Gorch"), "subtype of gorch" );
+
+ok( $type->is_subtype_of("Bar"), "subtype of bar" );
+
+ok( $type->is_subtype_of("Object"), "subtype of Object" );
+
+ok( !$type->is_subtype_of("ThisTypeDoesNotExist"), "not subtype of unknown type name" );
+ok( !$type->is_a_type_of("ThisTypeDoesNotExist"), "not type of unknown type name" );
+
+ok( find_type_constraint("Bar")->check(FooC->new), "Foo passes Bar" );
+ok( find_type_constraint("Bar")->check(BarC->new), "Bar passes Bar" );
+ok( !find_type_constraint("Gorch")->check(BarC->new), "but Bar doesn't pass Gorch");
+
+my $boop = find_type_constraint("Boop");
+ok( $boop->has_message, 'Boop has a message');
+my $error = $boop->get_message(FooC->new);
+like( $error, qr/is not a Boop/,  'boop gives correct error message');
+
+
+ok( $type->equals($type), "equals self" );
+ok( $type->equals(Mouse::Meta::TypeConstraint::Role->new( name => "__ANON__", role => "Foo" )), "equals anon constraint of same value" );
+ok( $type->equals(Mouse::Meta::TypeConstraint::Role->new( name => "Oink", role => "Foo" )), "equals differently named constraint of same value" );
+ok( !$type->equals(Mouse::Meta::TypeConstraint::Role->new( name => "__ANON__", role => "Bar" )), "doesn't equal other anon constraint" );
+ok( $type->is_subtype_of(Mouse::Meta::TypeConstraint::Role->new( name => "__ANON__", role => "Bar" )), "subtype of other anon constraint" );
+
diff --git a/t/040_type_constraints/failing/025_type_coersion_on_lazy_attributes.t b/t/040_type_constraints/failing/025_type_coersion_on_lazy_attributes.t
new file mode 100644 (file)
index 0000000..745a4ce
--- /dev/null
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 2;
+
+{
+    package SomeClass;
+    use Mouse;
+    use Mouse::Util::TypeConstraints;
+
+    subtype 'DigitSix' => as 'Num'
+        => where { /^6$/ };
+    subtype 'TextSix' => as 'Str'
+        => where { /Six/i };
+    coerce 'TextSix'
+        => from 'DigitSix'
+        => via { confess("Cannot live without 6 ($_)") unless /^6$/; 'Six' };
+
+    has foo => (
+        is      => 'ro',
+        isa     => 'TextSix',
+        coerce  => 1,
+        default => 6,
+        lazy    => 1
+    );
+}
+
+my $attr = SomeClass->meta->get_attribute('foo');
+is($attr->get_value(SomeClass->new()), 'Six');
+is(SomeClass->new()->foo, 'Six');
+
diff --git a/t/040_type_constraints/failing/026_normalize_type_name.t b/t/040_type_constraints/failing/026_normalize_type_name.t
new file mode 100644 (file)
index 0000000..e2bc02d
--- /dev/null
@@ -0,0 +1,151 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 37;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Mouse::Util::TypeConstraints');
+}
+
+## First, we check that the new regex parsing works
+
+ok Mouse::Util::TypeConstraints::_detect_parameterized_type_constraint(
+    'ArrayRef[Str]') => 'detected correctly';
+
+is_deeply
+    [
+    Mouse::Util::TypeConstraints::_parse_parameterized_type_constraint(
+        'ArrayRef[Str]')
+    ],
+    [ "ArrayRef", "Str" ] => 'Correctly parsed ArrayRef[Str]';
+
+ok Mouse::Util::TypeConstraints::_detect_parameterized_type_constraint(
+    'ArrayRef[Str  ]') => 'detected correctly';
+
+is_deeply
+    [
+    Mouse::Util::TypeConstraints::_parse_parameterized_type_constraint(
+        'ArrayRef[Str  ]')
+    ],
+    [ "ArrayRef", "Str" ] => 'Correctly parsed ArrayRef[Str  ]';
+
+ok Mouse::Util::TypeConstraints::_detect_parameterized_type_constraint(
+    'ArrayRef[  Str]') => 'detected correctly';
+
+is_deeply
+    [
+    Mouse::Util::TypeConstraints::_parse_parameterized_type_constraint(
+        'ArrayRef[  Str]')
+    ],
+    [ "ArrayRef", "Str" ] => 'Correctly parsed ArrayRef[  Str]';
+
+ok Mouse::Util::TypeConstraints::_detect_parameterized_type_constraint(
+    'ArrayRef[  Str  ]') => 'detected correctly';
+
+is_deeply
+    [
+    Mouse::Util::TypeConstraints::_parse_parameterized_type_constraint(
+        'ArrayRef[  Str  ]')
+    ],
+    [ "ArrayRef", "Str" ] => 'Correctly parsed ArrayRef[  Str  ]';
+
+ok Mouse::Util::TypeConstraints::_detect_parameterized_type_constraint(
+    'ArrayRef[  HashRef[Int]  ]') => 'detected correctly';
+
+is_deeply
+    [
+    Mouse::Util::TypeConstraints::_parse_parameterized_type_constraint(
+        'ArrayRef[  HashRef[Int]  ]')
+    ],
+    [ "ArrayRef", "HashRef[Int]" ] =>
+    'Correctly parsed ArrayRef[  HashRef[Int]  ]';
+
+ok Mouse::Util::TypeConstraints::_detect_parameterized_type_constraint(
+    'ArrayRef[  HashRef[Int  ]  ]') => 'detected correctly';
+
+is_deeply
+    [
+    Mouse::Util::TypeConstraints::_parse_parameterized_type_constraint(
+        'ArrayRef[  HashRef[Int  ]  ]')
+    ],
+    [ "ArrayRef", "HashRef[Int  ]" ] =>
+    'Correctly parsed ArrayRef[  HashRef[Int  ]  ]';
+
+ok Mouse::Util::TypeConstraints::_detect_parameterized_type_constraint(
+    'ArrayRef[Int|Str]') => 'detected correctly';
+
+is_deeply
+    [
+    Mouse::Util::TypeConstraints::_parse_parameterized_type_constraint(
+        'ArrayRef[Int|Str]')
+    ],
+    [ "ArrayRef", "Int|Str" ] => 'Correctly parsed ArrayRef[Int|Str]';
+
+ok Mouse::Util::TypeConstraints::_detect_parameterized_type_constraint(
+    'ArrayRef[ArrayRef[Int]|Str]') => 'detected correctly';
+
+is_deeply
+    [
+    Mouse::Util::TypeConstraints::_parse_parameterized_type_constraint(
+        'ArrayRef[ArrayRef[Int]|Str]')
+    ],
+    [ "ArrayRef", "ArrayRef[Int]|Str" ] =>
+    'Correctly parsed ArrayRef[ArrayRef[Int]|Str]';
+
+## creating names via subtype
+
+ok my $r = Mouse::Util::TypeConstraints->get_type_constraint_registry =>
+    'Got registry object';
+
+ok my $subtype_a1
+    = subtype( 'subtype_a1' => as 'HashRef[Int]' ), => 'created subtype_a1';
+
+ok my $subtype_a2
+    = subtype( 'subtype_a2' => as 'HashRef[  Int]' ), => 'created subtype_a2';
+
+ok my $subtype_a3
+    = subtype( 'subtype_a2' => as 'HashRef[Int  ]' ), => 'created subtype_a2';
+
+ok my $subtype_a4 = subtype( 'subtype_a2' => as 'HashRef[  Int  ]' ), =>
+    'created subtype_a2';
+
+is $subtype_a1->parent->name, $subtype_a2->parent->name => 'names match';
+
+is $subtype_a1->parent->name, $subtype_a3->parent->name => 'names match';
+
+is $subtype_a1->parent->name, $subtype_a4->parent->name => 'names match';
+
+ok my $subtype_b1 = subtype( 'subtype_b1' => as 'HashRef[Int|Str]' ), =>
+    'created subtype_b1';
+
+ok my $subtype_b2 = subtype( 'subtype_b2' => as 'HashRef[Int | Str]' ), =>
+    'created subtype_b2';
+
+ok my $subtype_b3 = subtype( 'subtype_b3' => as 'HashRef[Str|Int]' ), =>
+    'created subtype_b3';
+
+is $subtype_b1->parent->name, $subtype_b2->parent->name => 'names match';
+
+is $subtype_b1->parent->name, $subtype_b3->parent->name => 'names match';
+
+is $subtype_b2->parent->name, $subtype_b3->parent->name => 'names match';
+
+## testing via add_constraint
+
+ok my $union1 = Mouse::Util::TypeConstraints::create_type_constraint_union(
+    'ArrayRef[Int|Str] | ArrayRef[Int | HashRef]') => 'Created Union1';
+
+ok my $union2 = Mouse::Util::TypeConstraints::create_type_constraint_union(
+    'ArrayRef[  Int|Str] | ArrayRef[Int | HashRef]') => 'Created Union2';
+
+ok my $union3 = Mouse::Util::TypeConstraints::create_type_constraint_union(
+    'ArrayRef[Int |Str   ] | ArrayRef[Int | HashRef  ]') => 'Created Union3';
+
+is $union1->name, $union2->name, 'names match';
+
+is $union1->name, $union3->name, 'names match';
+
+is $union2->name, $union3->name, 'names match';
diff --git a/t/040_type_constraints/failing/027_parameterize_from.t b/t/040_type_constraints/failing/027_parameterize_from.t
new file mode 100644 (file)
index 0000000..7ff3d0a
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 12;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Mouse::Util::TypeConstraints');
+}
+
+# testing the parameterize method
+
+{
+    my $parameterizable = subtype 'parameterizable_hashref', as 'HashRef';
+
+    my $parameterized = subtype 'parameterized_hashref', as 'HashRef[Int]';
+
+    my $int = Mouse::Util::TypeConstraints::find_type_constraint('Int');
+
+    my $from_parameterizable = $parameterizable->parameterize($int);
+
+    isa_ok $parameterizable,
+        'Mouse::Meta::TypeConstraint::Parameterizable', =>
+        'Got expected type instance';
+
+    package Test::Mouse::Meta::TypeConstraint::Parameterizable;
+    use Mouse;
+
+    has parameterizable      => ( is => 'rw', isa => $parameterizable );
+    has parameterized        => ( is => 'rw', isa => $parameterized );
+    has from_parameterizable => ( is => 'rw', isa => $from_parameterizable );
+}
+
+# Create and check a dummy object
+
+ok my $params = Test::Mouse::Meta::TypeConstraint::Parameterizable->new() =>
+    'Create Dummy object for testing';
+
+isa_ok $params, 'Test::Mouse::Meta::TypeConstraint::Parameterizable' =>
+    'isa correct type';
+
+# test parameterizable
+
+lives_ok sub {
+    $params->parameterizable( { a => 'Hello', b => 'World' } );
+} => 'No problem setting parameterizable';
+
+is_deeply $params->parameterizable,
+    { a => 'Hello', b => 'World' } => 'Got expected values';
+
+# test parameterized
+
+lives_ok sub {
+    $params->parameterized( { a => 1, b => 2 } );
+} => 'No problem setting parameterized';
+
+is_deeply $params->parameterized, { a => 1, b => 2 } => 'Got expected values';
+
+throws_ok sub {
+    $params->parameterized( { a => 'Hello', b => 'World' } );
+    }, qr/Attribute \(parameterized\) does not pass the type constraint/ =>
+    'parameterized throws expected error';
+
+# test from_parameterizable
+
+lives_ok sub {
+    $params->from_parameterizable( { a => 1, b => 2 } );
+} => 'No problem setting from_parameterizable';
+
+is_deeply $params->from_parameterizable,
+    { a => 1, b => 2 } => 'Got expected values';
+
+throws_ok sub {
+    $params->from_parameterizable( { a => 'Hello', b => 'World' } );
+    },
+    qr/Attribute \(from_parameterizable\) does not pass the type constraint/
+    => 'from_parameterizable throws expected error';
diff --git a/t/040_type_constraints/failing/029_define_type_twice_throws.t b/t/040_type_constraints/failing/029_define_type_twice_throws.t
new file mode 100644 (file)
index 0000000..67bc3ae
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 2;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Mouse::Util::TypeConstraints');
+}
+
+{
+    package Some::Class;
+    use Mouse::Util::TypeConstraints;
+
+    subtype 'MySubType' => as 'Int' => where { 1 };
+}
+
+throws_ok {
+    package Some::Other::Class;
+    use Mouse::Util::TypeConstraints;
+
+    subtype 'MySubType' => as 'Int' => where { 1 };
+} qr/cannot be created again/, 'Trying to create same type twice throws';
+
diff --git a/t/040_type_constraints/failing/030_class_subtypes.t b/t/040_type_constraints/failing/030_class_subtypes.t
new file mode 100644 (file)
index 0000000..6927c3f
--- /dev/null
@@ -0,0 +1,141 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 26;
+use Test::Exception;
+
+use Mouse::Util::TypeConstraints;
+use Mouse::Meta::TypeConstraint;
+
+
+## Create a subclass with a custom method
+
+{
+    package Test::Mouse::Meta::TypeConstraint::AnySubType;
+    use Mouse;
+    extends 'Mouse::Meta::TypeConstraint';
+
+    sub my_custom_method {
+        return 1;
+    }
+}
+
+my $Int = find_type_constraint('Int');
+ok $Int, 'Got a good type contstraint';
+
+my $parent  = Test::Mouse::Meta::TypeConstraint::AnySubType->new({
+               name => "Test::Mouse::Meta::TypeConstraint::AnySubType" ,
+               parent => $Int,
+});
+
+ok $parent, 'Created type constraint';
+ok $parent->check(1), 'Correctly passed';
+ok ! $parent->check('a'), 'correctly failed';
+ok $parent->my_custom_method, 'found the custom method';
+
+my $subtype1 = subtype 'another_subtype' => as $parent;
+
+ok $subtype1, 'Created type constraint';
+ok $subtype1->check(1), 'Correctly passed';
+ok ! $subtype1->check('a'), 'correctly failed';
+ok $subtype1->my_custom_method, 'found the custom method';
+
+
+my $subtype2 = subtype 'another_subtype' => as $subtype1 => where { $_ < 10 };
+
+ok $subtype2, 'Created type constraint';
+ok $subtype2->check(1), 'Correctly passed';
+ok ! $subtype2->check('a'), 'correctly failed';
+ok ! $subtype2->check(100), 'correctly failed';
+
+ok $subtype2->my_custom_method, 'found the custom method';
+
+
+{
+    package Foo;
+
+    use Mouse;
+}
+
+{
+    package Bar;
+
+    use Mouse;
+
+    extends 'Foo';
+}
+
+{
+    package Baz;
+
+    use Mouse;
+}
+
+my $foo = class_type 'Foo';
+my $isa_foo = subtype 'IsaFoo' => as $foo;
+
+ok $isa_foo, 'Created subtype of Foo type';
+ok $isa_foo->check( Foo->new ), 'Foo passes check';
+ok $isa_foo->check( Bar->new ), 'Bar passes check';
+ok ! $isa_foo->check( Baz->new ), 'Baz does not pass check';
+like $foo->get_message( Baz->new ), qr/^Validation failed for 'Foo' failed with value Baz=HASH\(0x\w+\) \(not isa Foo\)/, 'Better validation message';
+
+# Maybe in the future this *should* inherit?
+like $isa_foo->get_message( Baz->new ), qr/^Validation failed for 'IsaFoo' failed with value Baz=HASH\(0x\w+\)$/, "Subtypes do not automatically inherit parent type's message";
+
+
+# Implicit types
+{
+    package Quux;
+
+    use Mouse;
+
+    has age => (
+        isa => 'Positive',
+        is => 'bare',
+    );
+}
+
+throws_ok {
+    Quux->new(age => 3)
+} qr/^Attribute \(age\) does not pass the type constraint because: Validation failed for 'Positive' failed with value 3 \(not isa Positive\)/;
+
+lives_ok {
+    Quux->new(age => (bless {}, 'Positive'));
+};
+
+eval "
+    package Positive;
+    use Mouse;
+";
+
+throws_ok {
+    Quux->new(age => 3)
+} qr/^Attribute \(age\) does not pass the type constraint because: Validation failed for 'Positive' failed with value 3 \(not isa Positive\)/;
+
+lives_ok {
+    Quux->new(age => Positive->new)
+};
+
+class_type 'Negative' => message { "$_ is not a Negative Nancy" };
+
+{
+    package Quux::Ier;
+
+    use Mouse;
+
+    has age => (
+        isa => 'Negative',
+        is => 'bare',
+    );
+}
+
+throws_ok {
+    Quux::Ier->new(age => 3)
+} qr/^Attribute \(age\) does not pass the type constraint because: 3 is not a Negative Nancy /;
+
+lives_ok {
+    Quux::Ier->new(age => (bless {}, 'Negative'))
+};
diff --git a/t/040_type_constraints/failing/031_subtype_auto_vivify_parent.t b/t/040_type_constraints/failing/031_subtype_auto_vivify_parent.t
new file mode 100644 (file)
index 0000000..e245ab8
--- /dev/null
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 4;
+
+use Mouse::Util::TypeConstraints;
+
+
+{
+    package Foo;
+
+    sub new {
+        my $class = shift;
+
+        return bless {@_}, $class;
+    }
+}
+
+subtype 'FooWithSize'
+    => as 'Foo'
+    => where { $_[0]->{size} };
+
+
+my $type = find_type_constraint('FooWithSize');
+ok( $type,         'made a FooWithSize constraint' );
+ok( $type->parent, 'type has a parent type' );
+is( $type->parent->name, 'Foo', 'parent type is Foo' );
+isa_ok( $type->parent, 'Mouse::Meta::TypeConstraint::Class',
+        'parent type constraint is a class type' );
diff --git a/t/040_type_constraints/failing/032_throw_error.t b/t/040_type_constraints/failing/032_throw_error.t
new file mode 100644 (file)
index 0000000..d9c992b
--- /dev/null
@@ -0,0 +1,12 @@
+use strict;
+use warnings;
+
+use Test::More tests => 1;
+
+use Mouse::Util::TypeConstraints;
+
+
+eval { Mouse::Util::TypeConstraints::create_type_constraint_union() };
+
+like( $@, qr/\QYou must pass in at least 2 type names to make a union/,
+      'can throw a proper error without Mouse being loaded by the caller' );
diff --git a/t/040_type_constraints/failing/033_type_names.t b/t/040_type_constraints/failing/033_type_names.t
new file mode 100644 (file)
index 0000000..cdfee29
--- /dev/null
@@ -0,0 +1,36 @@
+use strict;
+use warnings;
+
+use Test::More tests => 6;
+use Test::Exception;
+
+use Mouse::Meta::TypeConstraint;
+use Mouse::Util::TypeConstraints;
+
+
+TODO:
+{
+    local $TODO = 'type names are not validated in the TC metaclass';
+
+    throws_ok { Mouse::Meta::TypeConstraint->new( name => 'Foo-Bar' ) }
+    qr/contains invalid characters/,
+        'Type names cannot contain a dash';
+}
+
+lives_ok { Mouse::Meta::TypeConstraint->new( name => 'Foo.Bar::Baz' ) }
+'Type names can contain periods and colons';
+
+throws_ok { subtype 'Foo-Baz' => as 'Item' }
+qr/contains invalid characters/,
+    'Type names cannot contain a dash (via subtype sugar)';
+
+lives_ok { subtype 'Foo.Bar::Baz' => as 'Item' }
+'Type names can contain periods and colons (via subtype sugar)';
+
+is( Mouse::Util::TypeConstraints::find_or_parse_type_constraint('ArrayRef[In-valid]'),
+    undef,
+    'find_or_parse_type_constraint returns undef on an invalid name' );
+
+is( Mouse::Util::TypeConstraints::find_or_parse_type_constraint('ArrayRef[Va.lid]'),
+    'ArrayRef[Va.lid]',
+    'find_or_parse_type_constraint returns name for valid name' );
diff --git a/t/040_type_constraints/failing/034_duck_types.t b/t/040_type_constraints/failing/034_duck_types.t
new file mode 100644 (file)
index 0000000..e5b467b
--- /dev/null
@@ -0,0 +1,80 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Test::More tests => 5;
+use Test::Exception;
+
+{
+
+    package Duck;
+    use Mouse;
+
+    sub quack { }
+
+}
+
+{
+
+    package Swan;
+    use Mouse;
+
+    sub honk { }
+
+}
+
+{
+
+    package RubberDuck;
+    use Mouse;
+
+    sub quack { }
+
+}
+
+{
+
+    package DucktypeTest;
+    use Mouse;
+    use Mouse::Util::TypeConstraints;
+
+    duck_type 'DuckType' => qw(quack);
+    duck_type 'SwanType' => [qw(honk)];
+
+    has duck => (
+        isa        => 'DuckType',
+        is => 'ro',
+        lazy_build => 1,
+    );
+
+    sub _build_duck { Duck->new }
+
+    has swan => (
+        isa => duck_type( [qw(honk)] ),
+        is => 'ro',
+    );
+
+    has other_swan => (
+        isa => 'SwanType',
+        is => 'ro',
+    );
+
+}
+
+# try giving it a duck
+lives_ok { DucktypeTest->new( duck => Duck->new ) } 'the Duck lives okay';
+
+# try giving it a swan which is like a duck, but not close enough
+throws_ok { DucktypeTest->new( duck => Swan->new ) }
+qr/Swan is missing methods 'quack'/,
+    "the Swan doesn't quack";
+
+# try giving it a rubber RubberDuckey
+lives_ok { DucktypeTest->new( swan => Swan->new ) } 'but a Swan can honk';
+
+# try giving it a rubber RubberDuckey
+lives_ok { DucktypeTest->new( duck => RubberDuck->new ) }
+'the RubberDuck lives okay';
+
+# try with the other constraint form
+lives_ok { DucktypeTest->new( other_swan => Swan->new ) } 'but a Swan can honk';
diff --git a/t/040_type_constraints/failing/035_duck_type_handles.t b/t/040_type_constraints/failing/035_duck_type_handles.t
new file mode 100644 (file)
index 0000000..40fe414
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Test::More tests => 2;
+use Test::Exception;
+
+my @phonograph;
+{
+    package Duck;
+    use Mouse;
+
+    sub walk {
+        push @phonograph, 'footsteps',
+    }
+
+    sub quack {
+        push @phonograph, 'quack';
+    }
+
+    package Swan;
+    use Mouse;
+
+    sub honk {
+        push @phonograph, 'honk';
+    }
+
+    package DucktypeTest;
+    use Mouse;
+    use Mouse::Util::TypeConstraints;
+
+    my $ducktype = duck_type 'DuckType' => qw(walk quack);
+
+    has duck => (
+        isa     => $ducktype,
+        handles => $ducktype,
+    );
+}
+
+my $t = DucktypeTest->new(duck => Duck->new);
+$t->quack;
+is_deeply([splice @phonograph], ['quack']);
+
+$t->walk;
+is_deeply([splice @phonograph], ['footsteps']);
+
diff --git a/t/040_type_constraints/failing/036_match_type_operator.t b/t/040_type_constraints/failing/036_match_type_operator.t
new file mode 100644 (file)
index 0000000..524c42d
--- /dev/null
@@ -0,0 +1,228 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 25;
+use Test::Exception;
+
+use Mouse::Util::TypeConstraints;
+
+# some simple type dispatching ...
+
+subtype 'Null'
+    => as 'ArrayRef'
+    => where { scalar @{$_} == 0 };
+
+sub head {
+    match_on_type @_ =>
+        Null => sub { die "Cannot get the head of Null" },
+    ArrayRef => sub { $_->[0] };
+}
+
+sub tail {
+    match_on_type @_ =>
+        Null => sub { die "Cannot get the tail of Null" },
+    ArrayRef => sub { [ @{ $_ }[ 1 .. $#{ $_ } ] ] };
+}
+
+sub len {
+    match_on_type @_ =>
+        Null => sub { 0 },
+    ArrayRef => sub { len( tail( $_ ) ) + 1 };
+}
+
+sub rev {
+    match_on_type @_ =>
+        Null => sub { [] },
+    ArrayRef => sub { [ @{ rev( tail( $_ ) ) }, head( $_ ) ] };
+}
+
+is( len( [] ), 0, '... got the right length');
+is( len( [ 1 ] ), 1, '... got the right length');
+is( len( [ 1 .. 5 ] ), 5, '... got the right length');
+is( len( [ 1 .. 50 ] ), 50, '... got the right length');
+
+is_deeply(
+    rev( [ 1 .. 5 ] ),
+    [ reverse 1 .. 5 ],
+    '... got the right reversed value'
+);
+
+# break down a Maybe Type ...
+
+sub break_it_down {
+    match_on_type shift,
+        'Maybe[Str]' => sub {
+            match_on_type $_ =>
+                'Undef' => sub { 'undef' },
+                  'Str' => sub { $_      }
+        },
+        sub { 'default' }
+}
+
+
+is( break_it_down( 'FOO' ), 'FOO', '... got the right value');
+is( break_it_down( [] ), 'default', '... got the right value');
+is( break_it_down( undef ), 'undef', '... got the right value');
+is( break_it_down(), 'undef', '... got the right value');
+
+# checking against enum types
+
+enum RGB  => qw[ red green blue ];
+enum CMYK => qw[ cyan magenta yellow black ];
+
+sub is_acceptable_color {
+    match_on_type shift,
+        'RGB'  => sub { 'RGB'              },
+        'CMYK' => sub { 'CMYK'             },
+                  sub { die "bad color $_" };
+}
+
+is( is_acceptable_color( 'blue' ), 'RGB', '... got the right value');
+is( is_acceptable_color( 'green' ), 'RGB', '... got the right value');
+is( is_acceptable_color( 'red' ), 'RGB', '... got the right value');
+is( is_acceptable_color( 'cyan' ), 'CMYK', '... got the right value');
+is( is_acceptable_color( 'magenta' ), 'CMYK', '... got the right value');
+is( is_acceptable_color( 'yellow' ), 'CMYK', '... got the right value');
+is( is_acceptable_color( 'black' ), 'CMYK', '... got the right value');
+
+dies_ok {
+    is_acceptable_color( 'orange' )
+} '... got the exception';
+
+## using it in an OO context
+
+{
+    package LinkedList;
+    use Mouse;
+    use Mouse::Util::TypeConstraints;
+
+    has 'next' => (
+        is        => 'ro',
+        isa       => __PACKAGE__,
+        lazy      => 1,
+        default   => sub { __PACKAGE__->new },
+        predicate => 'has_next'
+    );
+
+    sub pprint {
+        my $list = shift;
+        match_on_type $list =>
+            subtype(
+                 as 'LinkedList',
+              where { ! $_->has_next }
+                       ) => sub { '[]' },
+            'LinkedList' => sub { '[' . $_->next->pprint . ']' };
+    }
+}
+
+my $l = LinkedList->new;
+is($l->pprint, '[]', '... got the right pprint');
+$l->next;
+is($l->pprint, '[[]]', '... got the right pprint');
+$l->next->next;
+is($l->pprint, '[[[]]]', '... got the right pprint');
+$l->next->next->next;
+is($l->pprint, '[[[[]]]]', '... got the right pprint');
+
+# basic data dumper
+
+{
+    package Foo;
+    use Mouse;
+
+    sub to_string { 'Foo()' }
+}
+
+use B;
+
+sub ppprint {
+    my $x = shift;
+    match_on_type $x =>
+        HashRef   => sub {
+            my $hash = shift;
+            '{ ' . (join ", " => map {
+                        $_ . ' => ' . ppprint( $hash->{ $_ } )
+                    } sort keys %$hash ) . ' }'                         },
+        ArrayRef  => sub {
+            my $array = shift;
+            '[ ' . (join ", " => map { ppprint( $_ ) } @$array ) . ' ]' },
+        CodeRef   => sub { 'sub { ... }'                                },
+        RegexpRef => sub { 'qr/' . $_ . '/'                             },
+        GlobRef   => sub { '*' . B::svref_2object($_)->NAME             },
+        Object    => sub { $_->can('to_string') ? $_->to_string : $_    },
+        ScalarRef => sub { '\\' . ppprint( ${$_} )                      },
+        Num       => sub { $_                                           },
+        Str       => sub { '"'. $_ . '"'                                },
+        Undef     => sub { 'undef'                                      },
+                  => sub { die "I don't know what $_ is"                };
+}
+
+is(
+    ppprint(
+        {
+            one   => [ 1, 2, "three", 4, "five", \(my $x = "six") ],
+            two   => undef,
+            three => sub { "OH HAI" },
+            four  => qr/.*?/,
+            five  => \*ppprint,
+            six   => Foo->new,
+        }
+    ),
+    '{ five => *ppprint, four => qr/(?-xism:.*?)/, one => [ 1, 2, "three", 4, "five", \"six" ], six => Foo(), three => sub { ... }, two => undef }',
+    '... got the right pretty printed values'
+);
+
+# simple JSON serializer
+
+sub to_json {
+    my $x = shift;
+    match_on_type $x =>
+        HashRef   => sub {
+            my $hash = shift;
+            '{ ' . (join ", " => map {
+                        '"' . $_ . '" : ' . to_json( $hash->{ $_ } )
+                    } sort keys %$hash ) . ' }'                         },
+        ArrayRef  => sub {
+            my $array = shift;
+            '[ ' . (join ", " => map { to_json( $_ ) } @$array ) . ' ]' },
+        Num       => sub { $_                                           },
+        Str       => sub { '"'. $_ . '"'                                },
+        Undef     => sub { 'null'                                       },
+                  => sub { die "$_ is not acceptable json type"         };
+}
+
+is(
+    to_json( { one => 1, two => 2 } ),
+    '{ "one" : 1, "two" : 2 }',
+    '... got our valid JSON'
+);
+
+is(
+    to_json( {
+        one   => [ 1, 2, 3, 4 ],
+        two   => undef,
+        three => "Hello World"
+    } ),
+    '{ "one" : [ 1, 2, 3, 4 ], "three" : "Hello World", "two" : null }',
+    '... got our valid JSON'
+);
+
+
+# some error cases
+
+sub not_enough_matches {
+    my $x = shift;
+    match_on_type $x =>
+        Undef => sub { 'hello undef world'          },
+      CodeRef => sub { $_->('Hello code ref world') };
+}
+
+throws_ok {
+    not_enough_matches( [] )
+} qr/No cases matched for /, '... not enough matches';
+
+
+
+