The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# This script should be runnable with 'make test'.

######################### We start with some black magic to print on failure.

BEGIN { $| = 1 }
END { print "not ok 1\n"  unless $loaded }

use lib qw( ./t );
use Magic;

use Class::Contract;
$loaded = 1;
print "ok 1\n";

######################### End of black magic.

::ok('desc'   => 'Simple Contract',
     'expect' => 1,
     'code'   => <<'CODE');
package Simple;
use Class::Contract;
contract {
  method 'my_method'; impl {1};
  abstract method 'my_abstract_method';
  class abstract method 'my_class_abstract_method';
  abstract class method 'my_abstract_class_method';

  attr 'my_attr';
  class attr 'my_class_attr';
};
CODE

::ok('desc'   => 'Extended Contracts',
     'expect' => 1,
     'code'   => <<'CODE');
package Grandfather;
use Class::Contract; 

contract {
  ctor 'new';
    impl { shift if $Class::Contract::VERSION < 1.10; push @::test, [0, @_]; };
  class method 'incr';
    pre  { $::test{'pre'}++;  1 };
    impl { $::test{'impl'}++; 1 };
    post { $::test{'post'}++; 1 };
  eval qq|class method '_$_'; pre {1}; impl {1}| foreach qw(a b c d e f g h i);
  eval qq|class method '_$_'; impl {1}         | foreach qw(j k l m n o p q r);
  eval qq|class method '_$_'; pre {0}; impl {1}| foreach qw(s t u v w x y z 1);
  die $@  if $@;
};

package Father; 
use Class::Contract; 
contract {
  inherits 'Grandfather';
  ctor 'new'; 
    impl { shift if $Class::Contract::VERSION < 1.10; push @::test, [1, @_]; };
  class method 'incr';
    pre  { $::test{'pre'}++;  0 };
    impl { $::test{'impl'}++; 1 };
    post { $::test{'post'}++; 1 };
  eval qq|class method '_$_'; pre {1}; impl {1}| foreach qw(a b c j k l s t u);
  eval qq|class method '_$_'; impl {1}         | foreach qw(d e f m n o v w x);
  eval qq|class method '_$_'; pre {0}; impl {1}| foreach qw(g h i p q r y z 1);
  die $@  if $@;
};

package Son; 
use Class::Contract; 
contract {
  inherits 'Father';
  ctor 'new'; 
    impl { shift if $Class::Contract::VERSION < 1.10; push @::test, [2, @_]; };
  class method 'incr';
    pre  { $::test{'pre'}++;  0 };
    impl { $::test{'impl'}++; 1 };
    post { $::test{'post'}++; 1 };
  eval qq|class method '_$_'; pre {1}; impl {1}| foreach qw(a d g j m p s v y);
  eval qq|class method '_$_'; impl {1}         | foreach qw(b e h k n q t w z);
  eval qq|class method '_$_'; pre {0}; impl {1}| foreach qw(c f i l o r u x 1);
  die $@  if $@;
};

package Args;
use Class::Contract;
contract {
  ctor 'new';
    pre  { $::test{'pre'}  = \@_; 1 };
    impl { $::test{'impl'} = \@_; 1 };
    post { $::test{'post'} = \@_; 1 };
  class method 'same';
    pre  { @{$::test{'same_pre'}}  = (@_); 1 };
    impl { @{$::test{'same_impl'}} = (@_); 1 };
    post { @{$::test{'same_post'}} = (@_); 1 };
  class method 'modify_pre';
    pre  {
      shift  if $Class::Contract::VERSION < 1.10;
      $_[0] = 'pre'; $::test{'pre'} = $_[0]; 1;
    };
    impl { shift if $Class::Contract::VERSION < 1.10; $::test{'impl'}=$_[0]; 1};
    post { shift if $Class::Contract::VERSION < 1.10; $::test{'post'}=$_[0]; 1};
  class method 'modify_impl';
    pre  { shift if $Class::Contract::VERSION < 1.10; $::test{'pre'}=$_[0]; 1};
    impl {
      shift  if $Class::Contract::VERSION < 1.10;
      $_[0] = 'impl'; $::test{'impl'} = $_[0]; 1;
    };
    post {
      shift  if $Class::Contract::VERSION < 1.10;
      $::test{'post'} = $_[0]; 1
    };
  class method 'modify_post';
    pre  {
      shift  if $Class::Contract::VERSION < 1.10;
      $::test{'pre'}=$_[0]; 1
    };
    impl {
      shift  if $Class::Contract::VERSION < 1.10;
      $::test{'impl'}=$_[0]; 1
    };
    post {
      shift  if $Class::Contract::VERSION < 1.10;
      $_[0] = 'post'; $::test{'post'} = $_[0]; 1;
    };
};
CODE


