Pull out the 'recommends' table and refactor to make a bit more
[bioperl-live.git] / Bio / Root / Test.pm
blob5b1282e1f02125c2319e7629dcd91d7228592d17
2 # BioPerl module for Bio::Root::Test
4 # Please direct questions and support issues to <bioperl-l@bioperl.org>
6 # Cared for by Sendu Bala <bix@sendu.me.uk>
8 # Copyright Sendu Bala
10 # You may distribute this module under the same terms as perl itself
12 # POD documentation - main docs before the code
14 =head1 NAME
16 Bio::Root::Test - A common base for all Bioperl test scripts.
18 =head1 SYNOPSIS
20 use lib '.'; # (for core package tests only)
21 use Bio::Root::Test;
23 test_begin(-tests => 20,
24 -requires_modules => [qw(IO::String XML::Parser)],
25 -requires_networking => 1);
27 my $do_network_tests = test_network();
28 my $output_debugging = test_debug();
30 # carry out tests with Test::More, Test::Exception and Test::Warn syntax
32 SKIP: {
33 # these tests need version 2.6 of Optional::Module to work
34 test_skip(-tests => 10, -requires_module => 'Optional::Module 2.6');
35 use_ok('Optional::Module');
37 # 9 other optional tests that need Optional::Module
40 SKIP: {
41 test_skip(-tests => 10, -requires_networking => 1);
43 # 10 optional tests that require internet access (only makes sense in the
44 # context of a script that doesn't use -requires_networking in the call to
45 # &test_begin)
48 # in unix terms, we want to test with a file t/data/input_file.txt
49 my $input_file = test_input_file('input_file.txt');
51 # we want the name of a file we can write to, that will be automatically
52 # deleted when the test script finishes
53 my $output_file = test_output_file();
55 # we want the name of a directory we can store files in, that will be
56 # automatically deleted when the test script finishes
57 my $output_dir = test_output_dir();
59 =head1 DESCRIPTION
61 This provides a common base for all BioPerl test scripts. It safely handles the
62 loading of Test::More, Test::Exception and Test::Warn (actually, a subclass
63 compatible with Bioperl warnings) prior to tests being run. It also presents an
64 interface to common needs such as skipping all tests if required modules aren't
65 present or if network tests haven't been enabled. See test_begin().
67 In the same way, it allows you to skip just a subset of tests for those same
68 reasons, in addition to requiring certain executables and environment variables.
69 See test_skip().
71 It also has two further methods that let you decide if network tests should be
72 run, and if debugging information should be printed. See test_network() and
73 test_debug().
75 Finally, it presents a consistent way of getting the path to input and output
76 files. See test_input_file(), test_output_file() and test_output_dir().
78 =head1 FEEDBACK
80 =head2 Mailing Lists
82 User feedback is an integral part of the evolution of this and other
83 Bioperl modules. Send your comments and suggestions preferably to
84 the Bioperl mailing list. Your participation is much appreciated.
86 bioperl-l@bioperl.org - General discussion
87 http://bioperl.org/wiki/Mailing_lists - About the mailing lists
89 =head2 Support
91 Please direct usage questions or support issues to the mailing list:
93 I<bioperl-l@bioperl.org>
95 rather than to the module maintainer directly. Many experienced and
96 reponsive experts will be able look at the problem and quickly
97 address it. Please include a thorough description of the problem
98 with code and data examples if at all possible.
100 =head2 Reporting Bugs
102 Report bugs to the Bioperl bug tracking system to help us keep track
103 of the bugs and their resolution. Bug reports can be submitted via
104 the web:
106 https://redmine.open-bio.org/projects/bioperl/
108 =head1 AUTHOR - Sendu Bala
110 Email bix@sendu.me.uk
112 =head1 APPENDIX
114 The rest of the documentation details each of the object methods.
115 Internal methods are usually preceded with a _
117 =cut
119 package Bio::Root::Test;
121 use strict;
122 use warnings;
124 use File::Temp qw(tempdir);
125 use File::Spec;
126 use Exporter qw(import);
128 BEGIN {
129 # For prototyping reasons, we have to load Test::More's methods now, even
130 # though theoretically in future the user may use a different Test framework
132 # We want to load Test::More, Test::Exception and Test::Warn. Preferably the
133 # users own versions, but if they don't have them, the ones in t/lib.
134 # However, this module is in t/lib so t/lib is already in @INC so Test::* in
135 # t/lib will be used first, which we don't want: get rid of t/lib in @INC
136 no lib 't/lib';
137 eval { require Test::More;
138 require Test::Exception;
139 require Test::Warn; };
140 if ($@) {
141 eval "use lib 't/lib';";
143 eval "use Test::More;
144 use Test::Exception;";
145 die "$@\n" if $@;
147 # now that the users' Test::Warn has been loaded if they had it, we can
148 # use Bio::Root::TestWarn
149 eval "use Bio::Root::Test::Warn;";
150 die "$@\n" if $@;
153 # re-export Test::More, Test::Exception and Test::Warn methods and export our own
154 our @EXPORT = qw(ok use_ok require_ok
155 is isnt like unlike is_deeply
156 cmp_ok
157 skip todo todo_skip
158 pass fail
159 eq_array eq_hash eq_set
160 $TODO
161 plan
162 can_ok isa_ok
163 diag
164 BAIL_OUT
166 dies_ok
167 lives_ok
168 throws_ok
169 lives_and
171 warning_is
172 warnings_are
173 warning_like
174 warnings_like
176 test_begin
177 test_skip
178 test_output_file
179 test_output_dir
180 test_input_file
181 test_network
182 test_email
183 test_debug
184 float_is
187 if (Test::More->can('done_testing')) {
188 push @EXPORT, 'done_testing';
191 our $GLOBAL_FRAMEWORK = 'Test::More';
192 our @TEMP_FILES;
194 =head2 test_begin
196 Title : test_begin
197 Usage : test_begin(-tests => 20);
198 Function: Begin your test script, setting up the plan (skip all tests, or run
199 them all)
200 Returns : True if tests should be run.
201 Args : -tests => int (REQUIRED, the number of tests that will
202 be run)
203 -requires_modules => [] (array ref of module names that are
204 required; if any don't load, all tests
205 will be skipped. To specify a required
206 version of a module, include the version
207 number after the module name, separated
208 by a space)
209 -requires_module => str (as above, but for just one module)
210 -requires_networking => 1|0 (default 0, if true all tests will be
211 skipped if network tests haven't been
212 enabled in Build.PL)
213 -requires_email => 1 (if true the desired number of tests will
214 be skipped if either network tests
215 haven't been enabled in Build.PL or an
216 email hasn't been entered)
217 -excludes_os => str (default none, if OS suppied, all tests
218 will skip if running on that OS (eg.
219 'mswin'))
220 -framework => str (default 'Test::More', the Test module
221 to load. NB: experimental, avoid using)
223 Note, supplying -tests => 0 is possible, allowing you to skip all
224 tests in the case that a test script is testing deprecated modules
225 that have yet to be removed from the distribution
227 =cut
229 sub test_begin {
230 my ($skip_all, $tests, $framework) = _skip(@_);
231 $GLOBAL_FRAMEWORK = $framework;
233 if ($framework eq 'Test::More') {
234 # ideally we'd delay loading Test::More until this point, but see BEGIN
235 # block
237 if ($skip_all) {
238 eval "plan skip_all => '$skip_all';";
240 elsif (defined $tests && $tests == 0) {
241 eval "plan skip_all => 'All tests are being skipped, probably because the module(s) being tested here are now deprecated';";
243 elsif ($tests) {
244 eval "plan tests => $tests;";
247 return 1;
249 # go ahead and add support for other frameworks here
250 else {
251 die "Only Test::More is supported at the current time\n";
254 return 0;
257 =head2 test_skip
259 Title : test_skip
260 Usage : SKIP: {
261 test_skip(-tests => 10,
262 -requires_module => 'Optional::Module 2.01');
264 # 10 tests that need v2.01 of Optional::Module
266 Function: Skip a subset of tests for one of several common reasons: missing one
267 or more optional modules, network tests haven't been enabled, a
268 required binary isn't present, or an environmental variable isn't set
269 Returns : n/a
270 Args : -tests => int (REQUIRED, the number of tests that are
271 to be skipped in the event one of the
272 following options isn't satisfied)
273 -requires_modules => [] (array ref of module names that are
274 required; if any don't load, the desired
275 number of tests will be skipped. To
276 specify a required version of a module,
277 include the version number after the
278 module name, separated by a space)
279 -requires_module => str (as above, but for just one module)
280 -requires_executable => Bio::Tools::Run::WrapperBase instance
281 (checks WrapperBase::executable for the
282 presence of a binary, skips if absent)
283 -requires_env => str (checks %ENV for a specific env. variable,
284 skips if absent)
285 -excludes_os => str (default none, if OS suppied, desired num
286 of tests will skip if running on that OS
287 (eg. 'mswin'))
288 -requires_networking => 1 (if true the desired number of tests will
289 be skipped if network tests haven't been
290 enabled in Build.PL)
291 -requires_email => 1 (if true the desired number of tests will
292 be skipped if either network tests
293 haven't been enabled in Build.PL or an
294 email hasn't been entered)
296 =cut
298 sub test_skip {
299 my ($skip, $tests, $framework) = _skip(@_);
300 $tests || die "-tests must be a number greater than 0";
302 if ($framework eq 'Test::More') {
303 if ($skip) {
304 eval "skip('$skip', $tests);";
307 # go ahead and add support for other frameworks here
308 else {
309 die "Only Test::More is supported at the current time\n";
313 =head2 test_output_file
315 Title : test_output_file
316 Usage : my $output_file = test_output_file();
317 Function: Get the full path of a file suitable for writing to.
318 When your test script ends, the file will be automatically deleted.
319 Returns : string (file path)
320 Args : none
322 =cut
324 sub test_output_file {
325 die "test_output_file takes no args\n" if @_;
327 # RT 48813
328 my $tmp = File::Temp->new();
329 push(@TEMP_FILES, $tmp);
330 close($tmp); # Windows needs this
331 return $tmp->filename;
334 =head2 test_output_dir
336 Title : test_output_dir
337 Usage : my $output_dir = test_output_dir();
338 Function: Get the full path of a directory suitable for storing temporary files
340 When your test script ends, the directory and its contents will be
341 automatically deleted.
342 Returns : string (path)
343 Args : none
345 =cut
347 sub test_output_dir {
348 die "test_output_dir takes no args\n" if @_;
350 return tempdir(CLEANUP => 1);
353 =head2 test_input_file
355 Title : test_input_file
356 Usage : my $input_file = test_input_file();
357 Function: Get the path of a desired input file stored in the standard location
358 (currently t/data), but correct for all platforms.
359 Returns : string (file path)
360 Args : list of strings (ie. at least the input filename, preceded by the
361 names of any subdirectories within t/data)
362 eg. for the file t/data/in.file pass 'in.file', for the file
363 t/data/subdir/in.file, pass ('subdir', 'in.file')
365 =cut
367 sub test_input_file {
368 return File::Spec->catfile('t', 'data', @_);
371 =head2 test_network
373 Title : test_network
374 Usage : my $do_network_tests = test_network();
375 Function: Ask if network tests should be run.
376 Returns : boolean
377 Args : none
379 =cut
381 sub test_network {
382 require Module::Build;
383 my $build = Module::Build->current();
384 return $build->notes('network');
387 =head2 test_email
389 Title : test_email
390 Usage : my $do_network_tests = test_email();
391 Function: Ask if email address provided
392 Returns : boolean
393 Args : none
395 =cut
397 sub test_email {
398 require Module::Build;
399 my $build = Module::Build->current();
400 # this should not be settable unless the network tests work
401 return $build->notes('email');
404 =head2 test_debug
406 Title : test_debug
407 Usage : my $output_debugging = test_debug();
408 Function: Ask if debugging information should be output.
409 Returns : boolean
410 Args : none
412 =cut
414 sub test_debug {
415 return $ENV{'BIOPERLDEBUG'} || 0;
418 =head2 float_is
420 Title : float_is
421 Usage : float_is($val1, $val2);
422 Function: test two floating point values for equality
423 Returns : Boolean based on test (can use in combination with diag)
424 Args : two scalar values (floating point numbers) (required via prototype)
425 test message (optional)
427 =cut
429 sub float_is ($$;$) {
430 my ($val1, $val2, $message) = @_;
431 # catch any potential undefined values and directly compare
432 if (!defined $val1 || !defined $val2) {
433 is($val1, $val2 ,$message);
434 } else {
435 is(sprintf("%g",$val1), sprintf("%g",$val2),$message);
439 # decide if should skip and generate skip message
440 sub _skip {
441 my %args = @_;
443 # handle input strictly
444 my $tests = $args{'-tests'};
445 #(defined $tests && $tests =~ /^\d+$/) || die "-tests must be supplied and be an int\n";
446 delete $args{'-tests'};
448 my $req_mods = $args{'-requires_modules'};
449 delete $args{'-requires_modules'};
450 my @req_mods;
451 if ($req_mods) {
452 ref($req_mods) eq 'ARRAY' || die "-requires_modules takes an array ref\n";
453 @req_mods = @{$req_mods};
455 my $req_mod = $args{'-requires_module'};
456 delete $args{'-requires_module'};
457 if ($req_mod) {
458 ref($req_mod) && die "-requires_module takes a string\n";
459 push(@req_mods, $req_mod);
462 my $req_net = $args{'-requires_networking'};
463 delete $args{'-requires_networking'};
465 my $req_email = $args{'-requires_email'};
466 delete $args{'-requires_email'};
468 my $req_env = $args{'-requires_env'};
469 delete $args{'-requires_env'};
471 # strip any leading $ in case someone passes $FOO instead of 'FOO'
472 $req_env =~ s{^\$}{} if $req_env;
474 my $req_exe = $args{'-requires_executable'};
475 delete $args{'-requires_executable'};
477 if ($req_exe && (!ref($req_exe) || !$req_exe->isa('Bio::Tools::Run::WrapperBase'))) {
478 die "-requires_exe takes an argument of type Bio::Tools::Run::WrapperBase";
481 my $os = $args{'-excludes_os'};
482 delete $args{'-excludes_os'};
484 my $framework = $args{'-framework'} || $GLOBAL_FRAMEWORK;
485 delete $args{'-framework'};
487 # catch user mistakes
488 while (my ($key, $val) = each %args) {
489 die "unknown argument '$key' supplied, did you mistake 'required...' for 'requires...'?\n";
492 # test user requirments and return
493 if ($os) {
494 if ($^O =~ /$os/i) {
495 return ('Not compatible with your Operating System', $tests, $framework);
499 foreach my $mod (@req_mods) {
500 my $skip = _check_module($mod);
501 if ($skip) {
502 return ($skip, $tests, $framework);
506 if ($req_net && ! test_network()) {
507 return ('Network tests have not been requested', $tests, $framework);
510 if ($req_email && ! test_email()) {
511 return ('Valid email not provided; required for tests', $tests, $framework);
514 if ($req_exe && !$req_exe->executable) {
515 my $msg = 'Required executable for '.ref($req_exe).' is not present';
516 diag($msg);
517 return ($msg, $tests, $framework);
520 if ($req_env && !exists $ENV{$req_env}) {
521 my $msg = 'Required environment variable $'.$req_env. ' is not set';
522 diag($msg);
523 return ($msg, $tests, $framework);
526 return ('', $tests, $framework);
529 sub _check_module {
530 my $mod = shift;
532 my $desired_version;
533 if ($mod =~ /(\S+)\s+(\S+)/) {
534 $mod = $1;
535 $desired_version = $2;
538 eval "require $mod;";
540 if ($@) {
541 if ($@ =~ /Can't locate/) {
542 return "The optional module $mod (or dependencies thereof) was not installed";
544 else {
545 return "The optional module $mod generated the following error: \n$@";
548 elsif ($desired_version) {
549 no strict 'refs';
550 unless (defined ${"${mod}::VERSION"}) {
551 return "The optional module $mod didn't have a version, but we want v$desired_version";
553 elsif (${"${mod}::VERSION"} < $desired_version) {
554 return "The optional module $mod was out of date (wanted v$desired_version)";
558 return;