File Coverage

blib/lib/Test/DZil.pm
Criterion Covered Total %
statement 74 75 98.6
branch 17 22 77.2
condition 6 7 85.7
subroutine 17 17 100.0
pod 5 5 100.0
total 119 126 94.4


line stmt bran cond sub pod time code
1             package Test::DZil 6.037;
2             # ABSTRACT: tools for testing Dist::Zilla plugins
3              
4 50     50   6504691 use Dist::Zilla::Pragmas;
  50         161  
  50         301  
5              
6 50     50   27562 use Params::Util qw(_HASH0);
  50         305696  
  50         4537  
7 50     50   25381 use JSON::MaybeXS;
  50         371336  
  50         3956  
8 50     50   365 use Scalar::Util qw(blessed);
  50         88  
  50         2222  
9 50     50   30860 use Test::Deep ();
  50         349959  
  50         1798  
10 50     50   30644 use YAML::Tiny;
  50         359595  
  50         5926  
11              
12 50         588 use Sub::Exporter -setup => {
13             exports => [
14             is_filelist =>
15             is_yaml =>
16             is_json =>
17             dist_ini => \'_dist_ini',
18             simple_ini => \'_simple_ini',
19             Builder =>
20             Minter =>
21             ],
22             groups => [ default => [ qw(-all) ] ],
23 50     50   33592 };
  50         370405  
24              
25 50     50   54888 use namespace::autoclean -except => 'import';
  50         938597  
  50         306  
