File Coverage

blib/lib/Test/Kit.pm
Criterion Covered Total %
statement 153 154 99.3
branch 23 24 95.8
condition 11 15 73.3
subroutine 24 24 100.0
pod 0 1 0.0
total 211 218 96.7


line stmt bran cond sub pod time code
1             package Test::Kit;
2             $Test::Kit::VERSION = '2.15';
3 23     23   33487 use strict;
  23         27  
  23         527  
4 23     23   76 use warnings;
  23         21  
  23         494  
5              
6 23     23   8025 use Import::Into;
  23         45187  
  23         563  
7 23     23   100 use Module::Runtime 'use_module', 'module_notional_filename';
  23         23  
  23         64  
8 23     23   8659 use Sub::Delete;
  23         13826  
  23         1065  
9 23     23   10571 use Test::Builder ();
  23         148289  
  23         439  
10 23     23   10106 use Test::More ();
  23         85914  
  23         489  
11 23     23   99 use Scalar::Util qw(refaddr);
  23         26  
  23         1754  
12 23     23   8482 use Hook::LexWrap qw(wrap);
  23         52041  
  23         95  
13              
14 23     23   8450 use parent 'Exporter';
  23         5191  
  23         118  
15             our @EXPORT = ('include');
16             # my %test_kits_cache = (
17             # 'MyTest::Awesome' => {
18             # 'ok' => { source => [ 'Test::More' ], refaddr => 0x1234, },
19             # 'pass' => { source => [ 'Test::Simple', 'Test::More' ], refaddr => 0xbeef, },
20             # 'warnings_are' => { source => [ 'Test::Warn' ], refaddr => 0xbead, },
21             # ...
22             # },
23             # ...
24             # )
25             #
26             my %test_kits_cache;
27              
28             sub include {
29 43     43 0 4722 my @to_include = @_;
30              
31 43         52 my $class = __PACKAGE__;
32              
33 43         38 my $include_hashref;
34 43 100       52 if (grep { ref($_) } @to_include) {
  56         159  
35 11         17 $include_hashref = { @to_include };
36             }
37             else {
38 32         47 $include_hashref = { map { $_ => {} } @to_include };
  34         86  
39             }
40              
41 43         88 return $class->_include($include_hashref);
42             }
43              
44             sub _include {
45 43     43   40 my $class = shift;
46 43         59 my $include_hashref = shift;
47              
48 43         64 my $target = $class->_get_package_to_import_into();
49              
50 43         75 $class->_make_target_a_test_more_like_exporter($target);
51              
52 42         117 for my $package (sort keys %$include_hashref) {
53             # special cases for strict and warnings on pre-1.3 Test::Builder
54             #
55             # The logic here is copied from Moose which always causes strict and
56             # warnings to be enabled when it is used.
57             #
58             # A comment in Moose::Exporter states:
59             #
60             # "this works because both pragmas set $^H (see perldoc perlvar) which
61             # affects the current compilation - i.e. the file who use'd us - which
62             # is why we don't need to do anything special to make it affect that
63             # file rather than this one (which is already compiled)"
64             #
65             # In the Moose code the author simply calls strict->import() in the
66             # appropriate import() method and that does the trick. For us working
67             # at a bit more of a distance we have to be a bit trickier - adding
68             # strict->import() or warnings->import() to the import method on the
69             # target class. We do that by wrapping it with Hook::LexWrap::wrap().
70             #
71 44 100 100     2484 if ($Test::Builder::VERSION < 1.3 && ($package eq 'strict' || $package eq 'warnings')) {
      33        
72 2     2   11 wrap "${target}::import", post => sub { $package->import(); };
  2         307  
73             }
74             else {
75 42         91 my $fake_package = $class->_create_fake_package($package, $include_hashref->{$package}, $target);
76 41         128 $fake_package->import::into($target);
77             }
78             }
79              
80 41         9436 $class->_update_target_exports($target);
81              
82 41         111 return;
83             }
84              
85             sub _get_package_to_import_into {
86 43     43   56 my $class = shift;
87              
88             # so, as far as I can tell, on Perl 5.14 and 5.16 at least, we have the
89             # following callstack...
90             #
91             # 1. Test::Kit
92             # 2. MyTest
93             # 3. main
94             # 4. main
95             # 5. main
96             #
97             # ... and we want to get the package name "MyTest" out of there.
98             # So let's look for the first non-Test::Kit result
99              
100 43         115 for my $i (1 .. 20) {
101 86         158 my $caller_package = (caller($i))[0];
102 86 100       2007 if ($caller_package ne $class) {
103 43         72 return $caller_package;
104             }
105             }
106              
107 0         0 die "Unable to find package to import into";
108             }
109              
110             sub _make_target_a_test_more_like_exporter {
111 43     43   37 my $class = shift;
112 43         33 my $target = shift;
113              
114 43 100       96 return if $test_kits_cache{$target};
115              
116 28         57 $class->_check_target_does_not_import($target);
117              
118             {
119 23     23   7519 no strict 'refs';
  23         32  
  23         3850  
  27         25  
120 27         23 push @{ "${target}::ISA" }, 'Test::Builder::Module';
  27         258  
121              
122             # need to explicitly do this so that if we need to wrap import()
123             # for strict and warnings includes it already exists at the right
124             # point.
125 27         54 *{ "${target}::import" } = \&Test::Builder::Module::import;
  27         70  
126             }
127              
128 27         632 $test_kits_cache{$target} = {};
129              
130 27         647 return;
131             }
132              
133             sub _create_fake_package {
134 42     42   1370 my $class = shift;
135 42         39 my $package = shift;
136 42         40 my $package_include_hashref = shift;
137 42         35 my $target = shift;
138              
139 42         676 my $fake_package = "Test::Kit::Fake::${target}::${package}";
140              
141 42         103 my $fake_package_file = module_notional_filename($fake_package);
142 42         1204 $INC{$fake_package_file} = 1;
143              
144 42 100       143 my %exclude = map { $_ => 1 } @{ $package_include_hashref->{exclude} || [] };
  40         47  
  42         259  
145 42 100       42 my %rename = %{ $package_include_hashref->{rename} || {} };
  42         171  
146 42 100       47 my @import = @{ $package_include_hashref->{import} || [] };
  42         148  
147              
148 42         94 use_module($package)->import::into($fake_package, @import);
149              
150             {
151 23     23   92 no strict 'refs';
  23         27  
  23         552  
  42         72315  
152 23     23   69 no warnings 'redefine';
  23         23  
  23         4839  
153              
154 42         44 push @{ "${fake_package}::ISA" }, 'Exporter';
  42         309  
155              
156 42         134 for my $from (sort keys %rename) {
157 10         344 my $to = $rename{$from};
158              
159 10         9 *{ "$fake_package\::$to" } = \&{ "$fake_package\::$from" };
  10         30  
  10         26  
160              
161 10         27 delete_sub("${fake_package}::$from");
162             }
163              
164 42         582 for my $exclude (sort keys %exclude) {
165 40         2384 delete_sub("${fake_package}::$exclude");
166             }
167              
168 42         1659 @{ "${fake_package}::EXPORT" } = $class->_get_exports_for($fake_package, $package, $target, \%rename);
  41         230  
169             }
170              
171 41         102 return $fake_package;
172             }
173              
174             sub _get_exports_for {
175 42     42   581 my $class = shift;
176 42         38 my $fake_package = shift;
177 42         36 my $package = shift;
178 42         1091 my $target = shift;
179 42         39 my $rename = shift;
180              
181             # Want to look at each item in the symbol table of
182             # the fake package, and see whether it's the same
183             # (according to refaddr) as the one that was in the
184             # included package. If it is then it was exported
185             # by the package into the fake package.
186             #
187             # We also store the refaddr so that we can check things which are identical
188             # between included packages, and not throw a collision exception in that
189             # case.
190 42         118 my %type_to_sigil = ( # please don't export IO or FORMAT! ;-)
191             SCALAR => '$',
192             ARRAY => '@',
193             HASH => '%',
194             CODE => '',
195             );
196 42 50       38 my %reverse_rename = reverse %{ $rename || {} };
  42         140  
197 42         37 my @package_exports;
198             {
199 23     23   87 no strict 'refs';
  23         22  
  23         5629  
  42         33  
200              
201 42         709 for my $glob (keys %{ "${fake_package}::" }) {
  42         211  
202              
203 916         609 my $fake_glob = $glob;
204 916   66     1895 my $real_glob = $reverse_rename{$glob} // $glob;
205              
206 916         1071 for my $type (keys %type_to_sigil) {
207 3662         2002 my $fake_refaddr = refaddr *{ "${fake_package}::${fake_glob}" }{$type};
  3662         7874  
208 3662         2109 my $real_refaddr = refaddr *{ "${package}::${real_glob}" }{$type};
  3662         5370  
209              
210 3662 100 66     9383 if ($fake_refaddr && $real_refaddr && $fake_refaddr == $real_refaddr) {
      100        
211 832         1286 my $export = sprintf("%s%s", $type_to_sigil{$type}, $fake_glob);
212 832         743 push @package_exports, $export;
213              
214             # handle cache and collision checking
215 832         469 push @{ $test_kits_cache{$target}{$export}{source} }, $package;
  832         1588  
216 832 100       1054 if (my $existing_refaddr = $test_kits_cache{$target}{$export}{refaddr}) {
217 53 100       81 if ($existing_refaddr != $real_refaddr) {
218             die sprintf("Subroutine %s() already supplied to %s by %s",
219             $export,
220             $target,
221 1         21 $test_kits_cache{$target}{$export}{source}[0],
222             );
223             }
224             }
225             else {
226 779         1030 $test_kits_cache{$target}{$export}{refaddr} = $real_refaddr;
227             }
228             }
229             }
230             }
231             }
232              
233 41         237 return @package_exports;
234             }
235              
236             sub _check_target_does_not_import {
237 28     28   26 my $class = shift;
238 28         25 my $target = shift;
239              
240 28 100       300 if ($target->can('import')) {
241 1         20 die "Package $target already has an import() sub";
242             }
243              
244 27         32 return;
245             }
246              
247             sub _update_target_exports {
248 41     41   50 my $class = shift;
249 41         31 my $target = shift;
250              
251 41         36 my @exports = sort keys %{ $test_kits_cache{$target} };
  41         506  
252              
253             {
254 23     23   89 no strict 'refs';
  23         31  
  23         1200  
  41         66  
255 41         34 @{ "$target\::EXPORT" } = @exports;
  41         216  
256             }
257              
258 41         100 return;
259             }
260              
261             1;
262              
263             __END__