sync w/ main trunk
[bioperl-live.git] / Bio / Root / Test.pm
blob3f3135c9ba610d75e8611caa9ef56ad48f7abb2f
1 # $Id$
3 # BioPerl module for Bio::Root::Test
5 # Please direct questions and support issues to <bioperl-l@bioperl.org>
7 # Cared for by Sendu Bala <bix@sendu.me.uk>
9 # Copyright Sendu Bala
11 # You may distribute this module under the same terms as perl itself
13 # POD documentation - main docs before the code
15 =head1 NAME
17 Bio::Root::Test - A common base for all Bioperl test scripts.
19 =head1 SYNOPSIS
21 use lib '.'; # (for core package tests only)
22 use Bio::Root::Test;
24 test_begin(-tests => 20,
25 -requires_modules => [qw(IO::String XML::Parser)],
26 -requires_networking => 1);
28 my $do_network_tests = test_network();
29 my $output_debugging = test_debug();
31 # carry out tests with Test::More, Test::Exception and Test::Warn syntax
33 SKIP: {
34 # these tests need version 2.6 of Optional::Module to work
35 test_skip(-tests => 10, -requires_module => 'Optional::Module 2.6');
36 use_ok('Optional::Module');
38 # 9 other optional tests that need Optional::Module
41 SKIP: {
42 test_skip(-tests => 10, -requires_networking => 1);
44 # 10 optional tests that require internet access (only makes sense in the
45 # context of a script that doesn't use -requires_networking in the call to
46 # &test_begin)
49 # in unix terms, we want to test with a file t/data/input_file.txt
50 my $input_file = test_input_file('input_file.txt');
52 # we want the name of a file we can write to, that will be automatically
53 # deleted when the test script finishes
54 my $output_file = test_output_file();
56 # we want the name of a directory we can store files in, that will be
57 # automatically deleted when the test script finishes
58 my $output_dir = test_output_dir();
60 =head1 DESCRIPTION
62 This provides a common base for all BioPerl test scripts. It safely handles the
63 loading of Test::More, Test::Exception and Test::Warn (actually, a subclass
64 compatible with Bioperl warnings) prior to tests being run. It also presents an
65 interface to common needs such as skipping all tests if required modules aren't
66 present or if network tests haven't been enabled. See test_begin().
68 In the same way, it allows you to skip just a subset of tests for those same
69 reasons, in addition to requiring certain executables and environment variables.
70 See test_skip().
72 It also has two further methods that let you decide if network tests should be
73 run, and if debugging information should be printed. See test_network() and
74 test_debug().
76 Finally, it presents a consistent way of getting the path to input and output
77 files. See test_input_file(), test_output_file() and test_output_dir().
79 =head1 FEEDBACK
81 =head2 Mailing Lists
83 User feedback is an integral part of the evolution of this and other
84 Bioperl modules. Send your comments and suggestions preferably to
85 the Bioperl mailing list. Your participation is much appreciated.
87 bioperl-l@bioperl.org - General discussion
88 http://bioperl.org/wiki/Mailing_lists - About the mailing lists
90 =head2 Support
92 Please direct usage questions or support issues to the mailing list:
94 L<bioperl-l@bioperl.org>
96 rather than to the module maintainer directly. Many experienced and
97 reponsive experts will be able look at the problem and quickly
98 address it. Please include a thorough description of the problem
99 with code and data examples if at all possible.
101 =head2 Reporting Bugs
103 Report bugs to the Bioperl bug tracking system to help us keep track
104 of the bugs and their resolution. Bug reports can be submitted via
105 the web:
107 http://bugzilla.open-bio.org/
109 =head1 AUTHOR - Sendu Bala
111 Email bix@sendu.me.uk
113 =head1 APPENDIX
115 The rest of the documentation details each of the object methods.
116 Internal methods are usually preceded with a _
118 =cut
120 package Bio::Root::Test;
122 use strict;
123 use warnings;
125 use File::Temp qw(tempdir);
126 use File::Spec;
127 use Exporter qw(import);
129 BEGIN {
130 # For prototyping reasons, we have to load Test::More's methods now, even
131 # though theoretically in future the user may use a different Test framework
133 # We want to load Test::More, Test::Exception and Test::Warn. Preferably the
134 # users own versions, but if they don't have them, the ones in t/lib.
135 # However, this module is in t/lib so t/lib is already in @INC so Test::* in
136 # t/lib will be used first, which we don't want: get rid of t/lib in @INC
137 no lib 't/lib';
138 eval { require Test::More;
139 require Test::Exception;
140 require Test::Warn; };
141 if ($@) {
142 eval "use lib 't/lib';";
144 eval "use Test::More;
145 use Test::Exception;";
146 die "$@\n" if $@;
148 # now that the users' Test::Warn has been loaded if they had it, we can
149 # use Bio::Root::TestWarn
150 eval "use Bio::Root::Test::Warn;";
151 die "$@\n" if $@;
154 # re-export Test::More, Test::Exception and Test::Warn methods and export our own
155 our @EXPORT = qw(ok use_ok require_ok
156 is isnt like unlike is_deeply
157 cmp_ok
158 skip todo todo_skip
159 pass fail
160 eq_array eq_hash eq_set
161 $TODO
162 plan
163 can_ok isa_ok
164 diag
165 BAIL_OUT
167 dies_ok
168 lives_ok
169 throws_ok
170 lives_and
172 warning_is
173 warnings_are
174 warning_like
175 warnings_like
177 test_begin
178 test_skip
179 test_output_file
180 test_output_dir
181 test_input_file
182 test_network
183 test_debug
184 float_is
187 our $GLOBAL_FRAMEWORK = 'Test::More';
188 our @TEMP_FILES;
190 =head2 test_begin
192 Title : test_begin
193 Usage : test_begin(-tests => 20);
194 Function: Begin your test script, setting up the plan (skip all tests, or run
195 them all)
196 Returns : True if tests should be run.
197 Args : -tests => int (REQUIRED, the number of tests that will
198 be run)
199 -requires_modules => [] (array ref of module names that are
200 required; if any don't load, all tests
201 will be skipped. To specify a required
202 version of a module, include the version
203 number after the module name, separated
204 by a space)
205 -requires_module => str (as above, but for just one module)
206 -requires_networking => 1|0 (default 0, if true all tests will be
207 skipped if network tests haven't been
208 enabled in Build.PL)
209 -excludes_os => str (default none, if OS suppied, all tests
210 will skip if running on that OS (eg.
211 'mswin'))
212 -framework => str (default 'Test::More', the Test module
213 to load. NB: experimental, avoid using)
215 Note, supplying -tests => 0 is possible, allowing you to skip all
216 tests in the case that a test script is testing deprecated modules
217 that have yet to be removed from the distribution
219 =cut
221 sub test_begin {
222 my ($skip_all, $tests, $framework) = _skip(@_);
223 $GLOBAL_FRAMEWORK = $framework;
225 if ($framework eq 'Test::More') {
226 # ideally we'd delay loading Test::More until this point, but see BEGIN
227 # block
229 if ($skip_all) {
230 eval "plan skip_all => '$skip_all';";
232 elsif ($tests == 0) {
233 eval "plan skip_all => 'All tests are being skipped, probably because the module(s) being tested here are now deprecated';";
235 else {
236 eval "plan tests => $tests;";
239 return 1;
241 # go ahead and add support for other frameworks here
242 else {
243 die "Only Test::More is supported at the current time\n";
246 return 0;
249 =head2 test_skip
251 Title : test_skip
252 Usage : SKIP: {
253 test_skip(-tests => 10,
254 -requires_module => 'Optional::Module 2.01');
256 # 10 tests that need v2.01 of Optional::Module
258 Function: Skip a subset of tests for one of several common reasons: missing one
259 or more optional modules, network tests haven't been enabled, a
260 required binary isn't present, or an environmental variable isn't set
261 Returns : n/a
262 Args : -tests => int (REQUIRED, the number of tests that are
263 to be skipped in the event one of the
264 following options isn't satisfied)
265 -requires_modules => [] (array ref of module names that are
266 required; if any don't load, the desired
267 number of tests will be skipped. To
268 specify a required version of a module,
269 include the version number after the
270 module name, separated by a space)
271 -requires_module => str (as above, but for just one module)
272 -requires_executable => Bio::Tools::Run::WrapperBase instance
273 (checks WrapperBase::executable for the
274 presence of a binary, skips if absent)
275 -requires_env => str (checks %ENV for a specific env. variable,
276 skips if absent)
277 -excludes_os => str (default none, if OS suppied, desired num
278 of tests will skip if running on that OS
279 (eg. 'mswin'))
280 -requires_networking => 1 (if true the desired number of tests will
281 be skipped if network tests haven't been
282 enabled in Build.PL)
284 =cut
286 sub test_skip {
287 my ($skip, $tests, $framework) = _skip(@_);
288 $tests || die "-tests must be a number greater than 0";
290 if ($framework eq 'Test::More') {
291 if ($skip) {
292 eval "skip('$skip', $tests);";
295 # go ahead and add support for other frameworks here
296 else {
297 die "Only Test::More is supported at the current time\n";
301 =head2 test_output_file
303 Title : test_output_file
304 Usage : my $output_file = test_output_file();
305 Function: Get the full path of a file suitable for writing to.
306 When your test script ends, the file will be automatically deleted.
307 Returns : string (file path)
308 Args : none
310 =cut
312 sub test_output_file {
313 die "test_output_file takes no args\n" if @_;
315 my $tmp = File::Temp->new();
316 push(@TEMP_FILES, $tmp);
318 return $tmp->filename;
321 =head2 test_output_dir
323 Title : test_output_dir
324 Usage : my $output_dir = test_output_dir();
325 Function: Get the full path of a directory suitable for storing temporary files
327 When your test script ends, the directory and its contents will be
328 automatically deleted.
329 Returns : string (path)
330 Args : none
332 =cut
334 sub test_output_dir {
335 die "test_output_dir takes no args\n" if @_;
337 return tempdir(CLEANUP => 1);
340 =head2 test_input_file
342 Title : test_input_file
343 Usage : my $input_file = test_input_file();
344 Function: Get the path of a desired input file stored in the standard location
345 (currently t/data), but correct for all platforms.
346 Returns : string (file path)
347 Args : list of strings (ie. at least the input filename, preceded by the
348 names of any subdirectories within t/data)
349 eg. for the file t/data/in.file pass 'in.file', for the file
350 t/data/subdir/in.file, pass ('subdir', 'in.file')
352 =cut
354 sub test_input_file {
355 return File::Spec->catfile('t', 'data', @_);
358 =head2 test_network
360 Title : test_network
361 Usage : my $do_network_tests = test_network();
362 Function: Ask if network tests should be run.
363 Returns : boolean
364 Args : none
366 =cut
368 sub test_network {
369 require Module::Build;
370 my $build = Module::Build->current();
371 return $build->notes('network');
374 =head2 test_debug
376 Title : test_debug
377 Usage : my $output_debugging = test_debug();
378 Function: Ask if debugging information should be output.
379 Returns : boolean
380 Args : none
382 =cut
384 sub test_debug {
385 return $ENV{'BIOPERLDEBUG'} || 0;
388 =head2 float_is
390 Title : float_is
391 Usage : float_is($val1, $val2);
392 Function: test two floating point values for equality
393 Returns : Boolean based on test (can use in combination with diag)
394 Args : two scalar values (floating point numbers) (required via prototype)
395 test message (optional)
397 =cut
399 sub float_is ($$;$) {
400 my ($val1, $val2, $message) = @_;
401 # catch any potential undefined values and directly compare
402 if (!defined $val1 || !defined $val2) {
403 is($val1, $val2 ,$message);
404 } else {
405 is(sprintf("%g",$val1), sprintf("%g",$val2),$message);
409 # decide if should skip and generate skip message
410 sub _skip {
411 my %args = @_;
413 # handle input strictly
414 my $tests = $args{'-tests'};
415 (defined $tests && $tests =~ /^\d+$/) || die "-tests must be supplied and be an int\n";
416 delete $args{'-tests'};
418 my $req_mods = $args{'-requires_modules'};
419 delete $args{'-requires_modules'};
420 my @req_mods;
421 if ($req_mods) {
422 ref($req_mods) eq 'ARRAY' || die "-requires_modules takes an array ref\n";
423 @req_mods = @{$req_mods};
425 my $req_mod = $args{'-requires_module'};
426 delete $args{'-requires_module'};
427 if ($req_mod) {
428 ref($req_mod) && die "-requires_module takes a string\n";
429 push(@req_mods, $req_mod);
432 my $req_net = $args{'-requires_networking'};
433 delete $args{'-requires_networking'};
435 my $req_env = $args{'-requires_env'};
436 delete $args{'-requires_env'};
438 # strip any leading $ in case someone passes $FOO instead of 'FOO'
439 $req_env =~ s{^\$}{} if $req_env;
441 my $req_exe = $args{'-requires_executable'};
442 delete $args{'-requires_executable'};
444 if ($req_exe && (!ref($req_exe) || !$req_exe->isa('Bio::Tools::Run::WrapperBase'))) {
445 die "-requires_exe takes an argument of type Bio::Tools::Run::WrapperBase";
448 my $os = $args{'-excludes_os'};
449 delete $args{'-excludes_os'};
451 my $framework = $args{'-framework'} || $GLOBAL_FRAMEWORK;
452 delete $args{'-framework'};
454 # catch user mistakes
455 while (my ($key, $val) = each %args) {
456 die "unknown argument '$key' supplied, did you mistake 'required...' for 'requires...'?\n";
459 # test user requirments and return
460 if ($os) {
461 if ($^O =~ /$os/i) {
462 return ('Not compatible with your Operating System', $tests, $framework);
466 foreach my $mod (@req_mods) {
467 my $skip = _check_module($mod);
468 if ($skip) {
469 return ($skip, $tests, $framework);
473 if ($req_net && ! test_network()) {
474 return ('Network tests have not been requested', $tests, $framework);
477 if ($req_exe && !$req_exe->executable) {
478 my $msg = 'Required executable for '.ref($req_exe).' is not present';
479 diag($msg);
480 return ($msg, $tests, $framework);
483 if ($req_env && !exists $ENV{$req_env}) {
484 my $msg = 'Required environment variable $'.$req_env. ' is not set';
485 diag($msg);
486 return ($msg, $tests, $framework);
489 return ('', $tests, $framework);
492 sub _check_module {
493 my $mod = shift;
495 my $desired_version;
496 if ($mod =~ /(\S+)\s+(\S+)/) {
497 $mod = $1;
498 $desired_version = $2;
501 eval "require $mod;";
503 if ($@) {
504 return "The optional module $mod (or dependencies thereof) was not installed";
506 elsif ($desired_version) {
507 no strict 'refs';
508 unless (defined ${"${mod}::VERSION"}) {
509 return "The optional module $mod didn't have a version, but we want v$desired_version";
511 elsif (${"${mod}::VERSION"} < $desired_version) {
512 return "The optional module $mod was out of date (wanted v$desired_version)";
516 return;