File Coverage

blib/lib/TAP/DOM/Waivers.pm
Criterion Covered Total %
statement 52 54 96.3
branch 11 16 68.7
condition n/a
subroutine 12 12 100.0
pod 1 1 100.0
total 76 83 91.5


line stmt bran cond sub pod time code
1             package TAP::DOM::Waivers;
2             # git description: e19136e
3              
4             BEGIN {
5 3     3   429639 $TAP::DOM::Waivers::AUTHORITY = 'cpan:SCHWIGON';
6             }
7             {
8             $TAP::DOM::Waivers::VERSION = '0.001';
9             }
10             # ABSTRACT: Patching TAP::DOM, usually for test waivers
11              
12 3     3   86 use 5.008;
  3         10  
  3         96  
13 3     3   28 use strict;
  3         4  
  3         92  
14 3     3   18 use warnings;
  3         6  
  3         110  
15              
16 3     3   966 use Data::Dumper;
  3         9639  
  3         173  
17 3     3   2589 use Data::DPath 'dpathr';
  3         409831  
  3         26  
18 3     3   3337 use Clone "clone";
  3         10383  
  3         337  
19 3         49 use Sub::Exporter -setup => {
20             exports => [ 'waive' ],
21             groups => { all => [ 'waive' ] },
22 3     3   35 };
  3         6  
