File Coverage

blib/lib/Test/Kit.pm
Criterion Covered Total %
statement 153 156 98.0
branch 22 24 91.6
condition 13 21 61.9
subroutine 23 24 95.8
pod 0 1 0.0
total 211 226 93.3


line stmt bran cond sub pod time code
1             package Test::Kit;
2             $Test::Kit::VERSION = '2.16';
3 24     24   56473 use strict;
  24         170  
  24         695  
4 24     24   120 use warnings;
  24         53  
  24         595  
5              
6 24     24   10842 use Import::Into;
  24         66636  
  24         770  
7 24     24   179 use Module::Runtime 'use_module', 'module_notional_filename';
  24         56  
  24         116  
8 24     24   11370 use Sub::Delete;
  24         23404  
  24         1511  
9 24     24   13361 use Test::Builder ();
  24         1260328  
  24         663  
10 24     24   14173 use Test::More ();
  24         129426  
  24         693  
11 24     24   174 use Scalar::Util qw(refaddr);
  24         49  
  24         1317  
12 24     24   11833 use Hook::LexWrap qw(wrap);
  24         78715  
  24         143  
13              
14 24     24   10822 use parent 'Exporter';
  24         7075  
  24         158  
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 45     45 0 7055 my @to_include = @_;
30              
31 45         96 my $class = __PACKAGE__;
32              
33 45         69 my $include_hashref;
34 45 100       103 if (grep { ref($_) } @to_include) {
  58         228  
35 11         35 $include_hashref = { @to_include };
36             }
37             else {
38 34         73 $include_hashref = { map { $_ => {} } @to_include };
  36         139  
39             }
40              
41 45         161 return $class->_include($include_hashref);
42             }
43              
44             sub _include {
45 45     45   77 my $class = shift;
46 45         83 my $include_hashref = shift;
47              
48 45         165 my $target = $class->_get_package_to_import_into();
49              
50 45         395 $class->_make_target_a_test_more_like_exporter($target);
51              
52 44         228 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 46 50 0     888 if ($Test::Builder::VERSION < 1.3 && ($package eq 'strict' || $package eq 'warnings')) {
      33        
72 0     0   0 wrap "${target}::import", post => sub { $package->import(); };
  0         0  
73             }
74             else {
75 46         130 my $fake_package = $class->_create_fake_package($package, $include_hashref->{$package}, $target);
76 45         180 $fake_package->import::into($target);
77             }
78             }
79              
80 43         14777 $class->_update_target_exports($target);
81              
82 43         170 return;
83             }
84              
85             sub _get_package_to_import_into {
86 45     45   80 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 45         165 for my $i (1 .. 20) {
101 90         242 my $caller_package = (caller($i))[0];
102 90 100       3597 if ($caller_package ne $class) {
103 45         117 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 45     45   155 my $class = shift;
112 45         67 my $target = shift;
113              
114 45 100       163 return if $test_kits_cache{$target};
115              
116 29         85 $class->_check_target_does_not_import($target);
117              
118             {
119 24     24   11038 no strict 'refs';
  24         93  
  24         5993  
  28         44  
120 28         48 push @{ "${target}::ISA" }, 'Test::Builder::Module';
  28         373  
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 28         94 *{ "${target}::import" } = \&Test::Builder::Module::import;
  28         107  
126             }
127              
128 28         74 $test_kits_cache{$target} = {};
129              
130 28         59 return;
131             }
132              
133             sub _create_fake_package {
134 46     46   74 my $class = shift;
135 46         78 my $package = shift;
136 46         64 my $package_include_hashref = shift;
137 46         63 my $target = shift;
138              
139 46         136 my $fake_package = "Test::Kit::Fake::${target}::${package}";
140              
141 46         206 my $fake_package_file = module_notional_filename($fake_package);
142 46         2081 $INC{$fake_package_file} = 1;
143              
144 46 100       77 my %exclude = map { $_ => 1 } @{ $package_include_hashref->{exclude} || [] };
  40         82  
  46         257  
145 46 100       87 my %rename = %{ $package_include_hashref->{rename} || {} };
  46         220  
146 46 100       103 my @import = @{ $package_include_hashref->{import} || [] };
  46         269  
147              
148 46         163 use_module($package)->import::into($fake_package, @import);
149              
150             {
151 24     24   200 no strict 'refs';
  24         54  
  24         928  
  46         94950  
152 24     24   156 no warnings 'redefine';
  24         91  
  24         7632  
153              
154 46         95 push @{ "${fake_package}::ISA" }, 'Exporter';
  46         547  
155              
156 46         184 for my $from (sort keys %rename) {
157 10         570 my $to = $rename{$from};
158              
159 10         14 *{ "$fake_package\::$to" } = \&{ "$fake_package\::$from" };
  10         43  
  10         38  
160              
161 10         42 delete_sub("${fake_package}::$from");
162             }
163              
164 46         981 for my $exclude (sort keys %exclude) {
165 40         3915 delete_sub("${fake_package}::$exclude");
166             }
167              
168 46         803 @{ "${fake_package}::EXPORT" } = $class->_get_exports_for($fake_package, $package, $target, \%rename);
  45         306  
169             }
170              
171 45         158 return $fake_package;
172             }
173              
174             sub _get_exports_for {
175 46     46   121 my $class = shift;
176 46         86 my $fake_package = shift;
177 46         68 my $package = shift;
178 46         68 my $target = shift;
179 46         67 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 46         177 my %type_to_sigil = ( # please don't export IO or FORMAT! ;-)
191             SCALAR => '$',
192             ARRAY => '@',
193             HASH => '%',
194             CODE => '',
195             );
196 46 50       70 my %reverse_rename = reverse %{ $rename || {} };
  46         175  
197 46         78 my @package_exports;
198             {
199 24     24   193 no strict 'refs';
  24         80  
  24         9901  
  46         62  
200              
201 46         66 for my $glob (keys %{ "${fake_package}::" }) {
  46         300  
202              
203 961         1273 my $fake_glob = $glob;
204 961   66     2362 my $real_glob = $reverse_rename{$glob} // $glob;
205              
206 961         1868 for my $type (keys %type_to_sigil) {
207 3843         4355 my $fake_refaddr = refaddr *{ "${fake_package}::${fake_glob}" }{$type};
  3843         10946  
208 3843         4783 my $real_refaddr = refaddr *{ "${package}::${real_glob}" }{$type};
  3843         7915  
209              
210 3843 100 66     10896 if ($fake_refaddr && $real_refaddr && $fake_refaddr == $real_refaddr) {
      100        
211 869         2127 my $export = sprintf("%s%s", $type_to_sigil{$type}, $fake_glob);
212 869         1571 push @package_exports, $export;
213              
214             # handle cache and collision checking
215 869         989 push @{ $test_kits_cache{$target}{$export}{source} }, $package;
  869         2731  
216 869 100       1802 if (my $existing_refaddr = $test_kits_cache{$target}{$export}{refaddr}) {
217 53 100       104 if ($existing_refaddr != $real_refaddr) {
218             die sprintf("Subroutine %s() already supplied to %s by %s",
219             $export,
220             $target,
221 1         35 $test_kits_cache{$target}{$export}{source}[0],
222             );
223             }
224             }
225             else {
226 816         1748 $test_kits_cache{$target}{$export}{refaddr} = $real_refaddr;
227             }
228             }
229             }
230             }
231             }
232              
233 45         364 return @package_exports;
234             }
235              
236             sub _check_target_does_not_import {
237 29     29   48 my $class = shift;
238 29         41 my $target = shift;
239              
240 29         328 my $import = $target->can('import');
241 29         133 my $uniimport = UNIVERSAL->can('import');
242 29 100 66     130 if ($import && !($uniimport && $import == $uniimport)) {
      100        
243 1         24 die "Package $target already has an import() sub";
244             }
245              
246 28         56 return;
247             }
248              
249             sub _update_target_exports {
250 43     43   89 my $class = shift;
251 43         64 my $target = shift;
252              
253 43         75 my @exports = sort keys %{ $test_kits_cache{$target} };
  43         777  
254              
255             {
256 24     24   236 no strict 'refs';
  24         67  
  24         2497  
  43         120  
257 43         74 @{ "$target\::EXPORT" } = @exports;
  43         290  
258             }
259              
260 43         124 return;
261             }
262              
263             1;
264              
265             __END__