File Coverage

blib/lib/Test/Class/Moose/Role/CLI.pm
Criterion Covered Total %
statement 104 104 100.0
branch 23 26 88.4
condition n/a
subroutine 24 24 100.0
pod 0 1 0.0
total 151 155 97.4


line stmt bran cond sub pod time code
1             package Test::Class::Moose::Role::CLI;
2              
3             # ABSTRACT: Role for command line argument handling and extra CLI features
4              
5 1     1   739 use strict;
  1         3  
  1         31  
6 1     1   5 use warnings;
  1         2  
  1         28  
7 1     1   6 use namespace::autoclean;
  1         2  
  1         5  
8              
9 1     1   80 use 5.010000;
  1         3  
10              
11             our $VERSION = '0.99';
12              
13 1     1   424 use Moose::Role 2.0000;
  1         4137  
  1         5  
14 1     1   4648 use Carp;
  1         2  
  1         58  
15              
16 1     1   7 use File::Find qw( find );
  1         2  
  1         62  
17 1     1   6 use JSON::MaybeXS qw( encode_json );
  1         2  
  1         49  
18 1     1   5 use Module::Runtime qw( use_package_optimistically );
  1         3  
  1         4  
19 1     1   513 use Module::Util qw( fs_path_to_module );
  1         2574  
  1         56  
20 1     1   444 use MooseX::Getopt 0.71;
  1         248757  
  1         58  
21 1     1   540 use Test::Class::Moose::Runner;
  1         3  
  1         1267  
