File Coverage

lib/Dist/Zilla/Util/Test/KENTNL/dztest.pm
Criterion Covered Total %
statement 202 227 88.9
branch 39 68 57.3
condition 4 12 33.3
subroutine 48 49 97.9
pod 15 16 93.7
total 308 372 82.8


line stmt bran cond sub pod time code
1 3     3   586 use 5.006;
  3         6  
  3         82  
2 3     3   10 use strict;
  3         4  
  3         65  
3 3     3   10 use warnings;
  3         7  
  3         147  
4              
5             package Dist::Zilla::Util::Test::KENTNL::dztest;
6              
7             our $VERSION = '1.005012';
8              
9             # ABSTRACT: Shared dist testing logic for easy dzil things
10              
11             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
12              
13 3     3   10 use Carp qw( croak );
  3         3  
  3         132  
14 3     3   1251 use Moo qw( has );
  3         30294  
  3         12  
15 3     3   8909 use Test::Fatal qw( exception );
  3         2716  
  3         140  
16 3     3   16 use Test::More 0.96 qw( ); # subtest
  3         50  
  3         49  
17 3     3   604 use Path::Tiny qw(path);
  3         9217  
  3         238  
18 3     3   387 use Dist::Zilla::Util;
  3         17311  
  3         68  
19 3     3   1238 use Dist::Zilla::App::Tester qw( test_dzil );
  3         114212  
  3         19  
20 3     3   461 use Module::Runtime qw();
  3         7  
  3         52  
21              
22             ## no critic (ValuesAndExpressions::ProhibitConstantPragma,ErrorHandling::RequireCheckingReturnValueOfEval,Subroutines::ProhibitSubroutinePrototypes)
23 3     3   1075 use recommended 'Test::Differences', 'Test::TempDir::Tiny';
  3         2886  
  3         15  
24 3     3   1420 use Data::DPath qw( dpath );
  3         181527  
  3         18  