::ok('desc' => "pre gets shallow copy of args to be passed to impl",
     'expect' => 1,
     'need'   => 'Extended Contracts',
     'code'   => <<'CODE');
package main;
undef %::test;
my @args = qw(1 a 2 b 4 c);
Args->same(@args);

my $fail = 0;
foreach (0..$#args) {
  $fail++
    if $::test{'same_pre'}->[$_] ne $::test{'same_impl'}->[$_]
}
$fail ? 0 : 1;
CODE


::ok('desc' => "pre can't modify args passed to method or impl",
     'expect' => 1,
     'need'   => 'Extended Contracts',
     'code'   => <<'CODE');
package main;
%test = ();
my $arg = 'foo';
Args->modify_pre($arg);
join('', $arg, @test{qw(pre impl post)}) eq 'fooprefoofoo';
CODE


::ok('desc' => "impl can modify args passed to method and post",
     'expect' => 1,
     'need'   => 'Extended Contracts',
     'code'   => <<'CODE');
package main;
%test = ();
my $arg = 'foo';
Args->modify_impl($arg);
join('', $arg, @test{qw( pre impl post )}) eq 'implfooimplimpl';
CODE


::ok('desc'    => "post can't modify args passed to method",
     'expect'  => 1,
     'need'    => 'Extended Contracts',
     'code'    => <<'CODE');
package main;
%test = ();
my $arg = 'foo';
Args->modify_post($arg);
join('', $arg, @test{qw( pre impl post )}) eq 'foofoofoopost';
CODE


::ok('desc'   => 'invoking abstract method raises exception',
     'expect' => qr/^Can\'t call abstract method/s,
     'need'   => 'Simple Contract',
     'code'   => <<'CODE');
package main;
Simple->my_class_abstract_method;
CODE


::ok('desc'   => 'missing impl raise an exception',
     'expect' => qr/^No implementation for method foo/s,
     'code'   => <<'CODE');
package UnDeclared_Method;
use Class::Contract;
contract { method 'foo' };
CODE


::ok('desc'   => 'simple implementation based on abstract',
     'expect' => 1,
     'need'   => 'Simple Contract',
     'code'   => <<'CODE');
package Inherited_Class_Method;
use Class::Contract;
contract {
  inherits 'Simple';
  class method 'my_class_abstract_method';
    impl { 1 };
};

package main;
Inherited_Class_Method->my_class_abstract_method,
CODE


::ok('desc'   => "implementation not inherited",
     'expect' => 1,
     'need'   => 'Extended Contracts',
     'code'   => <<'CODE');
package main;
%test = ();
Son->incr;
$test{'impl'};
CODE

::ok('desc'   => 'post-conditions are inherited',
     'expect' => 3,
     'need'   => 'Extended Contracts',
     'code'   => <<'CODE');
package main;
%test = ();
Son->incr;
$test{'post'};
CODE

::ok('desc'   => 'pre check must satisfy self or an ascending ancestor',
     'expect' => '',
     'need'   => 'Extended Contracts',
     'code'   => <<'CODE');
package main;
undef $@;
$fail = '';

# No inheritence involvement                                    G 
eval qq|Grandfather->_a|; $fail .= 'a' if $@;               #   1
eval qq|Grandfather->_j|; $fail .= 'j' if $@;               #  null
eval qq|Grandfather->_s|; $fail .= 's' unless $@; undef $@; #   0

# Parent Child inheritence                                  G    F 
eval qq|Father->_a|; $fail .= 'a' if $@;                #   1    1
eval qq|Father->_d|; $fail .= 'd' if $@;                #   1   null
eval qq|Father->_g|; $fail .= 'g' if $@;                #   1    0
eval qq|Father->_j|; $fail .= 'j' if $@;                #  null  1
eval qq|Father->_m|; $fail .= 'm' if $@;                #  null null
eval qq|Father->_p|; $fail .= 'p' unless $@; undef $@;  #  null  0
eval qq|Father->_s|; $fail .= 's' if $@;                #   0    1
eval qq|Father->_v|; $fail .= 'v' unless $@; undef $@;  #   0   null
eval qq|Father->_y|; $fail .= 'y' unless $@; undef $@;  #   0    0