23              
24             sub waive {
25 5     5 1 99564 my ($dom, $waivers, $options) = @_;
26              
27 5         14 my $new_dom_ref;
28 5 100       31 if ($options->{no_clone}) {
29 1         3 $new_dom_ref = \$dom;
30             } else {
31 4         2147 $new_dom_ref = \ (clone($dom));
32             }
33 5         69 foreach my $waiver (@$waivers) {
34             # apply on matching dpath
35 5 100       13 if (my @paths = @{$waiver->{match_dpath} || []}) {
  5 50       54  
  1 100       12  
    50          
36 4         29 _patch_dom_dpath( $new_dom_ref, $waiver, $_ ) foreach @paths;
37             }
38             elsif (my @descriptions = @{$waiver->{match_description} || []}) {
39 1         2 my @paths = map { _description_to_dpath($_) } @descriptions;
  1         6  
40 1         6 _patch_dom_dpath( $new_dom_ref, $waiver, $_ ) foreach @paths;
41             }
42             }
43 5         32 return $$new_dom_ref;
44             }
45              
46             sub _description_to_dpath {
47 1     1   2 my ($description) = @_;
48              
49             # the '#' as delimiter is not expected in a description
50             # because it has TAP semantics, however, we escape to be sure
51 1         4 $description =~ s/\#/\\\#/g;
52              
53 1         7 return "//lines//description[value =~ qr#$description#]/..";
54             }
55              
56             sub _meta_patch {
57 2     2   4 my ($metapatch) = @_;
58              
59 2         4 my $patch;
60             my $explanation;
61 2 50       9 if ($explanation = $metapatch->{TODO}) {
    0          
62 2         12 $patch = {
63             is_ok => 1,
64             has_todo => 1,
65             is_actual_ok => 0,
66             directive => 'TODO',
67             explanation => $explanation,
68             };
69             } elsif ($explanation = $metapatch->{SKIP}) {
70 0         0 $patch = {
71             is_ok => 1,
72             has_skip => 1,
73             is_actual_ok => 0,
74             directive => 'SKIP',
75             explanation => $explanation,
76             };
77             }
78 2         6 return $patch;
79             }
80              
81             sub _patch_dom_dpath {
82 5     5   15 my ($dom_ref, $waiver, $path) = @_;
83              
84 5         11 my $patch;
85 5 100       20 if (exists $waiver->{metapatch}) {
86 2         10 $patch = _meta_patch($waiver->{metapatch});
87             } else {
88 3         9 $patch = $waiver->{patch};
89             }
90 5         18 my $comment = $waiver->{comment};
91 5         31 my @points = dpathr($path)->match($$dom_ref);
92 5         51329 foreach my $p (@points) {
93 0           $$p->{$_} = $patch->{$_} foreach keys %$patch;
94             }
95             }
96              
97             1;
98              
99             =pod
100              
101             =encoding utf-8
102              
103             =head1 NAME
104              
105             TAP::DOM::Waivers - Patching TAP::DOM, usually for test waivers
106              
107             =head1 SYNOPSIS
108              
109             use TAP::DOM;
110             use TAP::DOM::Waivers 'waiver';
111            
112             # get TAP
113             my $dom = TAP::DOM->new( tap => "somefile.tap" );
114            
115             # ,--------------------------------------------------------------------.
116             # | Define exceptions and how to modify test results.
117             # |
118             # | (1) Most powerful but most complex way:
119             # | - use DPath matching and finegrained patching
120             # |
121              
122             $waivers = [
123             {
124             # a description of what the waiver is trying to achieve
125             comment => "Force all IPv6 stuff to true",
126            
127             # a DPath that matches the records to patch:
128             match_dpath => [ "//lines//description[value =~ 'IPv6']/.." ],
129              
130             # apply changes to the matched records,
131             # here a TODO with an explanation:
132             patch => {
133             is_ok => 1,
134             has_todo => 1,
135             is_actual_ok => 0,
136             explanation => 'waiver for context xyz',
137             directive => 'TODO',
138             },
139             },
140             ];
141            
142             # |
143             # | (2) Simpler approach:
144             # |
145             # | - instead of the "patch" key above you can use "metapatches"
146             # | for Common use-cases, like #TODO or #SKIP
147             # |
148              
149             $waivers = [
150             {
151             comment => "Force all IPv6 stuff to true",
152             match_dpath => [ "//lines//description[value =~ 'IPv6']/.." ],
153             metapatch => { TODO => 'waiver for context xyz' },
154             },
155             ];
156              
157             # |
158             # | (3) Even simpler:
159             # | - also provide the description as regex
160             # |
161              
162             $waivers = [
163             {
164             comment => "Force all IPv6 stuff to true",
165             match_description => [ "IPv6" ],
166             metapatch => { TODO => 'waiver for context xyz' },
167             },
168             ];
169             #
170             # |
171             # `--------------------------------------------------------------------'
172              
173             # the actual DOM patching
174             my $patched_tap_dom = waiver($dom, $waivers);
175            
176             # do something with patched DOM
177             use Data::Dumper;
178             print Dumper($patched_tap_dom);
179            
180             # the original DOM can also be patched directly without cloning
181             waiver($dom, $waivers, { no_clone => 1 });
182             print Dumper(dom);
183            
184             # convert back to TAP from patched DOM
185             print $patched_tap_dom->to_tap;
186             print dom->to_tap;
187              
188             =head1 NAME
189              
190             TAP::DOM::Waivers - Exceptions (waivers) for TAP::DOM-like data
191              
192             =head1 ABOUT
193              
194             =head2 Achieve?
195              
196             Test I are exemptions to actual test results.
197              
198             This module lets you ignore known issues you don't want to care about,
199             usually by grouping them for a certain context.
200              
201             =head2 Example:
202              
203             A software project might not run with IPv6 enabled but you want to see
204             a big SUCCESS or NO SUCCESS in an IPv4-only context, without being
205             disturbed by irrelevant IPv6 tests, for now.
206              
207             Statically marking the problematic tests with C<#TODO> would require
208             to change that back and forth everytime. Dynamically marking those
209             tests depending on the runtime environment does not help when another
210             engineer actually works on fixing those IPV6 problems in the same
211             environment.
212              
213             The solution is to create a I which patches the IPv6 issues
214             away in the results B you actually ran the tests, for later
215             evaluation.
216              
217             =head2 Prove plugin
218              
219             See also L
220             for a way to utilze this module with B (not yet working?).
221              
222             =head1 Waiver specification
223              
224             =head2 How to match what to patch
225              
226             This module can patch TAP-DOMs (and similar data structures, see
227             below) by certain criteria. The primary and most powerful way is via
228             Data::DPath paths, as it allows to match fuzzily against continuously
229             changing TAP from evolving test suites.
230              
231             I use this with a big TAP database where I activate waivers as a layer
232             on top of TAP::DOM based evaluation. There the TAP-DOMs are just part
233             of a even bigger data structure, but the DPath matching still applies
234             there.
235              
236             =head3 B => [ @array_of_dpaths ]
237              
238             This provides a set of dpaths that are each tried to match. The DPaths
239             should point to a single entry in TAP-DOM - that's why the examples
240             above go down into an entry to match conditions (like the
241             description), and then go up one level to point to the whole entry.
242              
243             =head3 B => [ @array_of_regexes ]
244              
245             This is a high level frontend to I. The regexes are
246             internally embedded in dpaths which are then used to match. The
247             converted internal dpaths will match fuzzy for a typical TAP-DOM
248             structure, in particular:
249              
250             "//lines//description[value =~ qr/$description/]/..";
251              
252             Please note that this doesn't allow to specify complex conditions like
253             the combination of a description and a particular test success
254             (e.g. only the "not ok" tests with a particular description, see
255             examples in I).
256              
257             In combination with the also just canonically working I
258             (see below) it might create a slightly different TAP-DOM than you
259             expect, e.g. when you match and modify tests as '#TODO' that did not
260             even fail, but the metapatch marks them as 'not ok #TODO'. So the
261             original actual success is lost.
262              
263             It might be still "quite ok" and worth the less complexity but
264             consider using I for better control.
265              
266             =head2 Patch specs
267              
268             =head3 B => { %patch_spec }
269              
270             A hash entry key B contains single keys that overwrite
271             respective fields of a TAP-DOM entry.
272              
273             This allows finegrained control but it's somewhat difficult if you are
274             not familiar with the details of how a TAP situation looks like in a
275             TAP-DOM.
276              
277             Therefore you can describe more abstract use-cases with
278             I.
279              
280             =head3 B => { %patch_spec }
281              
282             A key B declares a common use-case. Inside a metapatch the
283             key describes the use case (like 'TODO'), and the value is the most
284             significant thingie (eg. the explanation).
285              
286             Currently these metapatches are supported:
287              
288             =over 4
289              
290             =item * B => I
291              
292             =item * B => I
293              
294             =back
295              
296             When such a metapatch is found it is converted internally into an
297             equivalent detailed patch, as described above.
298              
299             =head2 Comments
300              
301             The key B is not strictly needed. It will help once there is
302             some logging.
303              
304             =head1 Back from DOM to TAP
305              
306             Usually you regenerate a semantically comparable TAP document from the
307             DOM via L.
308              
309             =head1 API
310              
311             =head2 waive ($dom, $waivers, $options)
312              
313             This applies a set of waivers to a TAP-DOM.
314              
315             The C is usually a real L but don't have
316             to. It is explicitely allowed to provide similar data structures,
317             e.g., bigger structures that only contain TAP-DOMs in sub
318             structures. It's your responsibility to provide something meaningful.
319              
320             If you match with C you have control whether to use the
321             surrounding data structures to match or not.
322              
323             If a waiver does not match, nothing happens.
324              
325             =head1 AUTHOR
326              
327             Steffen Schwigon, C<< >>
328              
329             =head1 BUGS
330              
331             Please report any bugs or feature requests to C
332             rt.cpan.org>, or through the web interface at
333             L. I
334             will be notified, and then you'll automatically be notified of
335             progress on your bug as I make changes.
336              
337             =head1 SUPPORT
338              
339             You can find documentation for this module with the perldoc command.
340              
341             perldoc TAP::DOM::Waivers
342              
343             You can also look for information at:
344              
345             =over 4
346              
347             =item * RT: CPAN's request tracker
348              
349             L
350              
351             =item * AnnoCPAN: Annotated CPAN documentation
352              
353             L
354              
355             =item * CPAN Ratings
356              
357             L
358              
359             =item * Search CPAN
360              
361             L
362              
363             =back
364              
365             =head1 LICENSE AND COPYRIGHT
366              
367             Copyright 2011 Steffen Schwigon.
368              
369             This program is free software; you can redistribute it and/or modify
370             it under the terms of either: the GNU General Public License as
371             published by the Free Software Foundation; or the Artistic License.
372              
373             See http://dev.perl.org/licenses/ for more information.
374              
375             =head1 AUTHOR
376              
377             Steffen Schwigon
378              
379             =head1 COPYRIGHT AND LICENSE
380              
381             This software is copyright (c) 2014 by Steffen Schwigon.
382              
383             This is free software; you can redistribute it and/or modify it under
384             the same terms as the Perl 5 programming language system itself.
385              
386             =cut
387              
388             __END__