25             ## use critic
26              
27              
28              
29              
30              
31              
32              
33              
34              
35              
36              
37              
38              
39             sub add_file {
40 2     2 1 1606 my ( $self, $path, $content ) = @_;
41 2         7 my $target = $self->tempdir->child( _file_list($path) );
42 2         53 $target->parent->mkpath;
43 2         222 $target->spew_raw($content);
44 2         673 $self->files->{ $target->relative( $self->tempdir ) } = $target;
45 2         221 return;
46             }
47              
48              
49              
50              
51              
52              
53              
54              
55              
56             sub _subtest_build_ok {
57 1     1   1 my ($self) = @_;
58              
59 1         1 for my $file ( values %{ $self->files } ) {
  1         16  
60 1 50 33     10 next if -e $file and not -d $file;
61 0         0 return $self->tb->BAIL_OUT("expected file $file failed to add to tempdir");
62             }
63 1         38 $self->note_tempdir_files;
64              
65 1         1 my $exception;
66 1         21 $self->tb->is_eq( $exception = $self->safe_configure, undef, 'Can load config' );
67 1 50       451 $self->tb->diag($exception) if $exception;
68              
69 1         29 $self->tb->is_eq( $exception = $self->safe_build, undef, 'Can build' );
70 1 50       502 $self->tb->diag($exception) if $exception;
71              
72 1         4 $self->note_builddir_files;
73 1         3 return;
74             }
75              
76             sub build_ok {
77 1     1 1 4 my ($self) = @_;
78             return $self->tb->subtest(
79             'Configure and build' => sub {
80 1     1   415 $self->tb->plan( tests => 2 );
81 1         58 return $self->_subtest_build_ok;
82             },
83 1         17 );
84             }
85              
86              
87              
88              
89              
90              
91              
92              
93              
94              
95              
96              
97              
98             sub _subtest_prereqs_deeply {
99 1     1   2 my ( $self, $prereqs ) = @_;
100 1         5 my $meta = $self->distmeta;
101 1         648 $self->tb->ok( defined $meta, 'distmeta defined' );
102 1         265 $self->tb->note( $self->tb->explain( $meta->{prereqs} ) );
103              
104 1 50       250 if ( recommended->has('Test::Differences') ) {
105 1         6795 Test::Differences::eq_or_diff( $meta->{prereqs}, $prereqs, 'Prereqs match expected set' );
106             }
107             else {
108             ## no critic (Subroutines::ProhibitCallsToUnexportedSubs)
109 0         0 Test::More::is_deeply( $meta->{prereqs}, $prereqs, 'Prereqs match expected set' );
110             }
111 1         815 return;
112             }
113              
114             sub prereqs_deeply {
115 1     1 1 496 my ( $self, $prereqs ) = @_;
116             return $self->tb->subtest(
117             'distmeta prereqs comparison' => sub {
118 1     1   426 $self->tb->plan( tests => 2 );
119 1         137 $self->_subtest_prereqs_deeply($prereqs);
120             },
121 1         19 );
122             }
123              
124              
125              
126              
127              
128              
129              
130              
131              
132              
133              
134              
135             sub _test_has_message {
136 3     3   6 my ( $self, $log, $regex, $reason ) = @_;
137 3         2 my $i = 0;
138 3         3 for my $item ( @{$log} ) {
  3         6  
139 4 100       36 if ( $item =~ $regex ) {
140 3         60 $self->tb->note( qq[item $i: ], $self->tb->explain($item) );
141 3         411 $self->tb->ok( 1, "log message $i matched $regex$reason" );
142 3         748 return 1;
143             }
144 1         2 $i++;
145             }
146 0         0 $self->tb->ok( undef, "No log messages matched $regex$reason" );
147 0         0 return;
148             }
149              
150             sub _subtest_has_messages {
151 1     1   3 my ( $self, $map ) = @_;
152 1         22 my $log = $self->builder->log_messages;
153 1         90 $self->tb->ok( scalar @{$log}, ' has messages' );
  1         7  
154 1         276 my $need_diag;
155 1         2 for my $entry ( @{$map} ) {
  1         4  
156 2         3 my ( $regex, $reason ) = @{$entry};
  2         4  
157 2 50       7 $reason = ": $reason" if $reason;
158 2 50       5 $reason = q[] unless $reason;
159 2 50       6 $need_diag = 1 unless $self->_test_has_message( $log, $regex, $reason );
160             }
161 1 50       3 if ($need_diag) {
162 0         0 $self->tb->diag( $self->tb->explain($log) );
163 0         0 return;
164             }
165 1         3 return 1;
166             }
167              
168             sub has_messages {
169 1     1 1 422 my $nargs = ( my ( $self, $label, $map ) = @_ );
170              
171 1 50       4 croak 'Invalid number of arguments ( < 2 )' if 1 == $nargs;
172 1 50       2 croak 'Invalid number of arguments ( > 3 )' if $nargs > 3;
173              
174 1 50       3 if ( 2 == $nargs ) {
175 0         0 $map = $label;
176 0         0 $label = 'log messages check';
177             }
178             return $self->tb->subtest(
179             $label => sub {
180 1     1   506 $self->tb->plan( tests => 1 + scalar @{$map} );
  1         8  
181 1         131 $self->_subtest_has_messages($map);
182             },
183 1         21 );
184             }
185              
186              
187              
188              
189              
190              
191              
192              
193              
194              
195              
196              
197              
198              
199              
200              
201             sub _subtest_meta_path_deeply {
202 2     2   3 my ( $self, $expression, $expected ) = @_;
203 2 50       6 if ( not 'ARRAY' eq ref $expected ) {
204 0         0 $self->tb->diag(<<'EOF');
205             WARNING: Author appears to have forgotten to wrap $expected with [], and this may cause a bug.
206             EOF
207 0         0 $expected = [$expected];
208             }
209 2         8 my (@results) = dpath($expression)->match( $self->builder->distmeta );
210 2         682 $self->tb->ok( @results > 0, "distmeta matched expression $expression" );
211 2         553 $self->tb->note( $self->tb->explain( \@results ) );
212 2 50       614 if ( recommended->has('Test::Differences') ) {
213 2         24 Test::Differences::eq_or_diff( \@results, $expected, 'distmeta matched expectations' );
214             }
215             else {
216             ## no critic (Subroutines::ProhibitCallsToUnexportedSubs)
217 0         0 Test::More::is_deeply( \@results, $expected, 'distmeta matched expectations' );
218             }
219 2         1459 return;
220             }
221              
222             sub meta_path_deeply {
223 2     2 1 790 my ( $self, $expression, $expected, $reason ) = @_;
224 2 100       6 if ( not $reason ) {
225 1         3 $reason = "distmeta at $expression matches expected";
226             }
227             return $self->tb->subtest(
228             $reason => sub {
229 2     2   827 $self->tb->plan( tests => 2 );
230 2         246 return $self->_subtest_meta_path_deeply( $expression, $expected );
231             },
232 2         40 );
233             }
234              
235              
236              
237              
238              
239              
240              
241              
242              
243              
244              
245              
246              
247             sub test_has_built_file {
248 1     1 1 408 my ( $self, $path ) = @_;
249 1 50 33     2 if ( not -e $self->_build_root or not -d $self->_build_root ) {
250 0         0 $self->tb->ok( undef, 'build root does not exist, cant have files' );
251 0         0 return;
252             }
253 1         84 my $file = $self->_build_root->child( _file_list($path) );
254 1 50 33     17 if ( defined $file and -e $file and not -d $file ) {
      33        
255 1         46 $self->tb->ok( 1, "$file exists" );
256 1         265 return $file;
257             }
258 0         0 $self->tb->ok( undef, "$file exists" );
259 0         0 return;
260             }
261              
262              
263              
264              
265              
266              
267              
268              
269              
270              
271              
272              
273              
274              
275              
276              
277              
278              
279              
280              
281              
282             sub create_plugin {
283 4     4 1 117 my $nargs = ( my ( $self, $package, $name, $args ) = @_ );
284 4 100       11 if ( 2 == $nargs ) {
285 1         1 $name = $package;
286 1         2 $args = {};
287             }
288 4 100       10 if ( 3 == $nargs ) {
289 2 100       6 if ( ref $name ) {
290 1         2 $args = $name;
291 1         1 $name = $package;
292             }
293             else {
294 1         3 $args = {};
295             }
296             }
297 4         20 my $expanded = Dist::Zilla::Util->expand_config_package_name($package);
298 4         237 Module::Runtime::require_module($expanded);
299 4         264 return $expanded->new(
300             zilla => $self->configure,
301             plugin_name => $name,
302 4         26633 %{$args},
303             );
304             }
305              
306             has tb => (
307             is => ro =>,
308             lazy => 1,
309             default => sub {
310             Test::More->builder;
311             },
312             );
313             has files => (
314             is => ro =>,
315             lazy => 1,
316             default => sub { return {}; },
317             );
318              
319             has tempdir => (
320             is => ro =>,
321             lazy => 1,
322             builder => '_build_tempdir',
323             );
324              
325             sub _build_tempdir {
326 2     2   599 my ($self) = @_;
327 2         3 my $tempdir;
328 2 50       10 if ( recommended->has('Test::TempDir::Tiny') ) {
329 2         3033 $tempdir = path( Test::TempDir::Tiny::tempdir() );
330             }
331             else {
332 0         0 $tempdir = Path::Tiny->tempdir;
333             }
334 2         513 $self->tb->note("Creating fake dist in $tempdir");
335 2         527 return $tempdir;
336             }
337              
338             sub _file_list {
339 5     5   80 my ($file) = @_;
340 5 50       15 if ( 'ARRAY' eq ref $file ) {
341 0         0 return @{$file};
  0         0  
342             }
343 5         16 return ($file);
344             }
345              
346              
347              
348              
349              
350              
351              
352              
353              
354              
355              
356              
357              
358              
359              
360              
361              
362              
363             sub source_file {
364 1     1 1 5 my ( $self, $path ) = @_;
365 1         20 my $file = $self->tempdir->child( _file_list($path) );
366 1 50       26 return unless -e $file;
367 1 50       30 return if -d $file;
368 1         25 return $file;
369             }
370              
371             has builder => (
372             is => ro =>,
373             lazy => 1,
374             builder => '_build_builder',
375             handles => {
376             distmeta => 'distmeta',
377             },
378             );
379 1     1 0 2 sub build { my ($self) = @_; return $self->builder }
  1         12  
