File Coverage

blib/lib/Assert/Refute/T/Array.pm
Criterion Covered Total %
statement 70 71 98.5
branch 13 14 92.8
condition 5 11 45.4
subroutine 16 16 100.0
pod n/a
total 104 112 92.8


line stmt bran cond sub pod time code
1             package Assert::Refute::T::Array;
2              
3 4     4   275236 use strict;
  4         36  
  4         105  
4 4     4   19 use warnings;
  4         8  
  4         196  
5             our $VERSION = '0.16';
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         8  
  4         217  
53 4     4   22 use Scalar::Util qw(blessed);
  4         7  
  4         169  
54 4     4   689 use parent qw(Exporter);
  4         283  
  4         20  
55              
56             our @EXPORT = qw(array_of);
57              
58 4     4   674 use Assert::Refute::Build;
  4         7558  
  4         231  
59 4     4   490 use Assert::Refute qw(:all); # TODO oo interface in internals, plz
  4         9022  
  4         26  
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   6614 my ($self, $list, $match, $message) = @_;
82              
83 5   50     15 $message ||= "list of";
84             $self->subcontract( $message => sub {
85 5     5   165 my $report = shift;
86              
87             # TODO 0.30 mention list element number
88 5 100 66     31 if (ref $match eq 'Regexp') {
    100          
    50          
89 3         9 foreach (@$list) {
90 6         109 $report->like( $_, $match );
91             };
92             } elsif (blessed $match && $match->isa("Assert::Refute::Contract")) {
93 1         3 foreach (@$list) {
94 2         226 $report->subcontract( "list item" => $match, $_ );
95             };
96             } elsif (UNIVERSAL::isa( $match, 'CODE' )) {
97 1         2 foreach (@$list) {
98 2         89 $match->($report, $_);
99             };
100             } else {
101 0   0     0 croak "array_of: unknown criterion type: ".(ref $match || 'SCALAR');
102             };
103 5         28 } ); # 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   3789 my ($block, $list) = @_;
120              
121 7 100       24 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         9 my ($refa, $refb) = do {
127 5         23 my $caller = caller 1;
128 4     4   1830 no strict 'refs'; ## no critic - need to localize $a and $b
  4         14  
  4         1242  
129 5         7 \(*{$caller."::a"}, *{$caller."::b"});
  5         16  
  5         15  
130             };
131 5         13 local (*$refa, *$refb);
132              
133 5         9 my @bad;
134 5         16 for( my $i = 0; $i < @$list - 1; $i++) {
135 10         201 *$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       141 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             B<[EXPERIMENTAL]> Name and meaning may change in the future.
153              
154             =cut
155              
156             build_refute map_subtest => sub {
157 4     4   3890 my ($self, $code, $data, $message) = @_;
158              
159 4   50     12 $message ||= "map_subtest";
160              
161             $self->subcontract( $message => sub {
162 4     4   231 $code->($_[0]) for @$data;
163 4         19 } );
164             }, block => 1, export => 1, manual => 1, args => 1;
165              
166             =head2 reduce_subtest { $a ... $b } \@list, "message";
167              
168             Applies checks in { ... } to every pair of subsequent elements in list.
169             The element with lower number is $a, and with higher number is $b.
170              
171             reduce_subtest { ... } [1,2,3,4];
172              
173             would induce pairs:
174              
175             ($a = 1, $b = 2), ($a = 2, $b = 3), ($a = 3, $b = 4)
176              
177             Return value of code block is B.
178              
179             Automatically succeeds if list has less than 2 elements.
180              
181             B<[EXPERIMENTAL]> Name and meaning may change in the future.
182              
183             =cut
184              
185             build_refute reduce_subtest => sub {
186 4     4   2353 my ($self, $block, $list, $name) = @_;
187              
188 4   50     20 $name ||= "reduce_subtest";
189             # empty list always ok
190 4 100       13 return $self->refute( 0, $name ) if @$list < 2;
191              
192             # Unfortunately, $a and $b are package variables
193             # of the *calling* package...
194             # So localize them through a hack.
195 2         3 my ($refa, $refb) = do {
196 2         5 my $caller = caller 1;
197 4     4   98 no strict 'refs'; ## no critic - need to localize $a and $b
  4         12  
  4         663  
198 2         2 \(*{$caller."::a"}, *{$caller."::b"});
  2         7  
  2         6  
199             };
200              
201             $self->subcontract( $name => sub {
202 2     2   34 local (*$refa, *$refb);
203 2         9 for( my $i = 0; $i < @$list - 1; $i++) {
204 5         170 *$refa = \$list->[$i];
205 5         8 *$refb = \$list->[$i+1];
206 5         10 $block->($_[0]);
207             };
208 2         9 });
209             }, block => 1, export => 1, manual => 1, args => 1;
210              
211             =head1 LICENSE AND COPYRIGHT
212              
213             This module is part of L suite.
214              
215             Copyright 2017-2018 Konstantin S. Uvarin. C<< >>
216              
217             This program is free software; you can redistribute it and/or modify it
218             under the terms of the the Artistic License (2.0). You may obtain a
219             copy of the full license at:
220              
221             L
222              
223             =cut
224              
225             1;