File Coverage

blib/lib/Assert/Refute/Build.pm
Criterion Covered Total %
statement 92 92 100.0
branch 46 50 92.0
condition 21 31 67.7
subroutine 15 15 100.0
pod 3 3 100.0
total 177 191 92.6


line stmt bran cond sub pod time code
1             package Assert::Refute::Build;
2              
3 57     57   71252 use 5.006;
  57         215  
4 57     57   279 use strict;
  57         99  
  57         1237  
5 57     57   262 use warnings;
  57         102  
  57         2968  
6             our $VERSION = '0.16';
7              
8             =head1 NAME
9              
10             Assert::Refute::Build - tool for extending Assert::Refute suite
11              
12             =head1 DESCRIPTION
13              
14             Although arbitrary checks may be created using just the C function,
15             they may be cumbersome to use and especially share.
16              
17             This module takes care of some boilerplate as well as maintains parity
18             between functional and object-oriented interfaces of L.
19              
20             =head1 SYNOPSIS
21              
22             Extending the test suite goes as follows:
23              
24             package My::Package;
25             use Assert::Refute::Build;
26             use parent qw(Exporter);
27              
28             build_refute is_everything => sub {
29             return if $_[0] == 42;
30             return "$_[0] is not answer to life, universe, and everything";
31             }, export => 1, args => 1;
32              
33             1;
34              
35             This can be later used inside production code to check a condition:
36              
37             use Assert::Refute qw(:all);
38             use My::Package;
39             my $fun_check = contract {
40             is_everything( shift );
41             };
42             my $oo_check = contract {
43             $_[0]->is_everything( $_[1] );
44             }, need_object => 1;
45             # ditto
46              
47             # apply $fun_check or $oo_check to a variable, get result
48              
49             my $log = $oo_check->apply(137);
50             $log->is_passing; # nope
51             $log->get_tap; # get details
52              
53             This call will create a prototyped function is_everything(...) in the calling
54             package, with C positional parameters and an optional human-readable
55             message. (Think C, C).
56              
57             =head1 FUNCTIONS
58              
59             All functions are exportable.
60              
61             =cut
62              
63 57     57   451 use Carp;
  57         162  
  57         3448  
64 57     57   36180 use Data::Dumper;
  57         398087  
  57         3940  
65 57     57   433 use Scalar::Util qw(weaken blessed set_prototype looks_like_number refaddr);
  57         118  
  57         3544  
66 57     57   19755 use parent qw(Exporter);
  57         14353  
  57         328  