380              
381             sub _build_builder {
382 1     1   367 my ($self) = @_;
383 1         16 my $b = $self->configure;
384 1         14 $b->build;
385 1         24936 return $b;
386             }
387              
388              
389              
390              
391              
392              
393              
394              
395              
396              
397              
398             sub safe_build {
399 1     1 1 12 my ($self) = @_;
400 1     1   7 return exception { $self->build };
  1         46  
401             }
402              
403              
404              
405              
406              
407              
408              
409              
410              
411             has configure => (
412             is => ro =>,
413             lazy => 1,
414             builder => '_build_configure',
415             );
416              
417             sub _build_configure {
418 2     2   720 my ($self) = @_;
419 2         13 require Dist::Zilla::Tester;
420 2         16 my $b = Dist::Zilla::Tester->builder()->from_config( { dist_root => q[] . $self->tempdir } );
421 2         1086036 return $b;
422             }
423              
424              
425              
426              
427              
428              
429              
430              
431              
432             sub safe_configure {
433 1     1 1 6 my ($self) = @_;
434 1     1   11 return exception { $self->configure };
  1         44  
435             }
436              
437             sub _build_root {
438 6     6   149 my ($self) = @_;
439 6         109 return path( $self->builder->tempdir )->child('build');
440             }
441              
442             sub _note_path_files {
443 2     2   74 my ( $self, $root_path ) = @_;
444 2 50       5 if ( not -e $root_path ) {
445 0         0 $self->tb->diag("$root_path does not exist, not noting its contents");
446             }
447 2         28 my $i = path($root_path)->iterator( { recurse => 1 } );
448 2         57 while ( my $path = $i->() ) {
449 2 50       251 next if -d $path;
450 2         71 $self->tb->note( "$path : " . $path->stat->size . q[ ] . $path->stat->mode );
451             }
452 2         714 return;
453             }
454              
455              
456              
457              
458              
459              
460              
461              
462              
463             sub built_file {
464 1     1 1 295 my ( $self, $path ) = @_;
465 1         2 my $root = $self->_build_root;
466 1 50       76 return unless -e $root;
467 1 50       18 return unless -d $root;
468 1         11 my $file = $root->child( _file_list($path) );
469 1 50       17 return unless -e $file;
470 1 50       13 return if -d $file;
471 1         12 return $file;
472             }
473              
474              
475              
476              
477              
478              
479              
480             sub note_tempdir_files {
481 1     1 1 1 my ($self) = @_;
482 1         18 return $self->_note_path_files( $self->tempdir );
483             }
484              
485              
486              
487              
488              
489              
490              
491             sub note_builddir_files {
492 1     1 1 1 my ($self) = @_;
493 1 50       3 if ( -e $self->_build_root ) {
494 1         127 return $self->_note_path_files( $self->_build_root );
495             }
496 0         0 $self->tb->note('No Build Root, probably due to no file gatherers');
497 0         0 return;
498             }
499              
500             sub _subtest_has_message {
501 1     1   3 my ( $self, $regex, $reason ) = @_;
502 1         20 my $log = $self->builder->log_messages;
503 1         78 $self->tb->ok( scalar @{$log}, ' has messages' );
  1         49  
504 1 50       252 return 1 if $self->_test_has_message( $log, $regex, $reason );
505 0         0 $self->tb->diag( $self->tb->explain($log) );
506 0         0 return;
507             }
508              
509              
510              
511              
512              
513              
514              
515              
516              
517             sub has_message {
518 1     1 1 590 my ( $self, $regex, $reason ) = @_;
519 1 50       4 $reason = ": $reason" if $reason;
520 1 50       8 $reason = q[] unless $reason;
521             return $self->tb->subtest(
522             "log message check$reason" => sub {
523 1     1   401 $self->tb->plan( tests => 2 );
524 1         125 $self->_subtest_has_message( $regex, $reason );
525             },
526 1         26 );
527             }
528              
529              
530              
531              
532              
533              
534              
535              
536              
537              
538              
539              
540              
541              
542              
543             sub run_command {
544 0     0 1   my ( $self, $argv, $arg ) = @_;
545 0           return test_dzil( $self->tempdir, $argv, $arg );
546             }
547              
548 3     3   5501 no Moo;
  3         6  
  3         18  
