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 3     3   1449 use strict;
  3         8  
  3         121  
4 3     3   19 use warnings;
  3         5  
  3         134  
5             our $VERSION = '0.1501';
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 3     3   18 use Carp;
  3         4  
  3         155  
53 3     3   17 use Scalar::Util qw(blessed);
  3         12  
  3         148  
54 3     3   19 use parent qw(Exporter);
  3         5  
  3         14  
55              
56             our @EXPORT = qw(array_of);
57              
58 3     3   234 use Assert::Refute::Build;
  3         5  
  3         192  
59 3     3   21 use Assert::Refute qw(:all); # TODO oo interface in internals, plz
  3         6  
  3         25  
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   15 my ($self, $list, $match, $message) = @_;
82              
83 5   50     12 $message ||= "list of";
84             $self->subcontract( $message => sub {
85 5     5   7 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         10 foreach (@$list) {
90 6         16 $report->like( $_, $match );
91             };
92             } elsif (blessed $match && $match->isa("Assert::Refute::Contract")) {
93 1         4 foreach (@$list) {
94 2         9 $report->subcontract( "list item" => $match, $_ );
95             };
96             } elsif (UNIVERSAL::isa( $match, 'CODE' )) {
97 1         2 foreach (@$list) {
98 2         5 $match->($report, $_);
99             };
100             } else {
101 0   0     0 croak "array_of: unknown criterion type: ".(ref $match || 'SCALAR');
102             };
103 5         29 } ); # 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   13 my ($block, $list) = @_;
120              
121 7 100       22 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         10 my $caller = caller 1;
128 3     3   24 no strict 'refs'; ## no critic - need to localize $a and $b
  3         5  
  3         990  
129 5         8 \(*{$caller."::a"}, *{$caller."::b"});
  5         17  
  5         17  
130             };
131 5         14 local (*$refa, *$refb);
132              
133 5         8 my @bad;
134 5         15 for( my $i = 0; $i < @$list - 1; $i++) {
135 10         30 *$refa = \$list->[$i];
136 10         18 *$refb = \$list->[$i+1];
137 10 100       22 $block->() or push @bad, "($i, ".($i+1).")";
138             };
139              
140 5 100       47 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   11 my ($self, $code, $data, $message) = @_;
158              
159 4   50     9 $message ||= "map_subtest";
160              
161             $self->subcontract( $message => sub {
162 4     4   13 $code->($_[0]) for @$data;
163 4         18 } );
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   10 my ($self, $block, $list, $name) = @_;
187              
188 4   50     19 $name ||= "reduce_subtest";
189             # empty list always ok
190 4 100       37 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         6 my ($refa, $refb) = do {
196 2         6 my $caller = caller 1;
197 3     3   22 no strict 'refs'; ## no critic - need to localize $a and $b
  3         7  
  3         526  
198 2         3 \(*{$caller."::a"}, *{$caller."::b"});
  2         8  
  2         7  
199             };
200              
201             $self->subcontract( $name => sub {
202 2     2   5 local (*$refa, *$refb);
203 2         21 for( my $i = 0; $i < @$list - 1; $i++) {
204 5         24 *$refa = \$list->[$i];
205 5         13 *$refb = \$list->[$i+1];
206 5         13 $block->($_[0]);
207             };
208 2         13 });
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;