26              
27             #pod =head1 DESCRIPTION
28             #pod
29             #pod Test::DZil provides routines for writing tests for Dist::Zilla plugins.
30             #pod
31             #pod =cut
32              
33             #pod =func Builder
34             #pod
35             #pod =func Minter
36             #pod
37             #pod my $tzil = Builder->from_config(...);
38             #pod
39             #pod These return class names that subclass L<Dist::Zilla::Dist::Builder> or
40             #pod L<Dist::Zilla::Dist::Minter>, respectively, with the L<Dist::Zilla::Tester>
41             #pod behavior added.
42             #pod
43             #pod =cut
44              
45             sub Builder {
46 166     166 1 20004054 require Dist::Zilla::Tester;
47 166         2438 Dist::Zilla::Tester::builder();
48             }
49              
50             sub Minter {
51 1     1 1 473747 require Dist::Zilla::Tester;
52 1         41 Dist::Zilla::Tester::minter();
53             }
54              
55             #pod =func is_filelist
56             #pod
57             #pod is_filelist( \@files_we_have, \@files_we_want, $desc );
58             #pod
59             #pod This test assertion compares two arrayrefs of filenames, taking care of slash
60             #pod normalization and sorting. C<@files_we_have> may also contain objects that
61             #pod do L<Dist::Zilla::Role::File>.
62             #pod
63             #pod =cut
64              
65             sub is_filelist {
66 37     37 1 466 my ($have, $want, $comment) = @_;
67              
68 37         245 my @want = @$want;
69 37 100 66     205 my @have = map { my $str = (blessed $_ and
  227         4024  
70             $_->DOES('Dist::Zilla::Role::File'))
71             ? $_->name
72             : $_;
73 227         471 $str =~ s{\\}{/}g; $str } @$have;
  227         457  
74              
75 37         118 local $Test::Builder::Level = $Test::Builder::Level + 1;
76 37         255 Test::Deep::cmp_bag(\@have, \@want, $comment);
77             }
78              
79             #pod =func is_yaml
80             #pod
81             #pod is_yaml( $yaml_string, $want_struct, $comment );
82             #pod
83             #pod This test assertion deserializes the given YAML string and does a
84             #pod C<L<cmp_deeply|Test::Deep/cmp_deeply>>.
85             #pod
86             #pod =cut
87              
88             sub is_yaml {
89 2     2 1 5164 my ($yaml, $want, $comment) = @_;
90              
91 2 50       15 my $have = YAML::Tiny->read_string($yaml)
92             or die "Cannot decode YAML";
93              
94 2         2164 local $Test::Builder::Level = $Test::Builder::Level + 1;
95 2         11 Test::Deep::cmp_deeply($have->[0], $want, $comment);
96             }
97              
98             #pod =func is_json
99             #pod
100             #pod is_json( $json_string, $want_struct, $comment );
101             #pod
102             #pod This test assertion deserializes the given JSON string and does a
103             #pod C<L<cmp_deeply|Test::Deep/cmp_deeply>>.
104             #pod
105             #pod =cut
106              
107             sub is_json {
108 2     2 1 661 my ($json, $want, $comment) = @_;
109              
110 2 50       20 my $have = JSON::MaybeXS->new(ascii => 1)->decode($json)
111             or die "Cannot decode JSON";
112              
113 2         123 local $Test::Builder::Level = $Test::Builder::Level + 1;
114 2         7 Test::Deep::cmp_deeply($have, $want, $comment);
115             }
116              
117             sub _build_ini_builder {
118 95     95   189 my ($starting_core) = @_;
119 95   100     455 $starting_core ||= {};
120              
121             sub {
122 161     161   326293 my (@arg) = @_;
123 161 100       1200 my $new_core = _HASH0($arg[0]) ? shift(@arg) : {};
124              
125 161         1699 my $core_config = { %$starting_core, %$new_core };
126              
127 161         504 my $config = '';
128              
129 161         1093 for my $key (sort keys %$core_config) {
130             my @values = ref $core_config->{ $key }
131 0         0 ? @{ $core_config->{ $key } }
132 973 50       2371 : $core_config->{ $key };
133              
134 973         1497 $config .= "$key = $_\n" for grep {defined} @values;
  973         3134  
135             }
136              
137 161 50       869 $config .= "\n" if length $config;
138              
139 161         453 for my $line (@arg) {
140 473 100       1767 my @plugin = ref $line ? @$line : ($line, {});
141 473         889 my $moniker = shift @plugin;
142 473 100       3160 my $name = _HASH0($plugin[0]) ? undef : shift @plugin;
143 473   100     2282 my $payload = shift(@plugin) || {};
144              
145 473 50       1639 Carp::confess("bogus plugin configuration: too many args") if @plugin;
146              
147 473         1379 $config .= '[' . $moniker;
148 473 100       1368 $config .= ' / ' . $name if defined $name;
149 473         948 $config .= "]\n";
150              
151 473         1352 for my $key (sort keys %$payload) {
152             my @values = ref $payload->{ $key }
153 17         49 ? @{ $payload->{ $key } }
154 319 100       900 : $payload->{ $key };
155              
156 319         575 $config .= "$key = $_\n" for grep {defined} @values;
  336         1067  
157             }
158              
159 473         1257 $config .= "\n";
160             }
161              
162 161         3067 return $config;
163             }
164 95         672 }
165              
166             #pod =func dist_ini
167             #pod
168             #pod my $ini_text = dist_ini(\%root_config, @plugins);
169             #pod
170             #pod This routine returns a string that could be used to populate a simple
171             #pod F<dist.ini> file. The C<%root_config> gives data for the "root" section of the
172             #pod configuration. To provide a line multiple times, provide an arrayref. For
173             #pod example, the root section could read:
174             #pod
175             #pod {
176             #pod name => 'Dist-Sample',
177             #pod author => [
178             #pod 'J. Smith <jsmith@example.com>',
179             #pod 'Q. Smith <qsmith@example.com>',
180             #pod ],
181             #pod }
182             #pod
183             #pod The root section is optional.
184             #pod
185             #pod Plugins can be given in a few ways:
186             #pod
187             #pod =begin :list
188             #pod
189             #pod = C<"PluginMoniker">
190             #pod
191             #pod = C<[ "PluginMoniker" ]>
192             #pod
193             #pod These become C<[PluginMoniker]>
194             #pod
195             #pod = C<[ "PluginMoniker", "PluginName" ]>
196             #pod
197             #pod This becomes C<[PluginMoniker / PluginName]>
198             #pod
199             #pod = C<[ "PluginMoniker", { ... } ]>
200             #pod
201             #pod = C<[ "PluginMoniker", "PluginName", { ... } ]>
202             #pod
203             #pod These use the given hashref as the parameters inside the section, with the same
204             #pod semantics as the root section.
205             #pod
206             #pod =end :list
207             #pod
208             #pod =cut
209              
210             sub _dist_ini {
211 46     46   10025 _build_ini_builder;
212             }
213              
214             #pod =func simple_ini
215             #pod
216             #pod This behaves exactly like C<dist_ini>, but it merges any given root config into
217             #pod a starter config, which means that you can often skip any explicit root config.
218             #pod The starter config may change slightly over time, but is something like this:
219             #pod
220             #pod {
221             #pod name => 'DZT-Sample',
222             #pod abstract => 'Sample DZ Dist',
223             #pod version => '0.001',
224             #pod author => 'E. Xavier Ample <example@example.org>',
225             #pod license => 'Perl_5',
226             #pod copyright_holder => 'E. Xavier Ample',
227             #pod }
228             #pod
229             #pod =cut
230              
231             sub _simple_ini {
232 49     49   15072 _build_ini_builder({
233             name => 'DZT-Sample',
234             abstract => 'Sample DZ Dist',
235             version => '0.001',
236             author => 'E. Xavier Ample <example@example.org>',
237             license => 'Perl_5',
238             copyright_holder => 'E. Xavier Ample',
239             });
240             }
241              
242             1;
243              
244             __END__
245              
246             =pod
247              
248             =encoding UTF-8
249              
250             =head1 NAME
251              
252             Test::DZil - tools for testing Dist::Zilla plugins
253              
254             =head1 VERSION
255              
256             version 6.037
257              
258             =head1 DESCRIPTION
259              
260             Test::DZil provides routines for writing tests for Dist::Zilla plugins.
261              
262             =head1 PERL VERSION
263              
264             This module should work on any version of perl still receiving updates from
265             the Perl 5 Porters. This means it should work on any version of perl
266             released in the last two to three years. (That is, if the most recently
267             released version is v5.40, then this module should work on both v5.40 and
268             v5.38.)
269              
270             Although it may work on older versions of perl, no guarantee is made that the
271             minimum required version will not be increased. The version may be increased
272             for any reason, and there is no promise that patches will be accepted to
273             lower the minimum required perl.
274              
275             =head1 FUNCTIONS
276              
277             =head2 Builder
278              
279             =head2 Minter
280              
281             my $tzil = Builder->from_config(...);
282              
283             These return class names that subclass L<Dist::Zilla::Dist::Builder> or
284             L<Dist::Zilla::Dist::Minter>, respectively, with the L<Dist::Zilla::Tester>
285             behavior added.
286              
287             =head2 is_filelist
288              
289             is_filelist( \@files_we_have, \@files_we_want, $desc );
290              
291             This test assertion compares two arrayrefs of filenames, taking care of slash
292             normalization and sorting. C<@files_we_have> may also contain objects that
293             do L<Dist::Zilla::Role::File>.
294              
295             =head2 is_yaml
296              
297             is_yaml( $yaml_string, $want_struct, $comment );
298              
299             This test assertion deserializes the given YAML string and does a
300             C<L<cmp_deeply|Test::Deep/cmp_deeply>>.
301              
302             =head2 is_json
303              
304             is_json( $json_string, $want_struct, $comment );
305              
306             This test assertion deserializes the given JSON string and does a
307             C<L<cmp_deeply|Test::Deep/cmp_deeply>>.
308              
309             =head2 dist_ini
310              
311             my $ini_text = dist_ini(\%root_config, @plugins);
312              
313             This routine returns a string that could be used to populate a simple
314             F<dist.ini> file. The C<%root_config> gives data for the "root" section of the
315             configuration. To provide a line multiple times, provide an arrayref. For
316             example, the root section could read:
317              
318             {
319             name => 'Dist-Sample',
320             author => [
321             'J. Smith <jsmith@example.com>',
322             'Q. Smith <qsmith@example.com>',
323             ],
324             }
325              
326             The root section is optional.
327              
328             Plugins can be given in a few ways:
329              
330             =over 4
331              
332             =item C<"PluginMoniker">
333              
334             =item C<[ "PluginMoniker" ]>
335              
336             These become C<[PluginMoniker]>
337              
338             =item C<[ "PluginMoniker", "PluginName" ]>
339              
340             This becomes C<[PluginMoniker / PluginName]>
341              
342             =item C<[ "PluginMoniker", { ... } ]>
343              
344             =item C<[ "PluginMoniker", "PluginName", { ... } ]>
345              
346             These use the given hashref as the parameters inside the section, with the same
347             semantics as the root section.
348              
349             =back
350              
351             =head2 simple_ini
352              
353             This behaves exactly like C<dist_ini>, but it merges any given root config into
354             a starter config, which means that you can often skip any explicit root config.
355             The starter config may change slightly over time, but is something like this:
356              
357             {
358             name => 'DZT-Sample',
359             abstract => 'Sample DZ Dist',
360             version => '0.001',
361             author => 'E. Xavier Ample <example@example.org>',
362             license => 'Perl_5',
363             copyright_holder => 'E. Xavier Ample',
364             }
365              
366             =head1 AUTHOR
367              
368             Ricardo SIGNES 😏 <cpan@semiotic.systems>
369              
370             =head1 COPYRIGHT AND LICENSE
371              
372             This software is copyright (c) 2026 by Ricardo SIGNES.
373              
374             This is free software; you can redistribute it and/or modify it under
375             the same terms as the Perl 5 programming language system itself.
376              
377             =cut