File Coverage

blib/lib/Assert/Refute.pm
Criterion Covered Total %
statement 79 84 94.0
branch 29 34 85.2
condition 6 16 37.5
subroutine 17 18 94.4
pod 8 8 100.0
total 139 160 86.8


line stmt bran cond sub pod time code
1             package Assert::Refute;
2              
3 41     41   1973344 use 5.006;
  41         492  
4 41     41   249 use strict;
  41         89  
  41         1027  
5 41     41   241 use warnings;
  41         105  
  41         2711  
6             our $VERSION = '0.1501';
7              
8             =head1 NAME
9              
10             Assert::Refute - Unified testing and assertion tool
11              
12             =head1 DESCRIPTION
13              
14             This module allows injecting L-like code snippets
15             into production code, without turning the whole application
16             into a giant testing script.
17              
18             This can be though of as a lightweight design-by-contract form.
19              
20             =head1 SYNOPSIS
21              
22             The following code will die unless the conditions listed there are fulfilled:
23              
24             use Assert::Refute ":all", { on_fail => 'croak' };
25              
26             # Lots of code here
27             try_refute {
28             cmp_ok $price + $fee, "==", $total, "Money added up correctly";
29             like $description, qr/\w{3}/, "A readable description is present";
30             isa_ok $my_obj, "My::Class";
31             };
32              
33             A code snippet like this can guard important invariants,
34             ensure data correctness, or serve as a safety net while reworking
35             a monolithic application into separate testable modules.
36              
37             Note that the inside of the block can be copied into a unit-test as is,
38             giving one a fine-grained I----E accuracy> control.
39              
40             The same can be done without polluting the main package namespace:
41              
42             use Assert::Refute { on_fail => 'croak' };
43              
44             try_refute {
45             my $report = shift;
46             $report->cmp_ok( $price + $fee, "==", $total, "Money added up correctly" );
47             $report->like( $description, qr/\w{3}/, "A readable description is present" );
48             $report->isa_ok( $my_obj, "My::Class" );
49             };
50              
51             Relying on a global (in fact, per-package) callback is not required:
52              
53             use Assert::Refute {}, ":all";
54              
55             my $report = try_refute {
56             # ... assertions here
57             };
58             if (!$report->is_passing) {
59             $my_logger->error( "Something is not right: ".$report->get_tap );
60             # do whatever error handling is needed
61             };
62              
63             See L for more information about the underlying
64             object-oriented interface.
65              
66             =head1 ASSERTIONS, CONTRACTS, AND SUBCONTRACTS
67              
68             =over
69              
70             =item * We use the term I here to refer to a binary statement
71             that can be proven wrong using a well-defined, finite calculation.
72              
73             We say that assertion Is if such proof is provided,
74             and Ies otherwise.
75              
76             "X equals Y" and "a string contains such and such words"
77             are assertions by this definition.
78             "This code terminates" isn't because it requires solving the halting problem.
79             "All swans are white" isn't either unless there's code that produces
80             a black swan.
81              
82             =item * We use the term I here to refer to a code block
83             containing zero or more assertions.
84             A contract is said to I if any of its assertions fails,
85             and is assumed to I otherwise.
86              
87             This is not to be confused with full-fledged design-by-contract
88             which is much more specific about what contracts are.
89              
90             =item * Note that a contract itself is an assertion by this definition.
91             We use the term I to refer to an assertion that another
92             contract passes given certain arguments.
93              
94             These building blocks allow to create and verify
95             arbitrarily complex specifications.
96             See L below for limitations, though.
97              
98             =back
99              
100             =head1 EXPORT
101              
102             Any number of hash references may be added to the C statement,
103             resulting in an implicit Cconfigure> call.
104             A literal C<{}> will also trigger C.
105              
106             Everything else will be passed on to L.
107              
108             use Assert::Refute;
109              
110             as well as
111              
112             use Assert::Refute qw(:core);
113              
114             would only export C, C, C,
115             C, C, and C functions.
116              
117             Also for convenience some basic assertions mirroring the L suite
118             are exportable via C<:all> and C<:basic> export tag.
119              
120             use Assert::Refute qw(:all);
121              
122             would also export the following assertions:
123              
124             C, C, C, C, C, C,
125             C, C, C, C, C,
126             C, C, C, C, C.
127              
128             See L for more.
129              
130             This distribution also bundles some extra assertions:
131              
132             =over
133              
134             =item * L - inspect list structure;
135              
136             =item * L - verify exceptions and warnings;
137              
138             =item * L - inspect hash keys and values;
139              
140             =item * L - make sure numbers fit certain intervals;
141              
142             =back
143              
144             These need to be Cd explicitly.
145              
146             =cut
147              
148 41     41   289 use Carp;
  41         88  
  41         2970  
