File Coverage

blib/lib/Test/Kit.pm
Criterion Covered Total %
statement 153 154 99.3
branch 23 24 95.8
condition 10 12 83.3
subroutine 24 24 100.0
pod 0 1 0.0
total 210 215 97.6


line stmt bran cond sub pod time code
1             package Test::Kit;
2             $Test::Kit::VERSION = '2.14';
3 25     25   69590 use strict;
  25         47  
  25         623  
4 25     25   135 use warnings;
  25         41  
  25         621  
5              
6 25     25   17178 use Import::Into;
  25         75639  
  25         837  
7 25     25   155 use Module::Runtime 'use_module', 'module_notional_filename';
  25         47  
  25         115  
8 25     25   17789 use Sub::Delete;
  25         21177  
  25         1358  
9 25     25   20248 use Test::Builder ();
  25         235366  
  25         593  
10 25     25   19673 use Test::More ();
  25         129526  
  25         597  
11 25     25   163 use Scalar::Util qw(refaddr);
  25         43  
  25         2579  
12 25     25   17791 use Hook::LexWrap qw(wrap);
  25         84452  
  25         156  
13              
14 25     25   17784 use parent 'Exporter';
  25         7684  
  25         174  
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 49     49 0 6553 my @to_include = @_;
30              
31 49         87 my $class = __PACKAGE__;
32              
33 49         67 my $include_hashref;
34 49 100       97 if (grep { ref($_) } @to_include) {
  64         250  
35 13         36 $include_hashref = { @to_include };
36             }
37             else {
38 36         65 $include_hashref = { map { $_ => {} } @to_include };
  38         149  
39             }
40              
41 49         160 return $class->_include($include_hashref);
42             }
43              
44             sub _include {
45 49     49   81 my $class = shift;
46 49         105 my $include_hashref = shift;
47              
48 49         162 my $target = $class->_get_package_to_import_into();
49              
50 49         145 $class->_make_target_a_test_more_like_exporter($target);
51              
52 48         180 for my $package (sort keys %$include_hashref) {
53             # special cases for strict and warnings
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             # TODO see whether any other pragmata need to be added to the list
72             # along with strict and warnings.
73             #
74 50 100 100     1996 if ($package eq 'strict' || $package eq 'warnings') {
75 2     2   15 wrap "${target}::import", post => sub { $package->import(); };
  2         474  
76             }
77             else {
78 48         1201 my $fake_package = $class->_create_fake_package($package, $include_hashref->{$package}, $target);
79 47         204 $fake_package->import::into($target);
80             }
81             }
82              
83 47         16234 $class->_update_target_exports($target);
84              
85 47         205 return;
86             }
87              
88             sub _get_package_to_import_into {
89 49     49   77 my $class = shift;
90              
91             # so, as far as I can tell, on Perl 5.14 and 5.16 at least, we have the
92             # following callstack...
93             #
94             # 1. Test::Kit
95             # 2. MyTest
96             # 3. main
97             # 4. main
98             # 5. main
99             #
100             # ... and we want to get the package name "MyTest" out of there.
101             # So let's look for the first non-Test::Kit result
102              
103 49         145 for my $i (1 .. 20) {
104 98         295 my $caller_package = (caller($i))[0];
105 98 100       3705 if ($caller_package ne $class) {
106 49         129 return $caller_package;
107             }
108             }
109              
110 0         0 die "Unable to find package to import into";
111             }
112              
113             sub _make_target_a_test_more_like_exporter {
114 49     49   137 my $class = shift;
115 49         79 my $target = shift;
116              
117 49 100       165 return if $test_kits_cache{$target};
118              
119 30         92 $class->_check_target_does_not_import($target);
120              
121             {
122 25     25   11608 no strict 'refs';
  25         51  
  25         5881  
  29         68  
123 29         49 push @{ "${target}::ISA" }, 'Test::Builder::Module';
  29         1478  
124              
125             # need to explicitly do this so that if we need to wrap import()
126             # for strict and warnings includes it already exists at the right
127             # point.
128 29         1245 *{ "${target}::import" } = \&Test::Builder::Module::import;
  29         1104  
129             }
130              
131 29         73 $test_kits_cache{$target} = {};
132              
133 29         1229 return;
134             }
135              
136             sub _create_fake_package {
137 48     48   85 my $class = shift;
138 48         1172 my $package = shift;
139 48         69 my $package_include_hashref = shift;
140 48         73 my $target = shift;
141              
142 48         1204 my $fake_package = "Test::Kit::Fake::${target}::${package}";
143              
144 48         174 my $fake_package_file = module_notional_filename($fake_package);
145 48         1846 $INC{$fake_package_file} = 1;
146              
147 48 100       232 my %exclude = map { $_ => 1 } @{ $package_include_hashref->{exclude} || [] };
  42         93  
  48         358  
148 48 100       93 my %rename = %{ $package_include_hashref->{rename} || {} };
  48         332  
149 48 100       96 my @import = @{ $package_include_hashref->{import} || [] };
  48         244  
150              
151 48         1317 use_module($package)->import::into($fake_package, @import);
152              
153             {
154 25     25   139 no strict 'refs';
  25         53  
  25         845  
  48         148675  
155 25     25   130 no warnings 'redefine';
  25         46  
  25         8056  
156              
157 48         1146 push @{ "${fake_package}::ISA" }, 'Exporter';
  48         526  
158              
159 48         185 for my $from (sort keys %rename) {
160 12         474 my $to = $rename{$from};
161              
162 12         19 *{ "$fake_package\::$to" } = \&{ "$fake_package\::$from" };
  12         65  
  12         50  
163              
164 12         59 delete_sub("${fake_package}::$from");
165             }
166              
167 48         1121 for my $exclude (sort keys %exclude) {
168 42         3589 delete_sub("${fake_package}::$exclude");
169             }
170              
171 48         996 @{ "${fake_package}::EXPORT" } = $class->_get_exports_for($fake_package, $package, $target, \%rename);
  47         439  
172             }
173              
174 47         195 return $fake_package;
175             }
176              
177             sub _get_exports_for {
178 48     48   1174 my $class = shift;
179 48         76 my $fake_package = shift;
180 48         71 my $package = shift;
181 48         70 my $target = shift;
182 48         74 my $rename = shift;
183              
184             # Want to look at each item in the symbol table of
185             # the fake package, and see whether it's the same
186             # (according to refaddr) as the one that was in the
187             # included package. If it is then it was exported
188             # by the package into the fake package.
189             #
190             # We also store the refaddr so that we can check things which are identical
191             # between included packages, and not throw a collision exception in that
192             # case.
193 48         238 my %type_to_sigil = ( # please don't export IO or FORMAT! ;-)
194             SCALAR => '$',
195             ARRAY => '@',
196             HASH => '%',
197             CODE => '',
198             );
199 48 50       71 my %reverse_rename = reverse %{ $rename || {} };
  48         1268  
200 48         76 my @package_exports;
201             {
202 25     25   146 no strict 'refs';
  25         42  
  25         9741  
  48         1189  
203              
204 48         65 for my $glob (keys %{ "${fake_package}::" }) {
  48         1551  
205              
206 1002         1259 my $fake_glob = $glob;
207 1002   66     4527 my $real_glob = $reverse_rename{$glob} // $glob;
208              
209 1002         2162 for my $type (keys %type_to_sigil) {
210 4005         4277 my $fake_refaddr = refaddr *{ "${fake_package}::${fake_glob}" }{$type};
  4005         15779  
211 4005         5772 my $real_refaddr = refaddr *{ "${package}::${real_glob}" }{$type};
  4005         10914  
212              
213 4005 100 66     17785 if ($fake_refaddr && $real_refaddr && $fake_refaddr == $real_refaddr) {
      100        
214 904         3415 my $export = sprintf("%s%s", $type_to_sigil{$type}, $fake_glob);
215 904         1658 push @package_exports, $export;
216              
217             # handle cache and collision checking
218 904         2123 push @{ $test_kits_cache{$target}{$export}{source} }, $package;
  904         3300  
219 904 100       2208 if (my $existing_refaddr = $test_kits_cache{$target}{$export}{refaddr}) {
220 53 100       137 if ($existing_refaddr != $real_refaddr) {
221             die sprintf("Subroutine %s() already supplied to %s by %s",
222             $export,
223             $target,
224 1         47 $test_kits_cache{$target}{$export}{source}[0],
225             );
226             }
227             }
228             else {
229 851         2303 $test_kits_cache{$target}{$export}{refaddr} = $real_refaddr;
230             }
231             }
232             }
233             }
234             }
235              
236 47         417 return @package_exports;
237             }
238              
239             sub _check_target_does_not_import {
240 30     30   53 my $class = shift;
241 30         50 my $target = shift;
242              
243 30 100       443 if ($target->can('import')) {
244 1         32 die "Package $target already has an import() sub";
245             }
246              
247 29         63 return;
248             }
249              
250             sub _update_target_exports {
251 47     47   89 my $class = shift;
252 47         68 my $target = shift;
253              
254 47         71 my @exports = sort keys %{ $test_kits_cache{$target} };
  47         875  
255              
256             {
257 25     25   145 no strict 'refs';
  25         56  
  25         1795  
  47         136  
258 47         87 @{ "$target\::EXPORT" } = @exports;
  47         553  
259             }
260              
261 47         191 return;
262             }
263              
264             1;
265              
266             __END__
267              
268             =head1 NAME
269              
270             Test::Kit - Build custom test packages with only the features you want
271              
272             =head1 DESCRIPTION
273              
274             Test::Kit allows you to create a single module in your project which gives you
275             access to all of the testing functions you want.
276              
277             Its primary goal is to reduce boilerplate code that is currently littering the
278             top of all your test files.
279              
280             It also allows your testing to be more consistent; for example it becomes a
281             trivial change to include Test::FailWarnings in all of your tests, and there is
282             no danger that you forget to include it in a new test.
283              
284             =head1 VERSION
285              
286             Test::Kit 2.0 is a complete rewrite of Test::Kit by a new author.
287              
288             It serves much the same purpose as the original Test::Kit, but comes with a
289             completely new interface and some serious bugs ironed out.
290              
291             The 'features' such as '+explain' and '+on_fail' have been removed. If you were
292             using these please contact me via rt.cpan.org.
293              
294             =head1 SYNOPSIS
295              
296             Somewhere in your project...
297              
298             package MyProject::Test;
299              
300             use Test::Kit;
301              
302             # Combine multiple modules' behaviour into one
303              
304             include 'Test::More';
305             include 'Test::LongString';
306              
307             # Exclude or rename exported subs
308              
309             include 'Test::Warn' => {
310             exclude => [ 'warning_is' ],
311             renamed => {
312             'warning_like' => 'test_warn_warning_like'
313             },
314             };
315              
316             # Pass parameters through to import() directly
317              
318             include 'List::Util' => {
319             import => [ 'min', 'max', 'shuffle' ],
320             };
321              
322             # Include pragmata in your kit
323              
324             include 'strict', 'warnings';
325              
326             And then in your test files...
327              
328             use MyProject::Test tests => 4;
329              
330             ok 1, "1 is true";
331              
332             like_string(
333             `cat /usr/share/dict/words`,
334             qr/^ kit $/imsx,
335             "kit is a word"
336             );
337              
338             test_warn_warning_like {
339             warn "foo";
340             }
341             qr/FOO/i,
342             "warned foo";
343              
344             is max(qw(1 2 3 4 5)), 5, 'maximum is 5';
345              
346             =head1 EXCEPTIONS
347              
348             =head2 Unable to find package to import into
349              
350             This means that Test::Kit was unable to determine which module include() was
351             called from. It probably means you're doing something weird!
352              
353             If this is happening under any normal circumstances please file a bug report!
354              
355             =head2 Subroutine %s() already supplied to %s by %s
356              
357             This happens when there is a subroutine name collision. For example if you try
358             to include both Test::Simple and Test::More in your Kit it will complain that
359             ok() has been defined twice.
360              
361             You should be able to use the exclude or rename options to solve these
362             collisions.
363              
364             =head2 Package %s already has an import() sub
365              
366             This happens when your module has an import subroutine before the first
367             include() call. This could be because you have defined one, or because your
368             module has inherited an import() subroutine through an ISA relationship.
369              
370             Test::Kit intends to install its own import method into your module,
371             specifically it is going to install Test::Builder::Module's import() method.
372             Test::Builder::Module is an Exporter, so if you want to define your own
373             subroutines and export those you can push onto @EXPORT after all the calls to
374             include().
375              
376             =head1 COMPATIBILITY
377              
378             Test::Kit 2.1 and above should work with Test::Builder 1.3 and above (with
379             Test::Stream::Toolset/Exporter) and with older versions which still use
380             Test::Builder::Module.
381              
382             Huge thanks to Chad Granum and Karen Etheridge for all their help with the
383             Test::Builder 1.3 support.
384              
385             =head1 SEE ALSO
386              
387             A couple of other modules try to generalize this problem beyond the scope of testing:
388              
389             L<ToolSet> - Load your commonly-used modules in a single import
390              
391             L<Import::Base> - Import a set of modules into the calling module
392              
393             Test::Kit largely differs from these in that it always makes your module behave
394             like Test::More.
395              
396             =head1 AUTHOR
397              
398             Test::Kit 2.0 was written by Alex Balhatchet, C<< <kaoru at slackwise.net> >>
399              
400             Test::Kit 0.101 and before were authored by Curtis "Ovid" Poe, C<< <ovid at cpan.org> >>
401              
402             =cut