File Coverage

blib/lib/Assert/Refute/T/Array.pm
Criterion Covered Total %
statement 72 73 98.6
branch 16 18 88.8
condition 5 11 45.4
subroutine 16 16 100.0
pod n/a
total 109 118 92.3


line stmt bran cond sub pod time code
1             package Assert::Refute::T::Array;
2              
3 4     4   281312 use strict;
  4         37  
  4         119  
4 4     4   20 use warnings;
  4         11  
  4         184  
5             our $VERSION = '0.17';
6              
7             =head1 NAME
8              
9             Assert::Refute::T::Array - Assertions about arrays for Assert::Refute suite
10              
11             =head1 SYNOPSIS
12              
13             Add C and C checks to both runtime checks
14             and unit test scripts.
15              
16             use Test::More;
17             use Assert::Refute qw(:core);
18             use Assert::Refute::T::Array;
19              
20             Testing that array consists of given values:
21              
22             array_of [ "foo", "bar", "baz" ], qr/ba/, "This fails because of foo";
23              
24             array_of [
25             { id => 42, name => "Answer to life" },
26             { id => 137 },
27             ], contract {
28             package T;
29             use Assert::Refute::T::Basic;
30             like $_[0]->{name}, qr/^\w+$/;
31             like $_[0]->{id}, qr/^\d+$/;
32             }, "This also fails";
33              
34             Testing that array is ordered:
35              
36             is_sorted { $a lt $b } [sort qw(foo bar bar baz)],
37             "This fails because of repetition";
38             is_sorted { $a le $b } [sort qw(foo bar bar baz)],
39             "This passes though";
40              
41             Not only sorting, but other types of partial order can be tested:
42              
43             is_sorted { $b->{start_date} eq $a->{end_date} }, \@reservations,
44             "Next reservation aligned with the previous one";
45              
46             =head1 EXPORTS
47              
48             All of the below functions are exported by default:
49              
50             =cut
51              
52 4     4   22 use Carp;
  4         7  
  4         251  
53 4     4   23 use Scalar::Util qw( blessed reftype );
  4         7  
  4         240  
54 4     4   499 use parent qw(Exporter);
  4         335  
  4         30  
55              
56             our @EXPORT = qw(array_of);
57              
58 4     4   817 use Assert::Refute::Build;
  4         9057  
  4         256  
59 4     4   591 use Assert::Refute qw(:all); # TODO oo interface in internals, plz
  4         12090  
  4         24  
