File Coverage

blib/lib/Dist/Zilla/Tester/DieHard.pm
Criterion Covered Total %
statement 34 34 100.0
branch n/a
condition n/a
subroutine 12 12 100.0
pod 0 1 0.0
total 46 47 97.8


line stmt bran cond sub pod time code
1             # ---------------------------------------------------------------------- copyright and license ---
2             #
3             # file: lib/Dist/Zilla/Tester/DieHard.pm
4             #
5             # Copyright © 2015, 2016 Van de Bugger.
6             #
7             # This file is part of perl-Dist-Zilla-Tester-DieHard.
8             #
9             # perl-Dist-Zilla-Tester-DieHard is free software: you can redistribute it and/or modify it under
10             # the terms of the GNU General Public License as published by the Free Software Foundation,
11             # either version 3 of the License, or (at your option) any later version.
12             #
13             # perl-Dist-Zilla-Tester-DieHard is distributed in the hope that it will be useful, but WITHOUT
14             # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15             # PURPOSE. See the GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License along with
18             # perl-Dist-Zilla-Tester-DieHard. If not, see <http://www.gnu.org/licenses/>.
19             #
20             # ---------------------------------------------------------------------- copyright and license ---
21              
22             #tt
23             #tt
24             #tt
25             #tt
26             #tt
27             #tt
28             #tt
29             #tt
30              
31              
32             #pod =for test_synopsis my ( %args, $expected_exception, $expected_messages );
33             #pod
34             #pod =head1 SYNOPSIS
35             #pod
36             #pod Use C<Dist::Zilla::Tester::DieHard> instead of C<Dist::Zilla::Tester>:
37             #pod
38             #pod use Dist::Zilla::Tester::DieHard; # instead of Dist::Zilla::Tester
39             #pod use Test::Deep qw{ cmp_deeply };
40             #pod use Test::Fatal;
41             #pod use Test::More;
42             #pod
43             #pod my $tzil = Builder->from_config( \%args );
44             #pod my $ex = exception { $tzil->build(); };
45             #pod is( $ex, $expected_exception, 'check status' );
46             #pod cmd_deeply( $tzil->log_messages, $expected_messages, 'check log messages' );
47             #pod
48             #pod =head1 DESCRIPTION
49             #pod
50             #pod C<Dist::Zilla::Tester::DieHard> (or, for brevity just C<DieHard>) extends C<Dist::Zilla::Tester>.
51             #pod If C<Dist::Zilla> dies in construction, C<DieHard> catches the exception, saves the exception and
52             #pod C<Dist::Zilla> logger, and returns a "survivor" object.
53             #pod
54             #pod The returned survivor will fail in C<build> (or C<release>) method: it just rethrows the saved
55             #pod exception. However, such "delayed death" saves log messages for analysis:
56             #pod
57             #pod my $tzil = Builder->from_config( … );
58             #pod # ^ Construction never fails,
59             #pod # it always returns an object,
60             #pod # either builder or survivor.
61             #pod my $ex = exception { $tzil->build(); }; # or $tzil->release();
62             #pod # ^ Builder does build,
63             #pod # survivor rethrows the saved exception.
64             #pod is( $ex, $expected_exception, 'check status' );
65             #pod cmd_deeply( $tzil->log_messages, $expected_messages, 'check log messages' );
66             #pod # ^ In *any* case we can check log messages.
67             #pod
68             #pod =head2 C<Survivor>
69             #pod
70             #pod C<Survivor> is shortened name of real class. Full class name is
71             #pod C<Dist::Zilla::Tester::DieHard::Survivor>.
72             #pod
73             #pod Following methods can be called on C<Survivor> object: C<clear_log_events>, C<log_events>,
74             #pod C<log_messages>.
75             #pod
76             #pod C<build>, C<release>
77             #pod methods rethrow the saved exception.
78             #pod
79             #pod =note Completeness
80             #pod
81             #pod Regular C<Dist::Zilla::Tester> (as of v5.039) is not documented, so and I have to study its sources
82             #pod to find out features it provides.
83             #pod
84             #pod I have implemented only part of C<Dist::Zilla::Tester> features, shown in L</"SYNOPSIS"> and
85             #pod L</"DESCRIPTION">. C<Minter> is not (yet?) implemented — I do not need it (yet?). Probably there
86             #pod are other not (yet?) implemented features I am not aware of.
87             #pod
88             #pod =note Implementation Detail
89             #pod
90             #pod Implementation is simpler if C<Survivor> saves not logger, but entire chrome (logger is a part of
91             #pod chrome). In such a case C<Survivor> can consume C<Dist::Zilla::Tester::_Role> and get bunch of
92             #pod methods "for free".
93             #pod
94             #pod =note C<most_recent_log_events> Function
95             #pod
96             #pod C<Dist::Zilla::Tester> 5.040 introduced C<most_recent_log_events> function which can be used to
97             #pod retrieve log events even if builder construction failed. However:
98             #pod
99             #pod =for :list
100             #pod 1. This module was implemented and released before C<DIst::Zilla> 5.040.
101             #pod 2. C<most_recent_log_events> is not documented.
102             #pod 3. Using C<most_recent_log_events> requires revisiting existing test code, while C<DieHard> does
103             #pod not.
104             #pod
105             #pod =cut
106              
107             # --------------------------------------------------------------------------------------------------
108              
109             package Dist::Zilla::Tester::DieHard;
110              
111 3     3   7898169 use Moose;
  3         11  
  3         122  