67             our @EXPORT = qw(build_refute current_contract to_scalar);
68              
69             # NOTE HACK
70             # If we're being loaded after Test::More, we're *likely* inside a test script
71             # This has to be re-done properly
72             # Cannot instantiate *here* because cyclic dependencies
73             # so wait until current_contract() is called
74             our $MORE_DETECTED = Test::Builder->can("new") ? 1 : 0;
75              
76             =head2 build_refute name => \&CODE, %options
77              
78             This function
79              
80             =over
81              
82             =item * accepts a subroutine reference that returns a false value on success
83             and a brief description of the discrepancy on failure
84             (e.g. C<"$got != $expected">);
85              
86             Note that this function does not need to know anything about the testing
87             environment it is in, it just cares about its arguments
88             (think I).
89              
90             =item * builds an exportable wrapper around it that would talk to
91             the most recent L instance;
92              
93             =item * adds a method with the same name to L
94             so that object-oriented and functional interfaces
95             are as close to each other as possible.
96              
97             =back
98              
99             As a side effect, Assert::Refute's internals are added to the caller's
100             C<@CARP_NOT> array so that carp/croak points to where the built function
101             is actually used.
102              
103             B One needs to use Exporter explicitly if either C
104             or C option is in use. This MAY change in the future.
105              
106             Options may include:
107              
108             =over
109              
110             =item * C => 1 - add function to @EXPORT
111             (Exporter still has to be used by target module explicitly).
112              
113             =item * C => 1 - add function to @EXPORT_OK (don't export by default).
114              
115             =item * C => 1 - don't generate a function at all, just add to
116             L's methods.
117              
118             =item * C => 1 - don't generate any code.
119             Instead, assume that user has already done that and just add a method
120             to L and a prototyped exportable wrapper.
121              
122             This may be useful to create refutations based on subcontract or such.
123              
124             B<[EXPERIMENTAL]>.
125              
126             =item * C => C - number of arguments.
127             This will generate a prototyped function
128             accepting C scalars + optional description.
129              
130             =item * C => 1 - create a list prototype instead.
131             Mutually exclusive with C.
132              
133             =item * C => 1 - create a block function.
134              
135             =item * C => 1 - skip prototype, function will have to be called
136             with parentheses.
137              
138             =back
139              
140             The name must not start with C, C, or C.
141             Also colliding with a previously defined name would case an exception.
142              
143             =cut
144              
145             my %Backend;
146             my %Carp_not;
147             my $trash_can = __PACKAGE__."::generated::For::Cover::To::See";
148             my %known;
149             $known{$_}++ for qw(args list block no_proto manual
150             export export_ok no_create);
151              
152             sub build_refute(@) { ## no critic # Moose-like DSL for the win!
153 940     940 1 61081 my ($name, $cond, %opt) = @_;
154              
155 940         1459 my $class = "Assert::Refute::Report";
156              
157 940 100       3197 if ($name =~ /^(get_|set_|do_)/) {
158 1         146 croak "build_refute(): fucntion name shall not start with get_, set_, or do_";
159             };
160 939 100 66     10144 if (my $backend = ( $class->can($name) && ($Backend{$name} || $class )) ) {
161 2         413 croak "build_refute(): '$name' already registered by $backend";
162             };
163 937         2421 my @extra = grep { !$known{$_} } keys %opt;
  1894         4615  
164 937 50       2145 croak "build_refute(): unknown options: @extra"
165             if @extra;
166             croak "build_refute(): list and args options are mutually exclusive"
167 937 50 66     2573 if $opt{list} and defined $opt{args};
168              
169 937         5832 my @caller = caller(0);
170 937   33     3538 my $target = $opt{target} || $caller[0];
171              
172 937 50 33     3043 confess "Too bad (@caller)" if !$target or $target eq __PACKAGE__;
173              
174 937   100     2203 my $nargs = $opt{args} || 0;
175 937 100       1777 $nargs = 9**9**9 if $opt{list};
176              
177 937 100       1625 $nargs++ if $opt{block};
178              
179             # TODO Add executability check if $block
180             my $method = $opt{manual} ? $cond : sub {
181 62     62   219 my $self = shift;
182 62 100       120 my $message; $message = pop unless @_ <= $nargs;
  62         174  
183              
184 62         177 return $self->refute( scalar $cond->(@_), $message );
185 937 100       4381 };
186             my $wrapper = $opt{manual} ? sub {
187 1   33 1   7 return $cond->( $Assert::Refute::DRIVER || current_contract(), @_ );
188             } : sub {
189 112 100   112   1986 my $message; $message = pop unless @_ <= $nargs;
  112         333  
190             return (
191             # Ugly hack for speed in happy case
192 112   66     474 $Assert::Refute::DRIVER || current_contract()
193             )->refute( scalar $cond->(@_), $message );
194 937 100       3997 };
195 937 100 100     5373 if (!$opt{no_proto} and ($opt{block} || $opt{list} || defined $opt{args})) {
      66        
196 936 100 100     3115 my $proto = $opt{list} ? '@' : '$' x ($opt{args} || 0);
197 936 100       3055 $proto = "&$proto" if $opt{block};
198 936 100       1827 $proto .= ';$' unless $opt{list};
199              
200             # '&' for set_proto to work on a scalar, not {CODE;}
201 936         4010 &set_prototype( $wrapper, $proto );
202             };
203              
204 937         3889 $Backend{$name} = "$target at $caller[1] line $caller[2]"; # just for the record
205 937         1900 my $todo_carp_not = !$Carp_not{ $target }++;
206 937         1399 my $todo_create = !$opt{no_create};
207 937 100       2009 my $export = $opt{export} ? "EXPORT" : $opt{export_ok} ? "EXPORT_OK" : "";
    100          
208              
209             # Magic below, beware!
210 57     57   37421 no strict 'refs'; ## no critic # really need magic here
  57         170  
  57         5660  
211              
212             # set up method for OO interface
213 937         1295 *{ $class."::$name" } = $method;
  937         3079  
214              
215             # FIXME UGLY HACK - somehow it makes Devel::Cover see the code in report
216 937         1355 *{ $trash_can."::$name" } = $cond;
  937         5065  
217              
218 937 50       1967 if ($todo_create) {
219 937         1174 *{ $target."::$name" } = $wrapper;
  937         3662  
220 937 100       1956 push @{ $target."::".$export }, $name
  936         2931  
221             if $export;
222             };
223 937 100       5116 if ($todo_carp_not) {
224 57     57   458 no warnings 'once';
  57         138  
  57         22001  
225 71         1906 push @{ $target."::CARP_NOT" }, "Assert::Refute::Contract", $class;
  71         423  
226             };
227              
228             # magic ends here
229              
230 937         5333 return 1;
231             };
232              
233             =head2 current_contract
234              
235             Returns a L object.
236             Dies if no contract is being executed at the time.
237              
238             =cut
239              
240             sub current_contract() { ## nocritic
241 78 100   78 1 4596 return $Assert::Refute::DRIVER if $Assert::Refute::DRIVER;
242              
243             # Would love to just die, but...
244 16 100       67 if ($MORE_DETECTED) {
245 15         6632 require Assert::Refute::Driver::More;
246 15         119 return $Assert::Refute::DRIVER = Assert::Refute::Driver::More->new;
247             };
248              
249 1         551 croak "Not currently testing anything";
250             };
251              
252             =head2 to_scalar
253              
254             Convert an arbitrary value into a human-readable string.
255              
256             to_scalar( $value )
257             to_scalar( $value, $depth )
258              
259             If $value is undefined and $depth is not given, returns C<'(undef)'>
260             (so that it's harder to confuse with a literal C<'undef'>).
261              
262             If $value is a scalar and $depth is not given, returns $value as is,
263             without quotes or anything.
264              
265             Otherwise returns L to depth $depth (or unlimited by default).
266              
267             One SHOULD NOT rely on exact format of returned data.
268              
269             =cut
270              
271             sub to_scalar {
272 290     290 1 1238 my ($data, $depth) = @_;
273              
274 290 100 100     1209 if (!ref $data and !defined $depth) {
275             # auto-explain
276 191 100       1058 return defined $data ? $data : '(undef)';
277             };
278              
279 99 100       207 $depth = 0 unless defined $depth;
280              
281 99         168 local $Data::Dumper::Indent = 0;
282 99         157 local $Data::Dumper::Sortkeys = 1;
283 99         248 local $Data::Dumper::Maxdepth = $depth;
284 99         159 local $Data::Dumper::Quotekeys = 0;
285 99         154 local $Data::Dumper::Useqq = 1;
286 99         274 my $str = Dumper($data);
287 99         5397 $str =~ s/^\$VAR1 *= *//;
288 99         372 $str =~ s/;\s*$//s;
289 99         552 return $str;
290             };
291              
292             =head1 LICENSE AND COPYRIGHT
293              
294             This module is part of L suite.
295              
296             Copyright 2017-2018 Konstantin S. Uvarin. C<< >>
297              
298             This program is free software; you can redistribute it and/or modify it
299             under the terms of the the Artistic License (2.0). You may obtain a
300             copy of the full license at:
301              
302             L
303              
304             =cut
305              
306             1;