File Coverage

blib/lib/Test/Class/Moose/Role/CLI.pm
Criterion Covered Total %
statement 98 98 100.0
branch 23 26 88.4
condition n/a
subroutine 22 22 100.0
pod 0 1 0.0
total 143 147 97.2


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