549              
550             1;
551              
552             __END__
553              
554             =pod
555              
556             =encoding UTF-8
557              
558             =head1 NAME
559              
560             Dist::Zilla::Util::Test::KENTNL::dztest - Shared dist testing logic for easy dzil things
561              
562             =head1 VERSION
563              
564             version 1.005012
565              
566             =head1 SYNOPSIS
567              
568             use Test::More;
569             use Test::DZil qw( simple_ini );
570             use Dist::Zilla::Util::Test::KENTNL qw( dztest );
571              
572             my $test = dztest;
573              
574             ## utility method.
575             $test->add_file( 'dist.ini', simple_ini( .... ));
576              
577             ## build the dist
578             # 1x subtest
579             $test->build_ok;
580              
581             ## assert prereqs are identical to the hash
582             ## extracting them from distmeta
583             # 1x subtest
584             $test->prereqs_deeply( { } );
585              
586             ## Test for specific log messages by regex
587             # 1x subtest
588             # - tests there are messages
589             # - each regex must match a message
590             my @list = (
591             [ $regex, $indepdent_reason ],
592             [ $regex ],
593             );
594             $test->has_messages( $reason, \@list );
595              
596             ## Test for any deep structure addressed
597             ## By a Data::DPath expression
598             # 1x subtest
599             # - asserts the expression returns a result
600             # - compares the structure against the expected one.
601             $test->meta_path_deeply(
602             '/author/*/[1]',
603             [ 'E. Xavier Ample <example@example.org>' ],
604             'The 1st author is the example author emitted by simple_ini'
605             );
606              
607             ## Test for a file existing on the build side
608             ## and return it if it exists.
609             my $file = $test->test_has_built_file('dist.ini');
610              
611             =head1 METHODS
612              
613             =head2 C<add_file>
614              
615             Add a file to the scratch directory to be built.
616              
617             # ->add_file( $path, $string );
618             # ->add_file( \@path, $string );
619             $test->add_file('dist.ini', simple_ini() );
620             $test->add_file('lib/Foo.pm', $content );
621             $test->add_file([ 'lib','Foo.pm' ], $content );
622              
623             =head2 C<build_ok>
624              
625             Build the dist safely, and report C<ok> if the dist builds C<ok>, spewing file listings via C<note>
626              
627             C<BAIL_OUT> is triggered if any of C<add_file> don't arrive in the intended location.
628              
629             =head2 C<prereqs_deeply>
630              
631             Demand C<distmeta> C<prereqs> exactly match those specified.
632              
633             $test->prereqs_deeply( { hash } );
634              
635             This is just a more memorable version of
636              
637             $test->meta_path_deeply('/prereqs/', { });
638              
639             =head2 C<has_messages>
640              
641             Test that there are messages, and all the given rules match messages.
642              
643             $test->has_messages( 'Some descriptor', [
644             [ $regex, $description ],
645             [ $regex, $description ],
646             ]);
647              
648             =head2 C<meta_path_deeply>
649              
650             $test->meta_path_deeply( $expression, $expected_data, $reason );
651              
652             Uses C<$expression> as a L<< C<Data::DPath>|Data::DPath >> expression to pick a I<LIST> of nodes
653             from C<distmeta>, and compare that I<LIST> vs C<$expected_data>
654              
655             # Matches only the first author.
656             $test->meta_path_deeply('/author/*/[1]', ['SomeAuthorName <wadef@wath>'], $reason );
657              
658             # Matches all authors
659             $test->meta_path_deeply('/author/*/*', ['SomeAuthorName <wadef@wath>','Author2', ..], $reason );
660              
661             =head2 C<test_has_built_file>
662              
663             Test ( as in, C<Test::More::ok> ) that a file exists in the C<dzil> build output directory.
664              
665             Also returns it if it exists.
666              
667             $test->test_has_built_file('dist.ini'); # ok/fail
668              
669             my $object = test->test_has_built_file('dist.ini'); # ok/fail + return
670              
671             =head2 C<create_plugin>
672              
673             Create an instance of the named plugin and return it.
674              
675             my $t = dztest();
676             $t->add_file('dist.ini', simple_ini( ... ));
677             my $plugin = $t->create_plugin('GatherDir' => { ignore_dotfiles => 1 });
678             # poke at $plugin here
679              
680             Note: This lets you test plugins outside the requirement of inter-operating
681             with C<dzil> phases, but has the downside of not interacting with C<dzil> phases,
682             or even being I<*seen*> by C<dzil> phases.
683              
684             But this is OK if you want to directly test a modules interface instead of doing
685             it through the proxy of C<dzil>
686              
687             You can also subsequently create many such objects without requiring a C<dzil build> penalty.
688              
689             =head2 C<source_file>
690              
691             Re-fetch content added with C<add_file>.
692              
693             You probably want C<built_file>.
694              
695             $test->source_file( $path );
696             $test->source_file( \@path );
697              
698             Returns C<undef> if the file does not exist.
699              
700             if ( my $content = $test->source_file('dist.ini') ) {
701             print $content->slurp_raw;
702             }
703              
704             =head2 C<safe_build>
705              
706             Ensure the distribution is built safely, returns exceptions or C<undef>.
707              
708             if ( $test->safe_build ) {
709             say "Failed build";
710             }
711              
712             =head2 C<safe_configure>
713              
714             Construct the internal builder object safely. Returns exceptions or C<undef>.
715              
716             if( $test->configure ) { say "configure failed" }
717              
718             =head2 C<built_file>
719              
720             Returns the named file if it exists in the build, C<undef> otherwise.
721              
722             my $file = $test->built_file('dist.ini');
723              
724             =head2 C<note_tempdir_files>
725              
726             Recursively walk C<tempdir> and note its contents.
727              
728             =head2 C<note_builddir_files>
729              
730             Recursively walk C<builddir>(output) and note its contents.
731              
732             =head2 C<has_message>
733              
734             Assert there are messages, and this single message exists:
735              
736             $test->has_message( $regex, $description );
737              
738             =head2 C<run_command>
739              
740             Execute a Dist::Zilla command in the constructed scratch directory.
741              
742             $test->run_command(['build','foo']);
743              
744             The syntax is technically:
745              
746             $test->run_command( $argv, $arg );
747              
748             But I'm yet to work out the meaning of the latter.
749              
750             =head1 ATTRIBUTES
751              
752             =head2 C<configure>
753              
754             Construct the internal builder object.
755              
756             $test->configure;
757              
758             =for Pod::Coverage build
759              
760             =head1 AUTHOR
761              
762             Kent Fredric <kentnl@cpan.org>
763              
764             =head1 COPYRIGHT AND LICENSE
765              
766             This software is copyright (c) 2015 by Kent Fredric <kentnl@cpan.org>.
767              
768             This is free software; you can redistribute it and/or modify it under
769             the same terms as the Perl 5 programming language system itself.
770              
771             =cut