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 33     33   192 use strict;
  33         237  
  33         845  
6 33     33   145 use warnings;
  33         93  
  33         657  
7 33     33   139 use namespace::autoclean;
  33         72  
  33         193  
8              
9             our $VERSION = '0.98';
10              
11 33     33   2091 use Carp;
  33         70  
  33         1711  
12 33     33   183 use Class::MOP;
  33         68  
  33         912  
13 33     33   13453 use List::SomeUtils qw( any uniq );
  33         112554  
  33         36631  
14              
15             my %BY_METHOD = (
16             tags => {}, # {$method}{$test_class}
17             plans => {},
18             );
19              
20             sub add_plan {
21 8     8 1 18 my ( undef, $test_class, $method, $plan ) = @_;
22 8 100       18 if ( defined $plan ) {
23 6         13 $plan =~ s/\D//g;
24 6 50       17 undef $plan unless $plan =~ /\d/; # no_plan
25             }
26 8         136 $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 4192     4192 1 6567 my ( undef, $test_class, $method ) = @_;
36 4192         16538 return exists $BY_METHOD{plans}{$method}{$test_class};
37             }
38              
39             sub add_tags {
40 25     25 1 59 my ( $class, $test_class, $method, $tags ) = @_;
41              
42 25         42 my @tags_copy = @{$tags};
  25         60  
43              
44             # check for additions or deletions to the inherited tag list
45 25 100   29   142 if ( any {/^[-+]/} @tags_copy ) {
  29         210  
46 1         5 @tags_copy = $class->_augment_tags( $test_class, $method, $tags );
47             }
48              
49 25         91 foreach my $tag (@tags_copy) {
50 30 50       154 if ( $tag !~ /^\w+$/ ) {
51 0         0 die "tags must be alphanumeric\n";
52             }
53             }
54              
55             # dedupe tags
56 25         54 my %tags = map { $_ => 1 } @tags_copy;
  30         115  
57              
58 25 50 66     87 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         561 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 7 my ( $class, $test_class, $tag ) = @_;
83              
84 2 50       6 croak("no class specified") if not defined $test_class;
85 2 50       5 croak("no tag specified") if not defined $tag;
86              
87             # XXX a naïve implementation, but it does the job for now.
88 2         12 my $test_class_meta = Class::MOP::Class->initialize($test_class);
89 2         36 foreach my $method ( $test_class_meta->get_all_method_names ) {
90 51 100       596 next unless $method =~ /test_/;
91 25 100       36 return 1 if $class->method_has_tag( $test_class, $method, $tag );
92             }
93 1         7 return;
94             }
95              
96             sub method_has_tag {
97 425     425 1 3773 my ( $class, $test_class, $method, $tag ) = @_;
98              
99 425 50       676 croak("no class specified") if not defined $test_class;
100 425 50       701 croak("no method specified") if not defined $method;
101 425 50       592 croak("no tag specified") if not defined $tag;
102              
103             # avoid auto-vivication
104 425 100       1313 return if not exists $BY_METHOD{tags}{$method};
105              
106 102 100       196 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         9 $BY_METHOD{tags}{$method}{$test_class}
111             = $class->_superclass_tags( $test_class, $method );
112             }
113              
114 102         360 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       9 croak("no class specified") if not defined $test_class;
121 5 50       9 croak("no method specified") if not defined $method;
122              
123 5 50       11 return {} if not exists $BY_METHOD{tags}{$method};
124              
125 5         14 my $test_class_meta = Class::MOP::Class->initialize($test_class);
126 5         56 my $method_meta;
127              
128 5 50       22 $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       460 if ( !$method_meta ) {
132              
133             #Might be a from a role or this class
134 1         5 my $mm = $test_class_meta->find_method_by_name($method);
135 1         47 my $orig = $mm->original_method;
136              
137 1 50 33     5 if ( $orig && ( $mm->package_name ne $orig->package_name ) ) {
138 1         11 $method_meta = $orig;
139             }
140             }
141              
142             # no method, so no tags to inherit
143 5 50       24 return {} if not $method_meta;
144              
145 5         30 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         20 my %tags = map { $_ => 1 }
151 5         7 keys %{ $BY_METHOD{tags}{$method}{$super_test_class} };
  5         17  
152 5         30 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         4 my $tag_list = $class->_superclass_tags( $test_class, $method );
167              
168 1         2 for my $tag_definition ( @{$tags} ) {
  1         3  
169 2         5 my $direction = substr( $tag_definition, 0, 1 );
170 2         4 my $tag = substr( $tag_definition, 1 );
171 2 100       6 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         4 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         2 return keys %{$tag_list};
  1         5  
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.98
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