149 41     41   278 use Exporter;
  41         110  
  41         1627  
150              
151 41     41   16235 use Assert::Refute::Report;
  41         115  
  41         1345  
152 41     41   302 use Assert::Refute::Build qw(current_contract);
  41         91  
  41         2056  
153 41     41   250 use Assert::Refute::T::Basic;
  41         81  
  41         57064  
154              
155             my @basic = (
156             @Assert::Refute::T::Basic::EXPORT, 'plan'
157             );
158             my @core = qw(
159             contract refute_these try_refute
160             refute subcontract contract_is current_contract
161             );
162              
163             our @ISA = qw(Exporter);
164             our @EXPORT = @core;
165             our @EXPORT_OK = @basic;
166              
167             our %EXPORT_TAGS = (
168             basic => \@basic,
169             core => \@core,
170             all => [@core, @basic],
171             );
172              
173             our $DRIVER; # Used by other modules, declaration JFYI
174             our %CALLER_CONF;
175              
176             our $NDEBUG;
177             $NDEBUG = $ENV{PERL_NDEBUG} unless defined $NDEBUG;
178             $NDEBUG = $ENV{NDEBUG} unless defined $NDEBUG;
179              
180             sub import {
181 56     56   126091 my $class = shift;
182 56         120 my (%conf, @exp, $need_conf);
183 56         140 foreach (@_) {
184 56 100 33     394 if (ref $_ eq 'HASH') {
    50          
    50          
185 20         68 %conf = (%conf, %$_);
186 20         48 $need_conf++;
187             } elsif (!ref $_ and $_ eq '{}') {
188             # TODO 0.15 remove together with auto-carp
189 0         0 $need_conf++; # allow for -MAssert::Refute={}
190             } elsif (!ref $_) {
191 36         107 push @exp, $_;
192             } else {
193 0         0 croak "Unexpected argument in Assert::Refute->import: ".ref $_;
194             };
195             };
196              
197 56 100       258 $class->configure( \%conf, scalar caller ) if $need_conf;
198 56         47048 $class->export_to_level(1, undef, @exp);
199             };
200              
201             my %known_callback = (
202             skip => '',
203             carp => sub {
204             my $report = shift;
205             carp $report->get_tap
206             .($report->is_passing ? "Contract passed" : "Contract failed");
207             },
208             croak => sub {
209             my $report = shift;
210             croak $report->get_tap
211             .($report->is_passing ? "Contract passed" : "Contract failed");
212             },
213             );
214             my %default_conf = (
215             on_fail => 'skip',
216             on_pass => 'skip',
217             );
218              
219             =head2 try_refute { ... }
220              
221             Check whether given contract BLOCK containing zero or more assertions passes.
222              
223             Contract will fail if any of the assertions fails,
224             a C is declared and not fulfilled,
225             or an exception is thrown.
226             Otherwise it is assumed to pass.
227              
228             The BLOCK must accept one argument, the contract execution report,
229             likely a L instance.
230              
231             More arguments MAY be added in the future.
232             Return value is ignored.
233              
234             A read-only report instance is returned by C instead.
235              
236             If C/C callbacks were specified during C or
237             using C, they will also be executed if appropriate.
238              
239             If C or C environment variable is set at compile time,
240             this block is replaced with a stub
241             which returns an unconditionally passing report.
242              
243             This is basically what one expects from a module in C namespace.
244              
245             =head2 refute_these
246              
247             B<[DEPRECATED]> Same as above.
248              
249             It will stay available (with a warning) until as least 0.15.
250              
251             =cut
252              
253             sub try_refute(&;@) { ## no critic # need prototype
254 54     54 1 2101 my ( $block, @arg ) = @_;
255              
256             # Should a missing config even happen? Ok, play defensively...
257 54         174 my $conf = $CALLER_CONF{+caller};
258 54 100       170 if( !$conf ) {
259 2         302 carp "try_refute(): Usage without explicit configure() is DEPRECATED, assuming { on_fail => 'carp' }";
260 2         14 $conf = __PACKAGE__->configure( { on_fail => 'carp' }, scalar caller );
261             };
262 54 100       172 return $conf->{skip_all} if exists $conf->{skip_all};
263              
264             # This is generally a ripoff of A::R::Contract->apply
265 53         297 my $report = $conf->{driver}->new->do_run($block);
266              
267             # perform whatever action is needed
268 53 100       173 my $callback = $conf->{ $report->is_passing ? "on_pass" : "on_fail" };
269 53 100       176 $callback->($report) if $callback;
270              
271 51         262 return $report;
272             };
273              
274             sub refute_these (&;@) { ## no critic # need prototype
275 1     1 1 189 carp "refute_these { ... } is DEPRECATED, use try_refute{ ... } instead";
276 1         6 goto \&try_refute; ## no critic
277             }
278              
279             =head2 contract { ... }
280              
281             Save a contract BLOCK for future use:
282              
283             my $contract = contract {
284             my ($foo, $bar) = @_;
285             # conditions here
286             };
287              
288             # much later
289             my $report = $contract->apply( $real_foo, $real_bar );
290             # Returns an Assert::Refute::Report with conditions applied
291              
292             This is similar to how C / C works in L.
293              
294             B<[DEPRECATED]> This function will disappear in v.0.20.
295              
296             Prior to advent of C, this call used to be the main entry point
297             to this module.
298             This is no more the case, and a simple subroutine containing assertions
299             would fit in most places where C is appropriate.
300              
301             Use L instead.
302              
303             =cut
304              
305             sub contract (&@) { ## no critic
306 1     1 1 202 carp "contract{ ... } is DEPRECATED, use Assert::Refute::Contract::contract instead";
307              
308 1         7 require Assert::Refute::Contract;
309 1         7 goto &Assert::Refute::Contract::contract;
310             };
311              
312             =head2 plan tests => $n
313              
314             Plan to run exactly C assertions within a contract block.
315             Plan is optional, contract blocks can run fine without a plan.
316              
317             A contract will fail unconditionally if plan is present and is not fulfilled.
318              
319             C may only be called before executing any assertions.
320             C dies if called outside a contract block.
321              
322             Not exported by default to avoid namespace pollution.
323              
324             =head2 plan skip_all => $reason
325              
326             B<[EXPERIMENTAL]>.
327             Like above, but plan is assumed to be zero and a reason for that is specified.
328              
329             Note that the contract block is not interrupted,
330             it's up to the user to call return.
331             This MAY change in the future.
332              
333             =cut
334              
335             sub plan(@) { ## no critic
336 2     2 1 14 current_contract->plan( @_ );
337             };
338              
339             =head2 refute( $reason, $message )
340              
341             Verify (or, rather, try hard to disprove)
342             an assertion in scope of the current contract.
343              
344             The test passes if the C<$reason> is I, i.e. an empty string, C<0>,
345             or C.
346             Otherwise the C<$reason> is assumed to be a description of what went wrong.
347              
348             You can think of it as C and C from L combined:
349              
350             ok !$reason, $message
351             or diag $reason;
352              
353             As a special case, a literal C<1> is considered to be a boolean value
354             and the assertions just fails, without further explanation.
355              
356             As another special case, an C<\@arrayref> reason
357             will be unfolded into multiple C lines, for instance
358              
359             refute [ $answer, "isn't", 42 ], "life, universe, and everything";
360              
361             will output 3 diag lines.
362              
363             Returns true for a passing assertion and false for a failing one.
364             Dies if no contract is being executed at the time.
365              
366             =cut
367              
368             sub refute ($$) { ## no critic
369 41     41 1 390 current_contract()->refute(@_);
370             };
371              
372             =head2 subcontract( "Message" => $contract, @arguments )
373              
374             "The specified contract passes, given the arguments" assertion.
375             This is similar to C in L.
376              
377             B<[NOTE]> that the message comes first, unlike in C
378             or other assertion types, and is I.
379              
380             A I may be an L object,
381             a plain subroutine with some assertions inside, or
382             an L instance from a previous contract run.
383              
384             A subroutine MUST accept an empty L object.
385              
386             For instance, one could apply a previously defined validation to a
387             structure member:
388              
389             my $valid_email = contract {
390             my $email = shift;
391             # ... define your checks here
392             };
393              
394             my $valid_user = contract {
395             my $user = shift;
396             is ref $user, 'HASH'
397             or die "Bail out - not a hash";
398             like $user->{id}, qr/^\d+$/, "id is a number";
399             subcontract "Check e-mail" => $valid_email, $user->{email};
400             };
401              
402             # much later
403             $valid_user->apply( $form_input );
404              
405             Or pass a definition as I to be applied to specific structure parts
406             (think I, like C or C).
407              
408             my $array_of_foo = contract {
409             my ($is_foo, $ref) = @_;
410              
411             foreach (@$ref) {
412             subcontract "Element check", $is_foo, $_;
413             };
414             };
415              
416             $array_of_foo->apply( $valid_user, \@user_list );
417              
418             =cut
419              
420             sub subcontract($$@) { ## no critic
421 9     9 1 138 current_contract()->subcontract( @_ );
422             };
423              
424             =head2 contract_is
425              
426             contract_is $report, $signature, "Message";
427              
428             Assert that a contract is fulfilled exactly to the specified extent.
429             See L for signature format.
430              
431             This may be useful for verifying assertions and contracts themselves.
432              
433             This is actually a clone of L.
434              
435             =cut
436              
437             =head2 current_contract
438              
439             Returns the L object being worked on.
440              
441             If L has been detected and no contract block
442             is executed explicitly, returns a L instance.
443             This allows to define assertions and run them uniformly under
444             both L and L control.
445              
446             Dies if no contract could be detected.
447              
448             It is actually a clone of L.
449              
450             =head1 STATIC METHODS
451              
452             Use these methods to configure Assert::Refute globally.
453              
454             =head2 configure
455              
456             use Assert::Refute \%options;
457             Assert::Refute->configure( \%options );
458             Assert::Refute->configure( \%options, "My::Package");
459              
460             Set per-caller configuration values for given package.
461             C is called implicitly by C
462             if hash parameter(s) are present.
463              
464             %options may include:
465              
466             =over
467              
468             =item * on_pass - callback to execute if tests pass (default: C)
469              
470             =item * on_fail - callback to execute if tests fail (default: C,
471             but not just C - see below).
472              
473             =item * driver - use that class instead of L
474             as contract report.
475              
476             =item * skip_all - reason for skipping ALL C blocks
477             in the affected package.
478             This defaults to C or C environment variable.
479              
480             B<[EXPERIMENTAL]>. Name and meaning MAY change in the future.
481              
482             =back
483              
484             The callbacks MUST be either
485             a C accepting L object,
486             or one of predefined strings:
487              
488             =over
489              
490             =item * skip - do nothing;
491              
492             =item * carp - warn the stringified report;
493              
494             =item * croak - die with stringified report as error message;
495              
496             =back
497              
498             Returns the resulting config (with default values added,etc).
499              
500             As of current, this method only affects C.
501              
502             =cut
503              
504             my %conf_known;
505             $conf_known{$_}++ for qw( on_pass on_fail driver skip_all );
506              
507             sub configure {
508 24     24 1 78 my ($class, $given_conf, $caller) = @_;
509              
510 24 100       355 croak "Usage: $class->configure( \\%hash, \$target )"
511             unless ref $given_conf eq 'HASH';
512              
513 23         63 my @extra = grep { !$conf_known{$_} } keys %$given_conf;
  8         26  
514 23 50       82 croak "$class->configure: unknown parameters (@extra)"
515             if @extra;
516              
517             # configure whoever called us by default
518 23   100     72 $caller ||= scalar caller;
519              
520 23         96 my $conf = { %default_conf, %$given_conf };
521 23         133 $conf->{on_fail} = _coerce_cb($conf->{on_fail});
522 23         65 $conf->{on_pass} = _coerce_cb($conf->{on_pass});
523              
524             # Load driver
525 23 100       68 if( $conf->{driver} ) {
526 1         4 my $mod = "$conf->{driver}.pm";
527 1         3 $mod =~ s#::#/#g;
528 1         7 require $mod;
529             croak "$conf->{driver} is not Assert::Refute::Report, cannot use as driver"
530 1 50       151 unless $conf->{driver}->isa('Assert::Refute::Report');
531             } else {
532 22         46 $conf->{driver} = 'Assert::Refute::Report'; # this works for sure
533             };
534              
535 22 100 66     78 if ($NDEBUG and !$conf->{skip_all}) {
536 1         3 $conf->{skip_all} = "Assert::Refute turned off via NDEBUG=$NDEBUG";
537             };
538              
539 22 100       51 if ($conf->{skip_all}) {
540 1         3 my $default_report = $conf->{driver}->new;
541 1         4 $default_report->plan( skip_all => $conf->{skip_all} );
542 1         4 $default_report->done_testing;
543 1         2 $conf->{skip_all} = $default_report;
544             } else {
545 21         36 delete $conf->{skip_all};
546             };
547              
548 22         60 $CALLER_CONF{$caller} = $conf;
549             };
550              
551             =head2 get_config
552              
553             Returns configuration from above, initializing with defaults if needed.
554              
555             =cut
556              
557             sub get_config {
558 0     0 1 0 my ($class, $caller) = @_;
559              
560 0   0     0 $caller ||= scalar caller;
561 0   0     0 return $CALLER_CONF{$caller} ||= $class->configure({}, $caller);
562             };
563              
564             sub _coerce_cb {
565 46     46   85 my $sub = shift;
566              
567 46 100       135 $sub = defined $known_callback{$sub} ? $known_callback{$sub} : $sub;
568 46 100       137 return unless $sub;
569 6 50 33     34 croak "Bad callback $sub"
570             unless ref $sub and UNIVERSAL::isa( $sub, 'CODE' );
571 6         13 return $sub;
572             };
573              
574             =head1 EXTENDING THE SUITE
575              
576             Although building wrappers around C call is easy enough,
577             specialized tool exists for doing that.
578              
579             Use L to define new I as
580             both prototyped exportable functions and their counterpart methods
581             in L.
582             These functions will perform absolutely the same
583             under control of C, C, and L:
584              
585             package My::Prime;
586              
587             use Assert::Refute::Build;
588             use parent qw(Exporter);
589              
590             build_refute is_prime => sub {
591             my $n = shift;
592             return "Not a natural number: $n" unless $n =~ /^\d+$/;
593             return "$n is not prime" if $n <= 1;
594             for (my $i = 2; $i*$i <= $n; $i++) {
595             return "$i divides $n" unless $n % $i;
596             };
597             return '';
598             }, args => 1, export => 1;
599              
600             Much later:
601              
602             use My::Prime;
603              
604             is_prime 101, "101 is prime";
605             is_prime 42, "Life is simple"; # not true
606              
607             Note that the implementation C only cares about its arguments,
608             and doesn't do anything except returning a value.
609             Suddenly it's a L!
610              
611             Yet the exact reason for $n not being a prime will be reflected in test output.
612              
613             One can also subclass L
614             to create new I, for instance,
615             to register failed/passed tests in a unit-testing framework of choice
616             or generate warnings/exceptions when conditions are not met.
617              
618             That's how L integration is done -
619             see L.
620              
621             =head1 PERFORMANCE
622              
623             Set C or C (takes precedence)
624             environment variable to true to replace I C blocks with a stub.
625             L was used as reference.
626              
627             If that's not enough, use L
628             or just define a DEBUG constant and
629             append an C statement to C blocks.
630              
631             That said, refute is reasonably fast.
632             Special care is taken to minimize the CPU usage by I contracts.
633              
634             The C file in this distribution is capable of
635             verifying around 4000 contracts of 100 statements each in just under a second
636             on my 4500 C laptop.
637             Your mileage may vary!
638              
639             =head1 WHY REFUTE
640              
641             Communicating a passing test normally requires 1 bit of information:
642             everything went as planned.
643             For failing test, however, as much information as possible is desired.
644              
645             Thus C stands for an inverted assertion.
646             If $condition is B, it is regarded as a B.
647             If it is B, however, it is considered to be the B
648             for a failing test.
649              
650             This is similar to how Unix programs set their exit code,
651             or to Perl's own C<$@> variable,
652             or to the I concept in science.
653              
654             A C is a result of multiple checks,
655             combined into a single refutation.
656             It will succeed silently, yet spell out details if it doesn't pass.
657              
658             These primitives can serve as building blocks for arbitrarily complex
659             assertions, tests, and validations.
660              
661             =head1 SEE ALSO
662              
663             L, L, L
664              
665             =head1 BUGS
666              
667             This module is still under heavy development.
668             See C file in this distribution for an approximate roadmap.
669              
670             New features are marked as B<[EXPERIMENTAL]>.
671             Features that are to be removed will
672             stay B<[DEPRECATED]> (with a corresponding warning) for at least 5 releases,
673             unless such deprecation is extremely cumbersome.
674              
675             Test coverage is maintained at >90%, but who knows what lurks in the other 10%.
676              
677             See L
678             to browse old bugs or report new ones.
679              
680             =head1 SUPPORT
681              
682             You can find documentation for this module with the C command.
683              
684             perldoc Assert::Refute
685              
686             You can also look for information at:
687              
688             =over
689              
690             =item * First and foremost, use
691             L!
692              
693             =item * C: CPAN's request tracker (report bugs here)
694              
695             L
696              
697             =item * AnnoCPAN: Annotated CPAN documentation
698              
699             L
700              
701             =item * CPAN Ratings
702              
703             L
704              
705             =item * Search CPAN
706              
707             L
708              
709             =back
710              
711             =head1 ACKNOWLEDGEMENTS
712              
713             =over
714              
715             =item * Thanks to L
716             for C function name as well as a lot of feedback.
717              
718             =item * This L
719             by C inspired me to actually start working
720             on the first incarnation of this project.
721              
722             =item * Thanks to C for pass() and fail() calls.
723              
724             =back
725              
726             =head1 LICENSE AND COPYRIGHT
727              
728             Copyright 2017-2018 Konstantin S. Uvarin. C<< >>
729              
730             This program is free software; you can redistribute it and/or modify it
731             under the terms of the the Artistic License (2.0). You may obtain a
732             copy of the full license at:
733              
734             L
735              
736             Any use, modification, and distribution of the Standard or Modified
737             Versions is governed by this Artistic License. By using, modifying or
738             distributing the Package, you accept this license. Do not use, modify,
739             or distribute the Package, if you do not accept this license.
740              
741             If your Modified Version has been derived from a Modified Version made
742             by someone other than you, you are nevertheless required to ensure that
743             your Modified Version complies with the requirements of this license.
744              
745             This license does not grant you the right to use any trademark, service
746             mark, tradename, or logo of the Copyright Holder.
747              
748             This license includes the non-exclusive, worldwide, free-of-charge
749             patent license to make, have made, use, offer to sell, sell, import and
750             otherwise transfer the Package with respect to any patent claims
751             licensable by the Copyright Holder that are necessarily infringed by the
752             Package. If you institute patent litigation (including a cross-claim or
753             counterclaim) against any party alleging that the Package constitutes
754             direct or contributory patent infringement, then this Artistic License
755             to you shall terminate on the date that such litigation is filed.
756              
757             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
758             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
759             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
760             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
761             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
762             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
763             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
764             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
765              
766             =cut
767              
768             1; # End of Assert::Refute