File Coverage

blib/lib/Test/Class/Moose/AttributeRegistry.pm
Criterion Covered Total %
statement 86 99 86.8
branch 34 52 65.3
condition 3 6 50.0
subroutine 14 16 87.5
pod 7 7 100.0
total 144 180 80.0


line stmt bran cond sub pod time code
1             package Test::Class::Moose::AttributeRegistry;
2              
3             ## ABSTRACT: Global registry of tags by class and method.
4              
5 32     32   181 use strict;
  32         260  
  32         821  
6 32     32   154 use warnings;
  32         67  
  32         682  
7 32     32   141 use namespace::autoclean;
  32         76  
  32         208  
8              
9             our $VERSION = '0.97';
10              
11 32     32   2142 use Carp;
  32         63  
  32         1552  
12 32     32   184 use Class::MOP;
  32         62  
  32         918  
13 32     32   8770 use List::SomeUtils qw( any uniq );
  32         103309  
  32         35750  
14              
15             my %BY_METHOD = (
16             tags => {}, # {$method}{$test_class}
17             plans => {},
18             );
19              
20             sub add_plan {
21 8     8 1 16 my ( undef, $test_class, $method, $plan ) = @_;
22 8 100       18 if ( defined $plan ) {
23 6         12 $plan =~ s/\D//g;
24 6 50       17 undef $plan unless $plan =~ /\d/; # no_plan
25             }
26 8         133 $BY_METHOD{plans}{$method}{$test_class} = $plan;
27             }
28              
29             sub get_plan {
30 0     0 1 0 my ( undef, $test_class, $method ) = @_;
31 0         0 return $BY_METHOD{plans}{$method}{$test_class};
32             }
33              
34             sub has_test_attribute {
35 4172     4172 1 6444 my ( undef, $test_class, $method ) = @_;
36 4172         13368 return exists $BY_METHOD{plans}{$method}{$test_class};
37             }
38              
39             sub add_tags {
40 25     25 1 74 my ( $class, $test_class, $method, $tags ) = @_;
41              
42 25         41 my @tags_copy = @{$tags};
  25         60  
43              
44             # check for additions or deletions to the inherited tag list
45 25 100   29   153 if ( any {/^[-+]/} @tags_copy ) {
  29         107  
46 1         7 @tags_copy = $class->_augment_tags( $test_class, $method, $tags );
47             }
48              
49 25         94 foreach my $tag (@tags_copy) {
50 30 50       135 if ( $tag !~ /^\w+$/ ) {
51 0         0 die "tags must be alphanumeric\n";
52             }
53             }
54              
55             # dedupe tags
56 25         57 my %tags = map { $_ => 1 } @tags_copy;
  30         98  
57              
58 25 50 66     100 if ( exists $BY_METHOD{tags}{$method}
59             && exists $BY_METHOD{tags}{$method}{$test_class} )
60             {
61 0         0 die
62             "tags for $test_class->$method already exists, method redefinition perhaps?\n";
63             }
64              
65 25         69 $BY_METHOD{tags}{$method}{$test_class} = \%tags;
66              
67 25         452 return;
68             }
69              
70             sub tags {
71 0     0 1 0 my @tags;
72 0         0 for my $method ( keys %BY_METHOD ) {
73 0         0 for my $test_class ( keys %{ $BY_METHOD{tags}{$method} } ) {
  0         0  
74 0         0 push @tags, keys %{ $BY_METHOD{tags}{$method}{$test_class} };
  0         0  
75             }
76             }
77              
78 0         0 return sort( uniq(@tags) );
79             }
80              
81             sub class_has_tag {
82 2     2 1 6 my ( $class, $test_class, $tag ) = @_;
83              
84 2 50       7 croak("no class specified") if not defined $test_class;
85 2 50       6 croak("no tag specified") if not defined $tag;
86              
87             # XXX a naïve implementation, but it does the job for now.
88 2         14 my $test_class_meta = Class::MOP::Class->initialize($test_class);
89 2         34 foreach my $method ( $test_class_meta->get_all_method_names ) {
90 44 100       558 next unless $method =~ /test_/;
91 22 100       37 return 1 if $class->method_has_tag( $test_class, $method, $tag );
92             }
93 1         8 return;
94             }
95              
96             sub method_has_tag {
97 422     422 1 4113 my ( $class, $test_class, $method, $tag ) = @_;
98              
99 422 50       678 croak("no class specified") if not defined $test_class;
100 422 50       698 croak("no method specified") if not defined $method;
101 422 50       570 croak("no tag specified") if not defined $tag;
102              
103             # avoid auto-vivication
104 422 100       1451 return if not exists $BY_METHOD{tags}{$method};
105              
106 101 100       246 if ( not exists $BY_METHOD{tags}{$method}{$test_class} ) {
107              
108             # If this method has no tag data at all, then inherit the tags from
109             # from the superclass
110 4         10 $BY_METHOD{tags}{$method}{$test_class}
111             = $class->_superclass_tags( $test_class, $method );
112             }
113              
114 101         454 return exists $BY_METHOD{tags}{$method}{$test_class}{$tag};
115             }
116              
117             sub _superclass_tags {
118 5     5   12 my ( $class, $test_class, $method ) = @_;
119              
120 5 50       10 croak("no class specified") if not defined $test_class;
121 5 50       11 croak("no method specified") if not defined $method;
122              
123 5 50       10 return {} if not exists $BY_METHOD{tags}{$method};
124              
125 5         17 my $test_class_meta = Class::MOP::Class->initialize($test_class);
126 5         62 my $method_meta;
127              
128 5 50       27 $method_meta = $test_class_meta->find_next_method_by_name($method)
129             if $test_class_meta->can('find_next_method_by_name');
130              
131 5 100       520 if ( !$method_meta ) {
132              
133             #Might be a from a role or this class
134 1         6 my $mm = $test_class_meta->find_method_by_name($method);
135 1         49 my $orig = $mm->original_method;
136              
137 1 50 33     7 if ( $orig && ( $mm->package_name ne $orig->package_name ) ) {
138 1         12 $method_meta = $orig;
139             }
140             }
141              
142             # no method, so no tags to inherit
143 5 50       26 return {} if not $method_meta;
144              
145 5         37 my $super_test_class = $method_meta->package_name();
146 5 50       15 if ( exists $BY_METHOD{tags}{$method}{$super_test_class} ) {
147              
148             # shallow copy the superclass method's tags, because it's possible to
149             # change add/remove items from the subclass's list later
150 7         21 my %tags = map { $_ => 1 }
151 5         9 keys %{ $BY_METHOD{tags}{$method}{$super_test_class} };
  5         19  
152 5         18 return \%tags;
153             }
154              
155             # nothing defined at this level, recurse
156 0         0 return $class->_superclass_tags( $super_test_class, $method );
157             }
158              
159             sub _augment_tags {
160 1     1   4 my ( $class, $test_class, $method, $tags ) = @_;
161              
162 1 50       4 croak("no class specified") if not defined $test_class;
163 1 50       4 croak("no method specified") if not defined $method;
164              
165             # Get the base list from the superclass
166 1         6 my $tag_list = $class->_superclass_tags( $test_class, $method );
167              
168 1         3 for my $tag_definition ( @{$tags} ) {
  1         2  
169 2         6 my $direction = substr( $tag_definition, 0, 1 );
170 2         3 my $tag = substr( $tag_definition, 1 );
171 2 100       8 if ( $direction eq '+' ) {
    50          
172 1         3 $tag_list->{$tag} = 1;
173             }
174             elsif ( $direction eq '-' ) {
175              
176             # die here if the tag wasn't inherited?
177 1         11 delete $tag_list->{$tag};
178             }
179             else {
180 0         0 die
181             "$test_class->$method attempting to override and modify tags, did you forget a '+'?\n";
182             }
183             }
184              
185 1         3 return keys %{$tag_list};
  1         6  
186             }
187              
188             1;
189              
190             __END__
191              
192             =pod
193              
194             =encoding UTF-8
195              
196             =head1 NAME
197              
198             Test::Class::Moose::AttributeRegistry - Global registry of tags by class and method.
199              
200             =head1 VERSION
201              
202             version 0.97
203              
204             =head1 SYNOPSIS
205              
206             use Test::Class::Moose::AttributeRegistry;
207              
208             my @tags = Test::Class::Moose::AttributeRegistry->tags;
209             print Test::Class::Moose::AttributeRegistry->method_has_tag( 'TestsFor::FooBar', 'test_baz', 'network' );
210              
211             =head1 DESCRIPTION
212              
213             This class permits addition and querying of the tags and plans defined on
214             methods via attributes. It's been gleefully stolen from
215             L<Attribute::Method::Tags> and is for internal use only. Don't rely on this
216             code.
217              
218             =head1 METHODS
219              
220             All the following are class methods, as the attribute registry is shared
221             globally. Note that all parameters for any of the methods below are required.
222              
223             =over 4
224              
225             =item add_plan( $class, $method, plan )
226              
227             Add a numeric (or undef) plan to a method.
228              
229             =item get_plan( $class, $method )
230              
231             Returns the numeric (or undef) plan for a method if that was set via the
232             C<Test> or C<Tests> attributes.
233              
234             =item has_test_attribute( $class, $method )
235              
236             Returns true if either C<Test> or C<Tests> was declared for a method. Used to
237             identify something as a test method even if the method name doesn't begin with
238             C<test_>.
239              
240             =item add_tags( $class, $method, $tags_ref )
241              
242             Adds the given list of tags (as an array-ref) for the specified class/method
243             combination. An exception will be raised if either the tags are
244             non-alphanumeric or the method is one that has already had tags registered
245             for it.
246              
247             =item tags
248              
249             Find all tags defined for all methods. Returns a sorted list of tags.
250              
251             =item class_has_tag( $class, $tag )
252              
253             Returns a boolean (0|1) indicating whether the given class has any method with
254             the specified tag.
255              
256             =item method_has_tag( $class, $method, $tag )
257              
258             Returns a boolean (0|1) indicating whether the given method in the given class
259             has the specified tag.
260              
261             =back
262              
263             =head1 SEE ALSO
264              
265             =over 4
266              
267             =item L<Attribute::Method::Tags>
268              
269             Attribute-based interface for adding tags to methods. Your author "liberated"
270             this code from L<Attribute::Method::Tags::Registry> (with a tip 'o the
271             keyboard to Mark Morgan for his work on this).
272              
273             =back
274              
275             =head1 SUPPORT
276              
277             Bugs may be submitted at L<https://github.com/houseabsolute/test-class-moose/issues>.
278              
279             I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
280              
281             =head1 SOURCE
282              
283             The source code repository for Test-Class-Moose can be found at L<https://github.com/houseabsolute/test-class-moose>.
284              
285             =head1 AUTHORS
286              
287             =over 4
288              
289             =item *
290              
291             Curtis "Ovid" Poe <ovid@cpan.org>
292              
293             =item *
294              
295             Dave Rolsky <autarch@urth.org>
296              
297             =back
298              
299             =head1 COPYRIGHT AND LICENSE
300              
301             This software is copyright (c) 2012 - 2019 by Curtis "Ovid" Poe.
302              
303             This is free software; you can redistribute it and/or modify it under
304             the same terms as the Perl 5 programming language system itself.
305              
306             The full text of the license can be found in the
307             F<LICENSE> file included with this distribution.
308              
309             =cut