File Coverage

blib/lib/Test/Auto/Subtests.pm
Criterion Covered Total %
statement 281 296 94.9
branch 37 60 61.6
condition 13 20 65.0
subroutine 68 68 100.0
pod 16 17 94.1
total 415 461 90.0


line stmt bran cond sub pod time code
1             package Test::Auto::Subtests;
2              
3 7     7   50 use strict;
  7         15  
  7         234  
4 7     7   46 use warnings;
  7         14  
  7         182  
5              
6 7     7   37 use feature 'state';
  7         18  
  7         759  
7              
8 7     7   49 use Moo;
  7         15  
  7         98  
9 7     7   2827 use Test::Auto::Try;
  7         18  
  7         277  
10 7     7   47 use Test::Auto::Types ();
  7         21  
  7         149  
11 7     7   42 use Test::More;
  7         21  
  7         164  
12 7     7   1910 use Type::Registry;
  7         25  
  7         111  
13              
14             require Carp;
15              
16             our $VERSION = '0.12'; # VERSION
17              
18             # ATTRIBUTES
19              
20             has parser => (
21             is => 'ro',
22             isa => Test::Auto::Types::Parser(),
23             required => 1
24             );
25              
26             # METHODS
27              
28             sub standard {
29 8     8 1 9895 my ($self) = @_;
30              
31 8         92 $self->package;
32 8         14568 $self->document;
33 8         11260 $self->libraries;
34 8         11988 $self->inherits;
35 8         11851 $self->attributes;
36 8         22097 $self->methods;
37 8         18464 $self->routines;
38 8         11266 $self->functions;
39 8         11947 $self->types;
40              
41 8         11679 return $self;
42             }
43              
44             sub package {
45 9     9 1 30 my ($self) = @_;
46              
47 9         43 my $parser = $self->parser;
48              
49             subtest "testing package", sub {
50 9 50   9   9787 my $package = $parser->render('name')
51             or plan skip_all => "no package";
52              
53 9         60 require_ok $package; # use_ok can't test roles
54 9         169 };
55             }
56              
57             sub plugin {
58 7     7 1 52 my ($self, $name) = @_;
59              
60 7         91 my $package = join '::', map ucfirst, (
61             'test', 'auto', 'plugin', $name
62             );
63              
64             subtest "testing plugin ($name)", sub {
65 6 50   6   3490 use_ok $package
  6     7   1604  
  6         16  
  6         79  
  7         5232  
66             or plan skip_all => "$package not loaded";
67              
68 7         2912 ok $package->isa('Test::Auto::Plugin'), 'isa Test::Auto::Plugin';
69 7         77 };
70              
71 7         10528 my $instance = $package->new(subtests => $self);
72              
73 7         19852 return $instance;
74             }
75              
76             sub libraries {
77 9     9 1 83 my ($self) = @_;
78              
79 9         41 my $parser = $self->parser;
80              
81             subtest "testing libraries", sub {
82 9 100   9   6472 my $packages = $parser->libraries
83             or plan skip_all => "no libraries";
84              
85 6     6   1084 map +(use_ok $_), @$packages;
  6         15  
  6         50  
  6         93  
  8         84  
86 9         1110 };
87             }
88              
89             sub inherits {
90 9     9 1 28 my ($self) = @_;
91              
92 9         103 my $parser = $self->parser;
93              
94             subtest "testing inherited", sub {
95 9 100   9   7800 my $inherited = $parser->inherits
96             or plan skip_all => "no inherited";
97              
98 2         64 map +(use_ok $_), @$inherited;
99 9         84 };
100             }
101              
102             sub document {
103 9     9 1 112 my ($self) = @_;
104              
105 9         37 my $parser = $self->parser;
106              
107             subtest "testing document", sub {
108 9     9   6428 ok $parser->render($_), "pod $_" for qw(
109             name
110             abstract
111             synopsis
112             abstract
113             description
114             );
115 9         107 };
116             }
117              
118             sub attributes {
119 9     9 1 31 my ($self) = @_;
120              
121 9         59 my $parser = $self->parser;
122              
123             subtest "testing attributes", sub {
124 9 50   9   6844 my $package = $parser->render('name')
125             or plan skip_all => "no package";
126              
127 9         190 my $attributes = $parser->stash('attributes');
128 9 100 66     81 plan skip_all => 'no attributes' if !$attributes || !%$attributes;
129              
130 8         43 for my $name (sort keys %$attributes) {
131             subtest "testing attribute $name", sub {
132 22         18173 my $attribute = $attributes->{$name};
133              
134 22         164 ok $package->can($name), 'can ok';
135 22         8277 ok $attribute->{is}, 'has $is';
136 22         8288 ok $attribute->{presence}, 'has $presence';
137 22         8236 ok $attribute->{type}, 'has $type';
138              
139 22         8329 my $registry = $self->registry;
140 22         116 ok !!$registry->lookup($attribute->{type}), 'valid $type';
141 22         34763 };
142             }
143 9         73 };
144             }
145              
146             sub methods {
147 9     9 1 40 my ($self) = @_;
148              
149 9         124 my $parser = $self->parser;
150              
151             subtest "testing methods", sub {
152 9 50   9   6598 my $package = $parser->render('name')
153             or plan skip_all => "no package";
154              
155 9         51 my $methods = $parser->methods;
156 9 100 66     75 plan skip_all => 'no methods' if !$methods || !%$methods;
157              
158 7         60 for my $name (sort keys %$methods) {
159             subtest "testing method $name", sub {
160 54         49820 my $method = $methods->{$name};
161              
162 54         430 ok $package->can($name), 'can ok';
163 54         25030 ok $method->{usage}, 'pod description';
164 54         24835 ok $method->{signature}, 'pod signature';
165 54         24824 ok $method->{examples}{1}, 'pod example-1';
166 54         94491 };
167             }
168 9         306 };
169             }
170              
171             sub routines {
172 9     9 1 53 my ($self) = @_;
173              
174 9         74 my $parser = $self->parser;
175              
176             subtest "testing routines", sub {
177 9 50   9   6676 my $package = $parser->render('name')
178             or plan skip_all => "no package";
179              
180 9         98 my $routines = $parser->routines;
181 9 50 66     96 plan skip_all => 'no routines' if !$routines || !%$routines;
182              
183 1         3 for my $name (sort keys %$routines) {
184             subtest "testing routine $name", sub {
185 1         15 my $routine = $routines->{$name};
186              
187 1         3 ok $package->can($name), 'can ok';
188 1         42 ok $routine->{usage}, 'pod description';
189 1         6 ok $routine->{signature}, 'pod signature';
190 1         2 ok $routine->{examples}{1}, 'pod example-1';
191 1         6 };
192             }
193 9         78 };
194             }
195              
196             sub functions {
197 9     9 1 80 my ($self) = @_;
198              
199 9         36 my $parser = $self->parser;
200              
201             subtest "testing functions", sub {
202 9 50   9   6715 my $package = $parser->render('name')
203             or plan skip_all => "no package";
204              
205 9         69 my $functions = $parser->functions;
206 9 100 100     81 plan skip_all => 'no functions' if !$functions || !%$functions;
207              
208 2         132 for my $name (sort keys %$functions) {
209             subtest "testing function $name", sub {
210 2         804 my $function = $functions->{$name};
211              
212 2         61 ok $package->can($name), 'can ok';
213 2         390 ok $function->{usage}, 'pod description';
214 2         373 ok $function->{signature}, 'pod signature';
215 2         445 ok $function->{examples}{1}, 'pod example-1';
216 2         19 };
217             }
218 9         75 };
219             }
220              
221             sub types {
222 8     8 0 34 my ($self) = @_;
223              
224 8         37 my $parser = $self->parser;
225              
226             subtest "testing types", sub {
227 8     8   5432 my $types = $parser->types;
228 8 100 66     86 plan skip_all => 'no types' if !$types || !%$types;
229              
230 2         63 for my $name (sort keys %$types) {
231             subtest "testing type $name", sub {
232 5         3278 my $type = $types->{$name};
233              
234 5 50       66 my $library = $type->{library}[0][0]
235             or plan skip_all => "no library";
236              
237 5         20 use_ok $library;
238 5         1638 ok $library->isa('Type::Library'), 'isa Type::Library';
239              
240 5         1513 my $constraint = $library->get_type($name);
241 5         75 ok $constraint, 'has constraint';
242              
243 5 50       1511 if ($constraint) {
244 5         53 ok $constraint->isa('Type::Tiny'), 'isa Type::Tiny constraint';
245              
246 5         1480 for my $number (sort keys %{$type->{examples}}) {
  5         23  
247 5         115 my $example = $type->{examples}{$number};
248 5         16 my $context = join "\n", @{$example->[0]};
  5         21  
249              
250             subtest "testing example-$number ($name)", sub {
251 5         3442 my $tryable = $self->tryable($context)->call('evaluator');
252 5         12 my $result = $tryable->result;
253              
254 5         73 ok $constraint->check($result), 'passed constraint check';
255 5         99 };
256             }
257              
258 5         7499 for my $number (sort keys %{$type->{coercions}}) {
  5         40  
259 1         22 my $coercion = $type->{coercions}{$number};
260 1         6 my $context = join "\n", @{$coercion->[0]};
  1         2  
261              
262             subtest "testing coercion-$number ($name)", sub {
263 0         0 my $tryable = $self->tryable($context)->call('evaluator');
264 0         0 my $result = $tryable->result;
265              
266 0         0 ok $constraint->check($constraint->coerce($result)),
267             'passed constraint coercion';
268 1         98 };
269             }
270             }
271 5         3899 };
272             }
273 8         80 };
274             }
275              
276             sub synopsis {
277 6     7 1 45 my ($self, $callback) = @_;
278              
279 6         26 my $parser = $self->parser;
280              
281 6         27 my $context = $parser->render('synopsis');
282 6         32 my $tryable = $self->tryable($context);
283              
284             subtest "testing synopsis", sub {
285 6     7   5248 my @results = $callback->($tryable->call('evaluator'));
286              
287 6         30 ok scalar(@results), 'called ok';
288 6         64 };
289             }
290              
291             sub scenario {
292 2     3 1 12 my ($self, $name, $callback) = @_;
293              
294 2         8 my $parser = $self->parser;
295              
296 2         5 my @results;
297              
298 2         11 my $example = $parser->scenarios($name, 'example');
299 2 50       8 my @content = $example ? @{$example->[0]} : ();
  2         11  
300              
301 2         16 unshift @content,
302             (map $parser->render(split /\s/),
303             (map +(/# given:\s*([\w\s-]+)/g), @content));
304              
305 2         15 my $tryable = $self->tryable(join "\n", @content);
306              
307             subtest "testing scenario ($name)", sub {
308 2 50   3   1873 unless (@content) {
309 0         0 BAIL_OUT "unknown scenario $name";
310              
311 0         0 return;
312             }
313 2         11 @results = $callback->($tryable->call('evaluator'));
314              
315 2         11 ok scalar(@results), 'called ok';
316 2         22 };
317             }
318              
319             sub example {
320 26     27 1 1675 my ($self, $number, $name, $type, $callback) = @_;
321              
322 26         83 my $parser = $self->parser;
323              
324 26         82 my $context;
325             my $signature;
326 26         0 my @results;
327              
328 26 100       83 if ($type eq 'method') {
    50          
    0          
329 25         113 $context = $parser->methods($name, 'examples');
330 25         83 $signature = $parser->methods($name, 'signature');
331 25 50       84 $signature = join "\n", @{$signature->[0]} if $signature;
  25         131  
332             }
333             elsif ($type eq 'function') {
334 1         6 $context = $parser->functions($name, 'examples');
335 1         4 $signature = $parser->functions($name, 'signature');
336 1 50       5 $signature = join "\n", @{$signature->[0]} if $signature;
  1         5  
337             }
338             elsif ($type eq 'routine') {
339 0         0 $context = $parser->routines($name, 'examples');
340 0         0 $signature = $parser->routines($name, 'signature');
341 0 0       0 $signature = join "\n", @{$signature->[0]} if $signature;
  0         0  
342             }
343             else {
344 0         0 Carp::confess "$type is not a valid example type";
345             }
346              
347 26         70 $number = abs $number;
348              
349 26   50     115 my $example = $context->{$number}[0] || [];
350 26         105 my @content = @$example;
351              
352 26         274 unshift @content,
353             (map $parser->render($_),
354             (map +(/# given:\s*(\w+)/g), @content));
355              
356 26         151 my $tryable = $self->tryable(join "\n", @content);
357              
358             subtest "testing example-$number ($name)", sub {
359 26 50   27   21912 unless (@content) {
360 0         0 BAIL_OUT "unknown $type $name for example-$number";
361              
362 0         0 return;
363             }
364 26         117 @results = $callback->($tryable->call('evaluator'));
365              
366 26         108 ok scalar(@results), 'called ok';
367 26         268 };
368              
369             subtest "testing example-$number ($name) results", sub {
370 26 50   27   21668 unless (@content) {
371 0         0 BAIL_OUT "unknown $type $name for example-$number";
372              
373 0         0 return;
374             }
375 26         226 my ($input, $output) = $signature =~ /(.*) : (.*)/;
376              
377 26         92 my $registry = $self->registry;
378              
379 26         129 ok my $type = $registry->lookup($output), 'return type ok';
380              
381 26 50       35628 map +(ok $type ? $type->check($_) : (), 'return value(s) ok'), @results;
382 26         48369 };
383             }
384              
385             sub evaluator {
386 40     41 1 99 my ($self, $context) = @_;
387              
388 40         68 local $@;
389              
390 6     6   54 my $returned = eval "no warnings 'redefine';\n\n$context";
  6     7   19  
  6     7   286  
  7     7   277  
  7     6   20  
  7     6   280  
  8     5   1180  
  7     4   467  
  7     4   175  
  8     4   274  
  7     4   28  
  7     4   310  
  7     4   64  
  6     4   17  
  6     4   321  
  6     4   98  
  6     3   33  
  6     3   148  
  5     2   69  
  5     2   14  
  5     1   157  
  4     1   60  
  4     1   14  
  4     1   166  
  4     1   65  
  4     1   26  
  4     1   99  
  4         78  
  4         271  
  4         167  
  4         22  
  4         22  
  4         87  
  4         21  
  4         54  
  4         154  
  4         27  
  4         51  
  4         138  
  4         22  
  4         23  
  4         148  
  4         28  
  4         70  
  4         55  
  4         20  
  4         72  
  4         256  
  3         16  
  3         55  
  3         105  
  3         15  
  3         25  
  3         51  
  40         4015  
  1         262  
  1         2  
  1         2  
  1         18  
  1         163  
  1         2  
  1         2  
  1         9  
391 40         29103 my $failures = $@;
392              
393 40 50       118 if ($failures) {
394 0         0 Carp::confess $failures
395             }
396              
397 40         313 return $returned;
398             }
399              
400             sub tryable {
401 40     41 1 114 my ($self, @passed) = @_;
402              
403 40         93 my @arguments = (invocant => $self);
404              
405 40 100       163 push @arguments, arguments => [@passed] if @passed;
406              
407 40         1035 return Test::Auto::Try->new(@arguments);
408             }
409              
410             sub registry {
411 48     49 1 147 my ($self) = @_;
412              
413 48         132 my $parser = $self->parser;
414 48         134 my $libraries = $parser->libraries;
415 48         123 my $package = $parser->name;
416              
417 48 50 33     305 $libraries = ['Types::Standard'] if !$libraries || !@$libraries;
418              
419 48         91 state $populate = 0;
420 48         105 state $registry = Type::Registry->for_class($package);
421              
422 48 100       274 map $registry->add_types($_), @$libraries if !$populate++;
423              
424 48         34682 return $registry;
425             }
426              
427             1;
428              
429             =encoding utf8
430              
431             =head1 NAME
432              
433             Test::Auto::Subtests
434              
435             =cut
436              
437             =head1 ABSTRACT
438              
439             Testing Automation
440              
441             =cut
442              
443             =head1 SYNOPSIS
444              
445             package main;
446              
447             use Test::Auto;
448             use Test::Auto::Parser;
449             use Test::Auto::Subtests;
450              
451             my $test = Test::Auto->new(
452             't/Test_Auto_Subtests.t'
453             );
454              
455             my $parser = Test::Auto::Parser->new(
456             source => $test
457             );
458              
459             my $subtests = Test::Auto::Subtests->new(
460             parser => $parser
461             );
462              
463             # execute dynamic subtests
464              
465             # $subtests->standard
466              
467             =cut
468              
469             =head1 DESCRIPTION
470              
471             This package use the L<Test::Auto::Parser> object to execute a set of dynamic
472             subtests.
473              
474             =cut
475              
476             =head1 LIBRARIES
477              
478             This package uses type constraints from:
479              
480             L<Test::Auto::Types>
481              
482             =cut
483              
484             =head1 ATTRIBUTES
485              
486             This package has the following attributes:
487              
488             =cut
489              
490             =head2 parser
491              
492             parser(Parser)
493              
494             This attribute is read-only, accepts C<(Parser)> values, and is required.
495              
496             =cut
497              
498             =head1 METHODS
499              
500             This package implements the following methods:
501              
502             =cut
503              
504             =head2 attributes
505              
506             attributes() : Any
507              
508             This method registers and executes a subtest which tests the declared
509             attributes.
510              
511             =over 4
512              
513             =item attributes example #1
514              
515             # given: synopsis
516              
517             $subtests->attributes;
518              
519             =back
520              
521             =cut
522              
523             =head2 document
524              
525             document() : Any
526              
527             This method registers and executes a subtest which tests the test document
528             structure.
529              
530             =over 4
531              
532             =item document example #1
533              
534             # given: synopsis
535              
536             $subtests->document;
537              
538             =back
539              
540             =cut
541              
542             =head2 evaluator
543              
544             evaluator(Str $context) : Any
545              
546             This method evaluates (using C<eval>) the context given and returns the result
547             or raises an exception.
548              
549             =over 4
550              
551             =item evaluator example #1
552              
553             # given: synopsis
554              
555             my $context = '1 + 1';
556              
557             $subtests->evaluator($context); # 2
558              
559             =back
560              
561             =cut
562              
563             =head2 example
564              
565             example(Num $number, Str $name, Str $type, CodeRef $callback) : Any
566              
567             This method finds and evaluates (using C<eval>) the documented example and
568             returns a C<Test::Auto::Try> object (see L<Data::Object::Try>). The C<try>
569             object can be used to trap exceptions using the C<catch> method, and/or execute
570             the code and return the result using the C<result> method.
571              
572             =over 4
573              
574             =item example example #1
575              
576             # given: synopsis
577              
578             $subtests->example(1, 'evaluator', 'method', sub {
579             my ($tryable) = @_;
580              
581             ok my $result = $tryable->result, 'result ok';
582             is $result, 2, 'meta evaluator test ok';
583              
584             $result;
585             });
586              
587             =back
588              
589             =cut
590              
591             =head2 functions
592              
593             functions() : Any
594              
595             This method registers and executes a subtest which tests the declared
596             functions.
597              
598             =over 4
599              
600             =item functions example #1
601              
602             # given: synopsis
603              
604             $subtests->functions;
605              
606             =back
607              
608             =cut
609              
610             =head2 inherits
611              
612             inherits() : Any
613              
614             This method registers and executes a subtest which tests the declared
615             inheritances.
616              
617             =over 4
618              
619             =item inherits example #1
620              
621             # given: synopsis
622              
623             $subtests->inherits;
624              
625             =back
626              
627             =cut
628              
629             =head2 libraries
630              
631             libraries() : Any
632              
633             This method registers and executes a subtest which tests the declared
634             type libraries.
635              
636             =over 4
637              
638             =item libraries example #1
639              
640             # given: synopsis
641              
642             $subtests->libraries;
643              
644             =back
645              
646             =cut
647              
648             =head2 methods
649              
650             methods() : Any
651              
652             This method registers and executes a subtest which tests the declared
653             methods.
654              
655             =over 4
656              
657             =item methods example #1
658              
659             # given: synopsis
660              
661             $subtests->methods;
662              
663             =back
664              
665             =cut
666              
667             =head2 package
668              
669             package() : Any
670              
671             This method registers and executes a subtest which tests the declared
672             package.
673              
674             =over 4
675              
676             =item package example #1
677              
678             # given: synopsis
679              
680             $subtests->package;
681              
682             =back
683              
684             =cut
685              
686             =head2 plugin
687              
688             plugin(Str $name) : Object
689              
690             This method builds, tests, and returns a plugin object based on the name
691             provided.
692              
693             =over 4
694              
695             =item plugin example #1
696              
697             # given: synopsis
698              
699             $subtests->plugin('ShortDescription');
700              
701             =back
702              
703             =cut
704              
705             =head2 registry
706              
707             registry() : InstanceOf["Type::Registry"]
708              
709             This method returns a type registry object comprised of the types declare in
710             the declared type libraries.
711              
712             =over 4
713              
714             =item registry example #1
715              
716             # given: synopsis
717              
718             my $registry = $subtests->registry;
719              
720             =back
721              
722             =cut
723              
724             =head2 routines
725              
726             routines() : Any
727              
728             This method registers and executes a subtest which tests the declared
729             routines.
730              
731             =over 4
732              
733             =item routines example #1
734              
735             # given: synopsis
736              
737             $subtests->routines;
738              
739             =back
740              
741             =cut
742              
743             =head2 scenario
744              
745             scenario(Str $name, CodeRef $callback) : Any
746              
747             This method finds and evaluates (using C<eval>) the documented scenario example
748             and returns a C<Test::Auto::Try> object (see L<Data::Object::Try>). The C<try>
749             object can be used to trap exceptions using the C<catch> method, and/or execute
750             the code and return the result using the C<result> method.
751              
752             =over 4
753              
754             =item scenario example #1
755              
756             package main;
757              
758             use Test::Auto;
759              
760             my $test = Test::Auto->new(
761             't/Test_Auto.t'
762             );
763              
764             my $subtests = $test->subtests;
765              
766             $subtests->scenario('exports', sub {
767             my ($tryable) = @_;
768              
769             ok my $result = $tryable->result, 'result ok';
770              
771             $result;
772             });
773              
774             =back
775              
776             =cut
777              
778             =head2 standard
779              
780             standard() : Subtests
781              
782             This method is shorthand which registers and executes a series of other
783             standard subtests.
784              
785             =over 4
786              
787             =item standard example #1
788              
789             # given: synopsis
790              
791             # use:
792             $subtests->standard;
793              
794             # instead of:
795             # $self->package;
796             # $self->document;
797             # $self->libraries;
798             # $self->inherits;
799             # $self->attributes;
800             # $self->methods;
801             # $self->routines;
802             # $self->functions;
803             # $self->types;
804              
805             =back
806              
807             =cut
808              
809             =head2 synopsis
810              
811             synopsis(CodeRef $callback) : Any
812              
813             This method evaluates (using C<eval>) the documented synopsis and returns a
814             C<Test::Auto::Try> object (see L<Data::Object::Try>). The C<try> object can be
815             used to trap exceptions using the C<catch> method, and/or execute the code and
816             return the result using the C<result> method.
817              
818             =over 4
819              
820             =item synopsis example #1
821              
822             # given: synopsis
823              
824             $subtests->synopsis(sub {
825             my ($tryable) = @_;
826              
827             ok my $result = $tryable->result, 'result ok';
828             is ref($result), 'Test::Auto::Subtests', 'isa ok';
829              
830             $result;
831             });
832              
833             =back
834              
835             =cut
836              
837             =head2 tryable
838              
839             tryable(Any @arguments) : InstanceOf["Test::Auto::Try"]
840              
841             This method returns a tryable object which can be used to defer code execution
842             with a try/catch construct.
843              
844             =over 4
845              
846             =item tryable example #1
847              
848             # given: synopsis
849              
850             my $tryable = $subtests->tryable;
851              
852             $tryable->call(sub { $_[0] + 1 });
853              
854             # $tryable->result(1);
855             #> 2
856              
857             =back
858              
859             =over 4
860              
861             =item tryable example #2
862              
863             # given: synopsis
864              
865             my $tryable = $subtests->tryable(1);
866              
867             $tryable->call(sub { $_[0] + $_[1] });
868              
869             # $tryable->result(1);
870             #> 2
871              
872             =back
873              
874             =cut
875              
876             =head1 AUTHOR
877              
878             Al Newkirk, C<awncorp@cpan.org>
879              
880             =head1 LICENSE
881              
882             Copyright (C) 2011-2019, Al Newkirk, et al.
883              
884             This is free software; you can redistribute it and/or modify it under the terms
885             of the The Apache License, Version 2.0, as elucidated in the
886             L<"license file"|https://github.com/iamalnewkirk/test-auto/blob/master/LICENSE>.
887              
888             =head1 PROJECT
889              
890             L<Wiki|https://github.com/iamalnewkirk/test-auto/wiki>
891              
892             L<Project|https://github.com/iamalnewkirk/test-auto>
893              
894             L<Initiatives|https://github.com/iamalnewkirk/test-auto/projects>
895              
896             L<Milestones|https://github.com/iamalnewkirk/test-auto/milestones>
897              
898             L<Issues|https://github.com/iamalnewkirk/test-auto/issues>
899              
900             =cut