File Coverage

blib/lib/Test/Mocha/Method.pm
Criterion Covered Total %
statement 86 86 100.0
branch 42 46 91.3
condition 16 16 100.0
subroutine 17 17 100.0
pod 0 4 100.0
total 161 169 97.6


line stmt bran cond sub pod time code
1             package Test::Mocha::Method;
2             # ABSTRACT: Objects to represent methods and their arguuments
3             $Test::Mocha::Method::VERSION = '0.67';
4 13     13   5810 use strict;
  13         26  
  13         364  
5 13     13   65 use warnings;
  13         24  
  13         670  
6              
7             # smartmatch dependencies
8 13     13   257 use 5.010001;
  13         46  
9 13     13   6408 use experimental 'smartmatch';
  13         46343  
  13         71  
10              
11 13     13   973 use Carp 'croak';
  13         28  
  13         759  
12 13     13   6727 use Devel::PartialDump 0.17 ();
  13         749934  
  13         482  
13 13     13   113 use Scalar::Util qw( blessed looks_like_number refaddr );
  13         64  
  13         1172  
14 13     13   5879 use Test::Mocha::Types qw( Matcher Slurpy );
  13         56  
  13         200  
15 13     13   14760 use Test::Mocha::Util 'check_slurpy_arg';
  13         40  
  13         842  
16 13     13   141 use Types::Standard qw( ArrayRef HashRef Str );
  13         29  
  13         64  
17              
18 13     13   9581 use overload '""' => \&stringify, fallback => 1;
  13         38  
  13         122  
19              
20             # cause string overloaded objects (Matchers) to be stringified
21             my $Dumper = Devel::PartialDump->new( objects => 0, stringify => 1 );
22              
23             sub new {
24             # uncoverable pod
25 481     481 0 1481 my ( $class, %args ) = @_;
26             ### assert: Str->check( $args{name} )
27             ### assert: ArrayRef->check( $args{args} )
28 481         1800 return bless \%args, $class;
29             }
30              
31             sub name {
32             # uncoverable pod
33 2122     2122 0 5902 return $_[0]->{name};
34             }
35              
36             sub args {
37             # uncoverable pod
38 1049     1049 0 1426 return @{ $_[0]->{args} };
  1049         2546  
39             }
40              
41             sub stringify {
42             # """
43             # Stringifies this method call to something that roughly resembles what
44             # you'd type in Perl.
45             # """
46             # uncoverable pod
47 385     385 0 29588 my ($self) = @_;
48 385         780 return $self->name . '(' . $Dumper->dump( $self->args ) . ')';
49             }
50              
51             sub __satisfied_by {
52             # """
53             # Returns true if the given C<$invocation> satisfies this method call.
54             # """
55             # uncoverable pod
56 691     691   1115 my ( $self, $invocation ) = @_;
57              
58 691 100       1188 return unless $invocation->name eq $self->name;
59              
60 332         1084 my @expected = $self->args;
61 332         633 my @input = $invocation->args;
62             # invocation arguments can't be argument matchers
63             ### assert: ! grep { Matcher->check($_) } @input
64 332         879 check_slurpy_arg(@expected);
65              
66             # match @input against @expected which may include argument matchers
67 332   100     1254 while ( @input && @expected ) {
68 241         490 my $matcher = shift @expected;
69              
70             # slurpy argument matcher
71 241 100       489 if ( Slurpy->check($matcher) ) {
    100          
72 35         801 $matcher = $matcher->{slurpy};
73             ### assert: $matcher->is_a_type_of(ArrayRef) || $matcher->is_a_type_of(HashRef)
74              
75 35         53 my $value;
76 35 100       72 if ( $matcher->is_a_type_of(ArrayRef) ) {
    50          
77 15         2826 $value = [@input];
78             }
79             elsif ( $matcher->is_a_type_of(HashRef) ) {
80 20 100       23511 return unless scalar(@input) % 2 == 0;
81 8         28 $value = {@input};
82             }
83             # else { invalid matcher type }
84 23 100       63 return unless $matcher->check($value);
85              
86 18         250 @input = ();
87             }
88             # argument matcher
89             elsif ( Matcher->check($matcher) ) {
90 67 100       2186 return unless $matcher->check( shift @input );
91             }
92             # literal match
93             else {
94 139 100       3267 return unless _match( shift(@input), $matcher );
95             }
96             }
97              
98             # slurpy matcher should handle empty argument lists
99 253 100 100     2160 if ( @expected > 0 && Slurpy->check( $expected[0] ) ) {
100 8         221 my $matcher = shift(@expected)->{slurpy};
101              
102 8         15 my $value;
103 8 100       23 if ( $matcher->is_a_type_of(ArrayRef) ) {
    50          
104 6         432 $value = [@input];
105             }
106             elsif ( $matcher->is_a_type_of(HashRef) ) {
107 2 50       1369 return unless scalar(@input) % 2 == 0;
108 2         5 $value = {@input};
109             }
110             # else { invalid matcher type }
111 8 50       25 return unless $matcher->check($value);
112             }
113              
114 253   100     1737 return @input == 0 && @expected == 0;
115             }
116              
117             sub _match {
118             # """Match 2 values for equality."""
119             # uncoverable pod
120 161     161   343 my ( $x, $y ) = @_;
121              
122             # This function uses smart matching, but we need to limit the scenarios
123             # in which it is used because of its quirks.
124              
125             # ref types must match
126 161 100       464 return if ref $x ne ref $y;
127              
128             # objects match only if they are the same object
129 138 100 100     541 if ( blessed($x) || ref($x) eq 'CODE' ) {
130 5         31 return refaddr($x) == refaddr($y);
131             }
132              
133             # don't smartmatch on arrays because it recurses
134             # which leads to the same quirks that we want to avoid
135 133 100       281 if ( ref($x) eq 'ARRAY' ) {
136 7 100       10 return if $#{$x} != $#{$y};
  7         10  
  7         19  
137              
138             # recurse to handle nested structures
139 6         10 foreach ( 0 .. $#{$x} ) {
  6         17  
140 17 100       38 return if !_match( $x->[$_], $y->[$_] );
141             }
142 4         16 return 1;
143             }
144              
145 126 100       228 if ( ref($x) eq 'HASH' ) {
146             # smartmatch only matches the hash keys
147 4 100       19 return if not $x ~~ $y;
148              
149             # ... but we want to match the hash values too
150 3         7 foreach ( keys %{$x} ) {
  3         8  
151 5 100       10 return if !_match( $x->{$_}, $y->{$_} );
152             }
153 2         44 return 1;
154             }
155              
156             # avoid smartmatch doing number matches on strings
157             # e.g. '5x' ~~ 5 is true
158 122 100 100     599 return if looks_like_number($x) xor looks_like_number($y);
159              
160 118         599 return $x ~~ $y;
161             }
162              
163             1;