# Grandparent Parent Child inheritence                      G    F    S
eval qq|Son->_a|; $fail .= 'a' if $@;                   #   1    1    1
eval qq|Son->_b|; $fail .= 'b' if $@;                   #   1    1   null
eval qq|Son->_c|; $fail .= 'c' if $@;                   #   1    1    0
eval qq|Son->_d|; $fail .= 'd' if $@;                   #   1   null  1
eval qq|Son->_e|; $fail .= 'e' if $@;                   #   1   null null
eval qq|Son->_f|; $fail .= 'f' if $@;                   #   1   null  0
eval qq|Son->_g|; $fail .= 'g' if $@;                   #   1    0    1
eval qq|Son->_h|; $fail .= 'h' if $@;                   #   1    0   null
eval qq|Son->_i|; $fail .= 'i' if $@;                   #   1    0    0
eval qq|Son->_j|; $fail .= 'j' if $@;                   #  null  1    1
eval qq|Son->_k|; $fail .= 'k' if $@;                   #  null  1   null
eval qq|Son->_l|; $fail .= 'l' if $@;                   #  null  1    0
eval qq|Son->_m|; $fail .= 'm' if $@;                   #  null null  1
eval qq|Son->_n|; $fail .= 'n' if $@;                   #  null null null
eval qq|Son->_o|; $fail .= 'o' unless $@; undef $@;     #  null null  0
eval qq|Son->_p|; $fail .= 'p' if $@;                   #  null  0    1
eval qq|Son->_q|; $fail .= 'q' unless $@; undef $@;     #  null  0   null
eval qq|Son->_r|; $fail .= 'r' unless $@; undef $@;     #  null  0    0
eval qq|Son->_s|; $fail .= 's' if $@;                   #   0    1    1
eval qq|Son->_t|; $fail .= 't' if $@;                   #   0    1   null
eval qq|Son->_u|; $fail .= 'u' if $@;                   #   0    1    0
eval qq|Son->_v|; $fail .= 'v' if $@;                   #   0   null  1
eval qq|Son->_w|; $fail .= 'w' unless $@; undef $@;     #   0   null null
eval qq|Son->_x|; $fail .= 'x' unless $@; undef $@;     #   0   null  0
eval qq|Son->_y|; $fail .= 'y' if $@;                   #   0    0    1
eval qq|Son->_z|; $fail .= 'z' unless $@; undef $@;     #   0    0   null
eval qq|Son->_1|; $fail .= '1' unless $@; undef $@;     #   0    0    0
print "fail: $fail\n"  if $fail;
$fail;
CODE

::ok('desc'   => 'private inheritance stays private',
     'expect' => qr/^Forget 'private'?/s,
     'code'   => <<'CODE');
package Private;
use Class::Contract;
contract {
  private class method 'foo';
    impl {1};
};

package Private::Still;
use Class::Contract;
contract {
  inherits 'Private';
  class method 'foo';
    impl {1};
};

CODE

__DATA__

##ok('desc'   => 'ancestor class method derived as object method',
#     'expect' => 1,
#     'need'   => 'additional thought',
#     'code'   => <<'CODE');
#1
#CODE

# REQ:	Pre and post conditions for methods shall receive the same argument
#       list as the implementation (Pre gets copy, Post identical)

# ?:  Do attribute preconditions get argument list?

# ?:  Are there limits as to where &old, &self, and &value can be called?

# REQ:  Invariant, pre, and post conditions may be declared optional.

# REQ:  Optional condition checking may be switched on or off using the
#       &check method (see examples below).

# REQ:  The implementation's return value shall be available in the
#	method's post condition(s) through the subroutine &value,
#       which returns a reference to a scalar or an array
#	(depending on the calling context).

# REQ:  &value shall also provide access to the value of an attribute within
#	that attribute's pre and post conditions.

# REQ:  The value of the object prior to a method shall be available in the
#	post-conditions via the &old subroutine, which returns a copy
#	of the object as it was prior to the method call.


### Garrett's additions ###

# REQ:  C<method> and C<ctor> shall croak if the naming clashes with the
#       subroutines exported by Class::Contract

# REQ:  It shall not be possible to redefine a Contract

# REQ:  ctor plays nicely with Overload.pm

# REQ:  &value plays nicely with Overload.pm