22              
23             has classes => (
24             is => 'ro',
25             isa => 'ArrayRef[Str]',
26             default => sub { [] },
27             );
28              
29             has methods => (
30             traits => ['Array'],
31             is => 'ro',
32             isa => 'ArrayRef[Str]',
33             default => sub { [] },
34             handles => {
35             _has_methods => 'count',
36             },
37             );
38              
39             has exclude_methods => (
40             traits => ['Array'],
41             is => 'ro',
42             isa => 'ArrayRef[Str]',
43             default => sub { [] },
44             handles => {
45             _has_exclude_methods => 'count',
46             },
47             );
48              
49             has tags => (
50             traits => ['Array'],
51             is => 'ro',
52             isa => 'ArrayRef[Str]',
53             default => sub { [] },
54             handles => {
55             _has_tags => 'count',
56             },
57             );
58              
59             has test_lib_dirs => (
60             traits => ['Array'],
61             is => 'ro',
62             isa => 'ArrayRef[Str]',
63             default => sub { ['t/lib'] },
64             handles => {
65             _all_test_lib_dirs => 'elements',
66             _has_test_lib_dirs => 'count',
67             },
68             );
69              
70             has exclude_tags => (
71             traits => ['Array'],
72             is => 'ro',
73             isa => 'ArrayRef[Str]',
74             default => sub { [] },
75             handles => {
76             _has_exclude_tags => 'count',
77             },
78             );
79              
80             has parallel_progress => (
81             is => 'ro',
82             isa => 'Bool',
83             predicate => '_has_parallel_progress',
84             );
85              
86             has color => (
87             is => 'ro',
88             isa => 'Bool',
89             predicate => '_has_color',
90             );
91              
92             has jobs => (
93             is => 'ro',
94             isa => 'Int',
95             predicate => '_has_jobs',
96             );
97              
98             has randomize_methods => (
99             is => 'ro',
100             isa => 'Bool',
101             predicate => '_has_randomize_methods',
102             );
103              
104             has randomize_classes => (
105             is => 'ro',
106             isa => 'Bool',
107             predicate => '_has_randomize_classes',
108             );
109              
110             has set_process_name => (
111             is => 'ro',
112             isa => 'Bool',
113             predicate => '_has_set_process_name',
114             );
115              
116             has statistics => (
117             is => 'ro',
118             isa => 'Bool',
119             predicate => '_has_statistics',
120             );
121              
122             has show_timing => (
123             is => 'ro',
124             isa => 'Bool',
125             predicate => '_has_show_timing',
126             );
127              
128             has use_environment => (
129             is => 'ro',
130             isa => 'Bool',
131             predicate => '_has_use_environment',
132             );
133              
134             has _runner_class => (
135             is => 'ro',
136             isa => 'ClassName',
137             init_arg => 'runner_class',
138             default => 'Test::Class::Moose::Runner',
139             );
140              
141             has _timing_data_file => (
142             is => 'ro',
143             isa => 'Str',
144             init_arg => 'timing_data_file',
145             predicate => '_has_timing_data_file',
146             );
147              
148             has _start_time => (
149             is => 'ro',
150             isa => 'Int',
151             init_arg => undef,
152             default => sub {time},
153             );
154              
155             has _runner => (
156             is => 'ro',
157             init_arg => undef,
158             lazy => 1,
159             builder => '_build_runner',
160             );
161              
162             has _class_names => (
163             traits => ['Array'],
164             is => 'ro',
165             isa => 'ArrayRef[Str]',
166             init_arg => undef,
167             lazy => 1,
168             builder => '_build_class_names',
169             handles => {
170             _has_class_names => 'count',
171             },
172             );
173              
174             with 'MooseX::Getopt::Dashes';
175              
176             sub run {
177 12     12 0 1784 my $self = shift;
178              
179 12         52 $self->_before_run;
180 12         92 $self->_load_classes;
181 12         393 $self->_runner->runtests;
182 12         55 $self->_after_run;
183 12         88 $self->_maybe_save_timing_data;
184              
185 12         270 return $self->_runner;
186             }
187              
188       11     sub _before_run { }
189              
190             sub _load_classes {
191 11     11   17 my $self = shift;
192              
193 11 100       379 if ( $self->_has_class_names ) {
194 2         7 local @INC = ( $self->_test_lib_dirs, @INC );
195 2         5 use_package_optimistically($_) for @{ $self->_class_names };
  2         52  
196             }
197             else {
198 9         645 require Test::Class::Moose::Load;
199 9         35 Test::Class::Moose::Load->import( $self->_test_lib_dirs );
200             }
201              
202 11         1917 return;
203             }
204              
205       11     sub _after_run { }
206              
207             {
208             my $meta = __PACKAGE__->meta;
209             my %attr_map = map { $_ => $_ }
210             grep { $meta->get_attribute($_)->original_role->name eq __PACKAGE__ }
211             grep { !/^_/ && $_ ne 'classes' } $meta->get_attribute_list;
212             $attr_map{randomize_methods} = 'randomize';
213             $attr_map{tags} = 'include_tags';
214             $attr_map{color} = 'color_output';
215             $attr_map{parallel_progress} = 'show_parallel_progress';
216              
217             sub _build_runner {
218 12     12   26 my $self = shift;
219              
220 12         18 my %args;
221 12         66 for my $attr ( keys %attr_map ) {
222 168         290 my $pred = '_has_' . $attr;
223 168 100       5113 next unless $self->$pred();
224              
225 18         478 $args{ $attr_map{$attr} } = $self->$attr;
226             }
227              
228 12 100       375 if ( $self->_has_class_names ) {
229 3         73 $args{test_classes} = $self->_class_names;
230             }
231              
232 12 100       40 if ( $args{methods} ) {
233             my $re = join '|',
234 1         4 map { quotemeta($_) } @{ delete $args{methods} };
  3         7  
  1         3  
235 1         38 $args{include} = qr/^(?:$re)$/;
236             }
237              
238 12 100       27 if ( $args{exclude_methods} ) {
239             my $re = join '|',
240 1         4 map { quotemeta($_) } @{ delete $args{exclude_methods} };
  3         7  
  1         2  
241 1         33 $args{exclude} = qr/^(?:$re)$/;
242             }
243              
244 12         292 use_package_optimistically( $self->_runner_class );
245 12         950 return $self->_runner_class->new(%args);
246             }
247             }
248              
249             sub _build_class_names {
250 15     15   30 my $self = shift;
251              
252             return [
253 13         166 map { $self->_munge_class($_) }
254 15         27 map { $self->_maybe_resolve_path($_) } @{ $self->classes }
  12         295  
  15         367  
255             ];
256             }
257              
258 9     9   161 sub _munge_class { $_[1] }
259              
260             sub _maybe_resolve_path {
261 12     12   25 my $self = shift;
262 12         19 my $path = shift;
263              
264 12 100       304 if ( -d $path ) {
265 1         8 return $self->_find_classes($path);
266             }
267              
268 11 100       54 if ( $path =~ /\.pm$/ ) {
269 5         21 for my $dir ( $self->_test_lib_dirs ) {
270 6 100       137 if ( $path =~ s{^.*\Q$dir}{} ) {
271 5         26 return fs_path_to_module($path);
272             }
273             }
274             }
275              
276 6         19 return $path;
277             }
278              
279             # This is still here to maintain backwards compatibility for people writing
280             # custom test runners. In past releases the only way to customize this value
281             # was to override this method, though we later added a CLI option to set this
282             # value.
283             sub _test_lib_dirs {
284 15     15   27 my $self = shift;
285 15         530 return $self->_all_test_lib_dirs;
286             }
287              
288             sub _find_classes {
289 1     1   3 my $self = shift;
290 1         3 my $dir = shift;
291              
292 1         2 my @classes;
293             my $finder = sub {
294 3 100   3   208 return unless /\.pm$/;
295 2         38 s{^.*\Q$dir}{};
296 2         10 push @classes, fs_path_to_module($_);
297 1         6 };
298              
299 1         109 find(
300             { wanted => $finder,
301             no_chdir => 1,
302             },
303             $dir
304             );
305              
306 1         43 return @classes;
307             }
308              
309             sub _maybe_save_timing_data {
310 12     12   25 my $self = shift;
311              
312 12 100       359 return unless $self->_has_timing_data_file;
313              
314 1         27 my $file = $self->_timing_data_file;
315 1 50       91 open my $fh, '>', $file or die "Cannot write to $file: $!";
316 1 50       3 print {$fh} encode_json(
  1         36  
317             { process_name => $0,
318             start_time => $self->_start_time,
319             timing => $self->_runner->test_report->timing_data,
320             }
321             ) or die "Cannot write to $file: $!";
322 1 50       101 close $fh or die "Cannot write to $file: $!";
323              
324 1         56 return;
325             }
326              
327             1;
328              
329             __END__
330              
331             =pod
332              
333             =encoding UTF-8
334              
335             =head1 NAME
336              
337             Test::Class::Moose::Role::CLI - Role for command line argument handling and extra CLI features
338              
339             =head1 VERSION
340              
341             version 0.99
342              
343             =head1 SYNOPSIS
344              
345             package My::CLI;
346              
347             use Moose;
348              
349             with 'Test::Class::Moose::Role::CLI';
350              
351             sub _munge_class {
352             return $_[1] =~ /^TestFor::/ ? $_[1] : 'TestFor::MyApp::' . $_[1] );
353             }
354              
355             sub _before_run { ... }
356             sub _after_run { ... }
357              
358             =head1 DESCRIPTION
359              
360             This role provides the core implementation of command line option processing
361             for L<Test::Class::Moose::CLI>. You can consume this role and add additional
362             hooks to customize how your test classes are run.
363              
364             See L<Test::Class::Moose::CLI> for a list of all the available command line
365             options that this role handles.
366              
367             =for Pod::Coverage run
368              
369             =head1 HOOKS
370              
371             This role has several hook methods that it calls. The role provides no-op or
372             default implementations of these hooks but you can provide an implementation in
373             your class that does something.
374              
375             =head2 _munge_class
376              
377             This method is called for each class as found by the command line C<--classes>
378             option. Note that this is called I<after> resolving file and directory paths
379             passed as a C<--classes> option.
380              
381             You can use this to allow people to pass short names like C<Model::Car> and
382             turn it into a full name like C<TestFor::MyApp::Model::Car>.
383              
384             By default this method is a no-op.
385              
386             =head2 _before_run
387              
388             This method is called before the test classes are run (or even loaded).
389              
390             By default this method is a no-op.
391              
392             =head2 _test_lib_dirs
393              
394             This should return a list of directories containing test classes. The
395             directories can be relative to the project root (F<t/lib>) or absolute.
396              
397             This defaults to returning a single path, F<t/lib>.
398              
399             Note that this is now also settable via
400             L<Test::Class::Moose::CLI/--test_lib_dirs>.
401              
402             =head2 _load_classes
403              
404             This method will try to load all the classes passed on the command line if any
405             were passed. If the value that was passed is a path rather than a class name,
406             any leading part matching a value in the list from C<_test_lib_dirs> will be
407             stripped, and the rest will be transformed from a path to a module name.
408              
409             Otherwise it invokes L<Test::Class::Moose::Load> with the value returned by
410             C<_test_lib_dirs> as its argument.
411              
412             =head2 _after_run
413              
414             This method is called after all the test classes are run.
415              
416             By default this method is a no-op.
417              
418             =head1 SUPPORT
419              
420             Bugs may be submitted at L<https://github.com/houseabsolute/test-class-moose/issues>.
421              
422             I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
423              
424             =head1 SOURCE
425              
426             The source code repository for Test-Class-Moose can be found at L<https://github.com/houseabsolute/test-class-moose>.
427              
428             =head1 AUTHORS
429              
430             =over 4
431              
432             =item *
433              
434             Curtis "Ovid" Poe <ovid@cpan.org>
435              
436             =item *
437              
438             Dave Rolsky <autarch@urth.org>
439              
440             =back
441              
442             =head1 COPYRIGHT AND LICENSE
443              
444             This software is copyright (c) 2012 - 2021 by Curtis "Ovid" Poe.
445              
446             This is free software; you can redistribute it and/or modify it under
447             the same terms as the Perl 5 programming language system itself.
448              
449             The full text of the license can be found in the
450             F<LICENSE> file included with this distribution.
451              
452             =cut