112              
113             # ABSTRACT: Die hard Dist::Zilla, but save the messages
114             our $VERSION = 'v0.6.3_02'; # TRIAL VERSION
115             our $CLASS = __PACKAGE__;
116              
117             extends 'Dist::Zilla::Tester';
118              
119             # Mimic the `Dist::Zilla::Tester` export.
120             use Sub::Exporter -setup => {
121             exports => [
122 6         19378 Builder => sub { $_[ 0 ]->can( 'builder' ) },
123 3         61 ],
124             groups => [ default => [ qw{ Builder } ] ],
125 3     3   23597 };
  3         12  
126              
127             #pod =for Pod::Coverage builder
128             #pod
129             #pod =cut
130              
131             sub builder {
132 9     9 0 2331142 return $CLASS . '::Builder';
133             };
134              
135 3     3   1922 no Moose;
  3         5  
  3         23  
136              
137             $CLASS->meta->make_immutable;
138              
139             # --------------------------------------------------------------------------------------------------
140              
141             {
142              
143             package Dist::Zilla::Tester::DieHard::Builder; ## no critic ( ProhibitMultiplePackages )
144              
145 3     3   829 use Moose;
  3         6  
  3         19  
146 3     3   21957 use namespace::autoclean;
  3         8  
  3         44  
147              
148             ## no critic ( ProhibitReusedNames )
149             our $VERSION = 'v0.6.3_02'; # TRIAL VERSION
150             our $CLASS = __PACKAGE__;
151             ## critic ( ProhibitReusedNames )
152              
153             extends join( '::', qw{ Dist Zilla Tester _Builder } );
154             # ^ Hide `Dist::Zilla::Tester::_Builder` from `AutoPrereqs`. If `…::_Builder` is added to
155             # prerequisities, `cpanm` starts downloading, testing and installing `Dist::Zilla`
156             # ignoring the fact that `Dist::Zilla` is already installed.
157              
158 3     3   551 use Dist::Zilla::Chrome::Test;
  3         8  
  3         96  
159 3     3   28 use Try::Tiny;
  3         6  
  3         1156  
160              
161             our $Chrome; ## no critic ( ProhibitPackageVars )
162              
163             around from_config => sub {
164             my ( $orig, $self, @args ) = @_;
165             local $Chrome; ## no critic ( RequireInitializationForLocalVars )
166             my $builder;
167             try {
168             # Try to create original `Dist::Zilla::Tester::_Builder` first.
169             $builder = $self->$orig( @args );
170             } catch {
171             my $ex = $_;
172             # If an exception occurs before builder construction (i. e. in
173             # `Dist::Zilla::Tester::_Builder`'s `around from_config`), `$Chrome` will be undefined.
174             # Let's crate a new chrome object.
175             if ( not defined( $Chrome ) ) {
176             $Chrome = Dist::Zilla::Chrome::Test->new();
177             };
178             # If creation failed due to exception, create stub object instead.
179             $builder = Dist::Zilla::Tester::DieHard::Survivor->new(
180             exception => $ex, # Survivor object saves exception
181             chrome => $Chrome, # and chrome.
182             );
183             };
184             return $builder;
185             };
186              
187             # Saving builder's chrome is not trivial. `from_config` is a class method. Before
188             # `$self->$orig()` call the builder does not exist yet, after call the builder does not exist
189             # already. I need to catch the moment when the builder is already born but not yet died.
190             # `BUILDARGS` method is called just before builder creation, so I can steal chrome from builder
191             # constructor arguments.
192             #
193             # To pass information (chrome reference) between object method `BUILDARGS` and class method
194             # `from_config` I have to use global (class) variable.
195              
196             around BUILDARGS => sub {
197             my ( $orig, $self, @args ) = @_;
198             $Chrome = $args[ 0 ]->{ chrome };
199             return $self->$orig( @args );
200             };
201              
202             $CLASS->meta->make_immutable;
203              
204             };
205              
206             # --------------------------------------------------------------------------------------------------
207              
208             {
209              
210             # This is "survivor", which substitutes builder when its creation fails.
211              
212             package Dist::Zilla::Tester::DieHard::Survivor; ## no critic ( ProhibitMultiplePackages )
213              
214 3     3   3872 use Moose;
  3         5  
  3         17  
215 3     3   21429 use namespace::autoclean;
  3         7  
  3         17  
216              
217             with join( '::', qw{ Dist Zilla Tester _Role } ); # Hide from `AutoPrereqs`.
218              
219             ## no critic ( ProhibitReusedNames )
220             our $VERSION = 'v0.6.3_02'; # TRIAL VERSION
221             our $CLASS = __PACKAGE__;
222             ## critic ( ProhibitReusedNames )
223              
224             has chrome => (
225             isa => 'Object',
226             is => 'ro',
227             );
228              
229             has exception => ( # Survivor stores the exception killed the buider
230             is => 'ro',
231             required => 1,
232             );
233              
234             my $rethrow = sub {
235 9     9   42523 my ( $self ) = @_;
236 9         546 die $self->exception; ## no critic ( RequireCarping )
237             };
238              
239             # Survivor mimics builder to some extent. I need only few methods:
240             for my $method ( qw{ build release } ) {
241 3     3   1093 no strict 'refs'; ## no critic ( ProhibitNoStrict )
  3         7  
  3         255  
242             *{ $method } = $rethrow;
243             };
244              
245             $CLASS->meta->make_immutable;
246              
247             }
248              
249             # --------------------------------------------------------------------------------------------------
250              
251             1;
252              
253             # --------------------------------------------------------------------------------------------------
254              
255             #pod =head1 COPYRIGHT AND LICENSE
256             #pod
257             #pod Copyright (C) 2015, 2016 Van de Bugger
258             #pod
259             #pod License GPLv3+: The GNU General Public License version 3 or later
260             #pod <http://www.gnu.org/licenses/gpl-3.0.txt>.
261             #pod
262             #pod This is free software: you are free to change and redistribute it. There is
263             #pod NO WARRANTY, to the extent permitted by law.
264             #pod
265             #pod
266             #pod =cut
267              
268             # ------------------------------------------------------------------------------------------------
269             #
270             # file: doc/what.pod
271             #
272             # This file is part of perl-Dist-Zilla-Tester-DieHard.
273             #
274             # ------------------------------------------------------------------------------------------------
275              
276             #pod =encoding UTF-8
277             #pod
278             #pod =head1 WHAT?
279             #pod
280             #pod C<Dist-Zilla-Tester-DieHard> (or shortly C<DieHard>) is a C<Dist::Zilla> testing tool, it extends standard
281             #pod C<Dist::Zilla::Tester>. If C<Dist::Zilla> dies in construction, C<DieHard> survives itself and
282             #pod saves the logger to let you analyze the messages.
283             #pod
284             #pod =cut
285              
286             # end of file #
287             # ------------------------------------------------------------------------------------------------
288             #
289             # file: doc/why.pod
290             #
291             # This file is part of perl-Dist-Zilla-Tester-DieHard.
292             #
293             # ------------------------------------------------------------------------------------------------
294              
295             #pod =encoding UTF-8
296             #pod
297             #pod =head1 WHY?
298             #pod
299             #pod Usually I test my C<Dist::Zilla> plugins in such a way:
300             #pod
301             #pod ...
302             #pod use Dist::Zilla::Tester;
303             #pod use Test::Deep qw{ cmp_deeply };
304             #pod use Test::Fatal;
305             #pod use Test::More;
306             #pod
307             #pod my $tzil = Builder->from_config( ... );
308             #pod my $exception = exception { $tzil->build(); };
309             #pod if ( $expected_success ) {
310             #pod is( $exception, undef, 'status' );
311             #pod } else {
312             #pod like( $exception, qr{...}, 'status' );
313             #pod };
314             #pod cmd_deeply( $tzil->log_messages, $expected_messages, 'log messages' );
315             #pod ...
316             #pod
317             #pod The approach works well, until C<Dist::Zilla> dies in C<from_config> (e. g. if a plugin throws an
318             #pod exception in its construction).
319             #pod
320             #pod A straightforward attempt to catch exception thrown in C<from_config>:
321             #pod
322             #pod my $tzil;
323             #pod my $exception = exception { $tzil = Builder->from_config( … ); };
324             #pod if ( $expected_success ) {
325             #pod is( $exception, undef, 'status' );
326             #pod } else {
327             #pod like( $exception, qr{…}, 'status' );
328             #pod };
329             #pod
330             #pod works but… C<from_config> dies leaving C<$tzil> undefined, C<log_messages> method is called on
331             #pod undefined value definitely fails:
332             #pod
333             #pod cmd_deeply( $tzil->log_messages, $expected_messages, 'log messages' );
334             #pod # ^^^^^^^^^^^^^^^^^^^
335             #pod # Oops: $tzil undefined.
336             #pod
337             #pod C<Dist::Zilla> dies, and all the messages logged by either C<Dist::Zilla> or its plugins are buried
338             #pod with C<Dist::Zilla>.
339             #pod
340             #pod Using C<Dist::Zilla::Tester::DieHard> instead of regular C<Dist::Zilla::Tester> solves this
341             #pod problem: even if a plugin throws an exception in constructor, C<< Builder->from_config >> does not
342             #pod die but returns a "survivor" object which can be used to retrieve log messages.
343             #pod
344             #pod =cut
345              
346             # end of file #
347              
348              
349             # end of file #
350              
351             __END__
352              
353             =pod
354              
355             =encoding UTF-8
356              
357             =head1 NAME
358              
359             Dist::Zilla::Tester::DieHard - Die hard Dist::Zilla, but save the messages
360              
361             =head1 VERSION
362              
363             Version v0.6.3_02, released on 2016-11-24 20:18 UTC.
364             This is a B<trial release>.
365              
366             =head1 WHAT?
367              
368             C<Dist-Zilla-Tester-DieHard> (or shortly C<DieHard>) is a C<Dist::Zilla> testing tool, it extends standard
369             C<Dist::Zilla::Tester>. If C<Dist::Zilla> dies in construction, C<DieHard> survives itself and
370             saves the logger to let you analyze the messages.
371              
372             =for test_synopsis my ( %args, $expected_exception, $expected_messages );
373              
374             =head1 SYNOPSIS
375              
376             Use C<Dist::Zilla::Tester::DieHard> instead of C<Dist::Zilla::Tester>:
377              
378             use Dist::Zilla::Tester::DieHard; # instead of Dist::Zilla::Tester
379             use Test::Deep qw{ cmp_deeply };
380             use Test::Fatal;
381             use Test::More;
382              
383             my $tzil = Builder->from_config( \%args );
384             my $ex = exception { $tzil->build(); };
385             is( $ex, $expected_exception, 'check status' );
386             cmd_deeply( $tzil->log_messages, $expected_messages, 'check log messages' );
387              
388             =head1 DESCRIPTION
389              
390             C<Dist::Zilla::Tester::DieHard> (or, for brevity just C<DieHard>) extends C<Dist::Zilla::Tester>.
391             If C<Dist::Zilla> dies in construction, C<DieHard> catches the exception, saves the exception and
392             C<Dist::Zilla> logger, and returns a "survivor" object.
393              
394             The returned survivor will fail in C<build> (or C<release>) method: it just rethrows the saved
395             exception. However, such "delayed death" saves log messages for analysis:
396              
397             my $tzil = Builder->from_config( … );
398             # ^ Construction never fails,
399             # it always returns an object,
400             # either builder or survivor.
401             my $ex = exception { $tzil->build(); }; # or $tzil->release();
402             # ^ Builder does build,
403             # survivor rethrows the saved exception.
404             is( $ex, $expected_exception, 'check status' );
405             cmd_deeply( $tzil->log_messages, $expected_messages, 'check log messages' );
406             # ^ In *any* case we can check log messages.
407              
408             =head2 C<Survivor>
409              
410             C<Survivor> is shortened name of real class. Full class name is
411             C<Dist::Zilla::Tester::DieHard::Survivor>.
412              
413             Following methods can be called on C<Survivor> object: C<clear_log_events>, C<log_events>,
414             C<log_messages>.
415              
416             C<build>, C<release>
417             methods rethrow the saved exception.
418              
419             =head1 NOTES
420              
421             =head2 Completeness
422              
423             Regular C<Dist::Zilla::Tester> (as of v5.039) is not documented, so and I have to study its sources
424             to find out features it provides.
425              
426             I have implemented only part of C<Dist::Zilla::Tester> features, shown in L</"SYNOPSIS"> and
427             L</"DESCRIPTION">. C<Minter> is not (yet?) implemented — I do not need it (yet?). Probably there
428             are other not (yet?) implemented features I am not aware of.
429              
430             =head2 Implementation Detail
431              
432             Implementation is simpler if C<Survivor> saves not logger, but entire chrome (logger is a part of
433             chrome). In such a case C<Survivor> can consume C<Dist::Zilla::Tester::_Role> and get bunch of
434             methods "for free".
435              
436             =head2 C<most_recent_log_events> Function
437              
438             C<Dist::Zilla::Tester> 5.040 introduced C<most_recent_log_events> function which can be used to
439             retrieve log events even if builder construction failed. However:
440              
441             =over 4
442              
443             =item 1
444              
445             This module was implemented and released before C<DIst::Zilla> 5.040.
446              
447             =item 2
448              
449             C<most_recent_log_events> is not documented.
450              
451             =item 3
452              
453             Using C<most_recent_log_events> requires revisiting existing test code, while C<DieHard> does not.
454              
455             =back
456              
457             =head1 WHY?
458              
459             Usually I test my C<Dist::Zilla> plugins in such a way:
460              
461             ...
462             use Dist::Zilla::Tester;
463             use Test::Deep qw{ cmp_deeply };
464             use Test::Fatal;
465             use Test::More;
466              
467             my $tzil = Builder->from_config( ... );
468             my $exception = exception { $tzil->build(); };
469             if ( $expected_success ) {
470             is( $exception, undef, 'status' );
471             } else {
472             like( $exception, qr{...}, 'status' );
473             };
474             cmd_deeply( $tzil->log_messages, $expected_messages, 'log messages' );
475             ...
476              
477             The approach works well, until C<Dist::Zilla> dies in C<from_config> (e. g. if a plugin throws an
478             exception in its construction).
479              
480             A straightforward attempt to catch exception thrown in C<from_config>:
481              
482             my $tzil;
483             my $exception = exception { $tzil = Builder->from_config( … ); };
484             if ( $expected_success ) {
485             is( $exception, undef, 'status' );
486             } else {
487             like( $exception, qr{…}, 'status' );
488             };
489              
490             works but… C<from_config> dies leaving C<$tzil> undefined, C<log_messages> method is called on
491             undefined value definitely fails:
492              
493             cmd_deeply( $tzil->log_messages, $expected_messages, 'log messages' );
494             # ^^^^^^^^^^^^^^^^^^^
495             # Oops: $tzil undefined.
496              
497             C<Dist::Zilla> dies, and all the messages logged by either C<Dist::Zilla> or its plugins are buried
498             with C<Dist::Zilla>.
499              
500             Using C<Dist::Zilla::Tester::DieHard> instead of regular C<Dist::Zilla::Tester> solves this
501             problem: even if a plugin throws an exception in constructor, C<< Builder->from_config >> does not
502             die but returns a "survivor" object which can be used to retrieve log messages.
503              
504             =for Pod::Coverage builder
505              
506             =head1 AUTHOR
507              
508             Van de Bugger <van.de.bugger@gmail.com>
509              
510             =head1 COPYRIGHT AND LICENSE
511              
512             Copyright (C) 2015, 2016 Van de Bugger
513              
514             License GPLv3+: The GNU General Public License version 3 or later
515             <http://www.gnu.org/licenses/gpl-3.0.txt>.
516              
517             This is free software: you are free to change and redistribute it. There is
518             NO WARRANTY, to the extent permitted by law.
519              
520             =cut