60              
61             =head2 array_of
62              
63             array_of \@list, $criteria, [ "message" ]
64              
65             Check that I item in the list matches criteria, which may be one of:
66              
67             =over
68              
69             =item * regex - just match against regular expression;
70              
71             =item * a functions - execute that function inside a single subcontract;
72              
73             =item * L - pass each element as argument to
74             a I subcontract.
75              
76             =back
77              
78             =cut
79              
80             build_refute array_of => sub {
81 5     5   6895 my ($self, $list, $match, $message) = @_;
82              
83 5   50     14 $message ||= "list of";
84             $self->subcontract( $message => sub {
85 5     5   176 my $report = shift;
86              
87             # TODO 0.30 mention list element number
88 5 100 66     36 if (ref $match eq 'Regexp') {
    100          
    50          
89 3         9 foreach (@$list) {
90 6         133 $report->like( $_, $match );
91             };
92             } elsif (blessed $match && $match->isa("Assert::Refute::Contract")) {
93 1         3 foreach (@$list) {
94 2         242 $report->subcontract( "list item" => $match, $_ );
95             };
96             } elsif (UNIVERSAL::isa( $match, 'CODE' )) {
97 1         3 foreach (@$list) {
98 2         105 $match->($report, $_);
99             };
100             } else {
101 0   0     0 croak "array_of: unknown criterion type: ".(ref $match || 'SCALAR');
102             };
103 5         44 } ); # end subcontract
104             }, export => 1, manual => 1, args => 2;
105              
106             =head2 is_sorted
107              
108             is_sorted { $a ... $b } \@list, "message";
109              
110             Check that condition about ($a, $b) holds
111             for every two subsequent items in array.
112              
113             Consider using C instead if there's a complex
114             condition inside.
115              
116             =cut
117              
118             build_refute is_sorted => sub {
119 7     7   3925 my ($block, $list) = @_;
120              
121 7 100       25 return '' if @$list < 2;
122              
123             # Unfortunately, $a and $b are package variables
124             # of the *calling* package...
125             # So localize them through a hack.
126 5         10 my ($refa, $refb) = do {
127 5         24 my $caller = caller 1;
128 4     4   2114 no strict 'refs'; ## no critic - need to localize $a and $b
  4         8  
  4         1519  
129 5         8 \(*{$caller."::a"}, *{$caller."::b"});
  5         18  
  5         16  
130             };
131 5         16 local (*$refa, *$refb);
132              
133 5         8 my @bad;
134 5         15 for( my $i = 0; $i < @$list - 1; $i++) {
135 10         248 *$refa = \$list->[$i];
136 10         19 *$refb = \$list->[$i+1];
137 10 100       20 $block->() or push @bad, "($i, ".($i+1).")";
138             };
139              
140 5 100       144 return !@bad ? '' : 'Not ordered pairs: '.join(', ', @bad);
141             }, block => 1, args => 1, export => 1;
142              
143             =head2 map_subtest { ok $_ } \@list, "message";
144              
145             Execute a subcontract that applies checks in { ... }
146             to every element of an array.
147              
148             Return value of code block is B.
149              
150             Automatically succeeds if there are no elements.
151              
152             =cut
153              
154             build_refute map_subtest => sub {
155 4     4   4622 my ($self, $code, $data, $message) = @_;
156              
157 4   50     12 $message ||= "map_subtest";
158              
159             $self->subcontract( $message => sub {
160 4 50   4   159 return ok 0, "Not an array"
161             unless reftype $data eq 'ARRAY';
162             # Test::More doesn't regard empty tests as passing.
163 4 100       15 ok 1, "empty array - automatic success"
164             unless @$data;
165 4         47 $code->($_[0]) for @$data;
166 4         23 } );
167             }, block => 1, export => 1, manual => 1, args => 1;
168              
169             =head2 reduce_subtest { $a ... $b } \@list, "message";
170              
171             Applies checks in { ... } to every pair of subsequent elements in list.
172             The element with lower number is $a, and with higher number is $b.
173              
174             reduce_subtest { ... } [1,2,3,4];
175              
176             would induce pairs:
177              
178             ($a = 1, $b = 2), ($a = 2, $b = 3), ($a = 3, $b = 4)
179              
180             Return value of code block is B.
181              
182             Automatically succeeds if list has less than 2 elements.
183              
184             =cut
185              
186             build_refute reduce_subtest => sub {
187 4     4   2895 my ($self, $block, $list, $name) = @_;
188              
189 4   50     23 $name ||= "reduce_subtest";
190             # empty list always ok
191 4 100       13 return $self->refute( 0, $name ) if @$list < 2;
192              
193             # Unfortunately, $a and $b are package variables
194             # of the *calling* package...
195             # So localize them through a hack.
196 2         5 my ($refa, $refb) = do {
197 2         5 my $caller = caller 1;
198 4     4   51 no strict 'refs'; ## no critic - need to localize $a and $b
  4         8  
  4         818  
199 2         3 \(*{$caller."::a"}, *{$caller."::b"});
  2         8  
  2         7  
200             };
201              
202             $self->subcontract( $name => sub {
203 2     2   59 local (*$refa, *$refb);
204 2         10 for( my $i = 0; $i < @$list - 1; $i++) {
205 5         223 *$refa = \$list->[$i];
206 5         12 *$refb = \$list->[$i+1];
207 5         12 $block->($_[0]);
208             };
209 2         12 });
210             }, block => 1, export => 1, manual => 1, args => 1;
211              
212             =head1 AUTHOR
213              
214             Konstantin S. Uvarin, C<< >>
215              
216             =head1 BUGS
217              
218             Please report bugs via github or RT:
219              
220             =over
221              
222             =item * L
223              
224             =item * C
225              
226             =item * L
227              
228             =back
229              
230             =head1 SUPPORT
231              
232             You can find documentation for this module with the C command.
233              
234             perldoc Assert::Refute::T::Array
235              
236             You can also look for information at:
237              
238             =over 4
239              
240             =item * github: L
241              
242             =item * RT: CPAN's request tracker (report bugs here)
243              
244             L
245              
246             =item * AnnoCPAN: Annotated CPAN documentation
247              
248             L
249              
250             =item * CPAN Ratings
251              
252             L
253              
254             =item * Search CPAN
255              
256             L
257              
258             =back
259              
260              
261             =head1 ACKNOWLEDGEMENTS
262              
263              
264             =head1 LICENSE AND COPYRIGHT
265              
266             Copyright 2018 Konstantin S. Uvarin.
267              
268             This program is free software; you can redistribute it and/or modify it
269             under the terms of the the Artistic License (2.0). You may obtain a
270             copy of the full license at:
271              
272             L
273              
274             Any use, modification, and distribution of the Standard or Modified
275             Versions is governed by this Artistic License. By using, modifying or
276             distributing the Package, you accept this license. Do not use, modify,
277             or distribute the Package, if you do not accept this license.
278              
279             If your Modified Version has been derived from a Modified Version made
280             by someone other than you, you are nevertheless required to ensure that
281             your Modified Version complies with the requirements of this license.
282              
283             This license does not grant you the right to use any trademark, service
284             mark, tradename, or logo of the Copyright Holder.
285              
286             This license includes the non-exclusive, worldwide, free-of-charge
287             patent license to make, have made, use, offer to sell, sell, import and
288             otherwise transfer the Package with respect to any patent claims
289             licensable by the Copyright Holder that are necessarily infringed by the
290             Package. If you institute patent litigation (including a cross-claim or
291             counterclaim) against any party alleging that the Package constitutes
292             direct or contributory patent infringement, then this Artistic License
293             to you shall terminate on the date that such litigation is filed.
294              
295             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
296             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
297             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
298             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
299             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
300             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
301             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
302             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
303              
304              
305             =cut
306              
307             1;