3 See the NOTICE file distributed with
this work
for additional information
4 regarding copyright ownership.
6 Licensed under the Apache License, Version 2.0 (the
"License");
7 you may not use
this file except in compliance with the License.
8 You may obtain a copy of the License at
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an
"AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License
for the specific language governing permissions and
16 limitations under the License.
21 package Bio::EnsEMBL::Hive::Utils::Test;
25 no warnings qw( redefine );
30 use File::Temp qw{tempfile};
46 our @ISA = qw(Exporter);
48 our %EXPORT_TAGS = ();
49 our @EXPORT_OK = qw( standaloneJob init_pipeline runWorker beekeeper generate_graph visualize_jobs db_cmd seed_pipeline tweak_pipeline peekJob get_test_urls get_test_url_or_die run_sql_on_db load_sql_in_db make_new_db_from_sqls make_hive_db safe_drop_database all_source_files);
51 our $VERSION =
'0.00';
54 # Helper method to compare warning messages. It allows the expectation to
55 # be given as a string (for exact match) or a regular expression.
56 sub _compare_job_warnings {
57 my ($got, $expects) = @_;
58 subtest
"WARNING content as expected" => sub {
60 my $exp_mess = shift @$expects;
61 if (re::is_regexp($exp_mess)) {
62 like(shift @$got, $exp_mess,
'WARNING message as expected');
64 is(shift @$got, $exp_mess,
'WARNING message as expected');
66 is_deeply($got, $expects,
'remaining WARNING arguments');
71 ## Helper method to compare dataflows. Only exact string matches are
72 #allowed at the moment.
73 sub _compare_job_dataflows {
74 my ($got, $expects) = @_;
75 is_deeply($got, $expects,
'DATAFLOW content as expected');
81 Example : standaloneJob(
'Bio::EnsEMBL::Hive::RunnableDB::JobFactory',
82 {
'inputlist' => [ [1,2], [3,4] ],
'column_names' => [
'a',
'b'] },
85 [ {
'a' => 1,
'b' => 2 }, {
'a' => 3,
'b' => 4 }, ],
90 Description : Run a given Runnable in
"standalone job" mode, i.e. with parameters but no connection to the database.
91 One can also give a list of events that the job is expected to
raise. Currently, dataflows and warnings
92 are supported. Examples can be found under t/05.runnabledb/
94 Exceptions : TAP-style
101 my ($module_or_file, $param_hash, $expected_events, $flags) = @_;
103 my $events_to_test = $expected_events ? [@$expected_events] : undef;
105 my $input_id = stringify($param_hash);
107 local $Data::Dumper::Terse = 1;
108 local $Data::Dumper::Sortkeys = 1;
110 # When a list of events is given, it must match exactly what the
111 # Runnable does (no missing / extra events, etc)
112 my $_test_event = sub {
113 my ($triggered_type, @got) = @_;
114 if (@$events_to_test) {
115 my $expects = shift @$events_to_test;
116 my $expected_type = shift @$expects;
117 if ($triggered_type ne $expected_type) {
118 fail(
"Got a $triggered_type event but was expecting $expected_type");
119 diag
"Got: " . Dumper([@_]);
120 } elsif ($triggered_type eq
'WARNING') {
121 _compare_job_warnings(\@got, $expects) or diag
"Got: " . Dumper([@_]);
123 _compare_job_dataflows(\@got, $expects) or diag
"Got: " . Dumper([@_]);
126 fail(
"event-stack is empty but the job emitted an event");
127 diag
"Got: " . Dumper([@_]);
131 # Local redefinition to hijack the events
134 &$_test_event(
'DATAFLOW', @_);
136 }
if $expected_events;
138 # Local redefinition to hijack the events
141 &$_test_event(
'WARNING', @_);
142 }
if $expected_events;
144 subtest
"standalone run of $module_or_file" => sub {
145 plan tests => 2 + ($expected_events ? 1+scalar(@$expected_events) : 0);
148 if ($flags->{expect_failure}) {
149 ok(!$is_success,
'job failed as expected');
151 ok($is_success,
'job completed');
153 }, sprintf(
'standaloneJob("%s", %s, (...), %s)', $module_or_file, stringify($param_hash), stringify($flags)));
155 if ($expected_events) {
156 ok(!scalar(@$events_to_test),
'no untriggered events');
157 diag(
"Did not receive: " . Dumper($events_to_test))
if scalar(@$events_to_test);
165 Arg[1] : String $file_or_module. The location of the PipeConfig file
166 Arg[2] : String $url. The location of the database to be created
167 Arg[3] : (optional) Arrayref $args. Extra parameters of the pipeline (as on the command-line)
168 Arg[4] : (optional) Arrayref $tweaks. Tweaks to be applied to the database (as with the -tweak command-line option)
169 Example : init_pipeline(
170 'Bio::EnsEMBL::Hive::Examples::LongMult::PipeConfig::LongMultServer_conf',
173 [
'pipeline.param[take_time]=0']
175 Description : Initialize a
new pipeline database
for the given PipeConfig module name on that URL.
176 $options simply represents the command-line options one would give on the command-line.
177 Additionally, tweaks can be defined. Note that -hive_force_init is automatically added.
179 Exceptions : TAP-style
186 my ($file_or_module, $url, $options, $tweaks) = @_;
190 if (ref($url) and !$tweaks) {
191 # Probably the old syntax
192 warn
"The init_pipeline(\$options, \$tweaks) interface is deprecated. You should now give first a \$url parameter\n";
195 my ($url_flag_index) = grep {$options->[$_] eq
'-pipeline_url'} (0..(scalar(@$options) - 1));
196 unless (defined $url_flag_index) {
197 die
"Could not find a -url parameter in init_pipeline()'s arguments\n";
199 $url = (splice(@$options, $url_flag_index, 2))[1];
202 local @ARGV = @$options;
203 unshift @ARGV, (-pipeline_url => $url, -hive_force_init => 1);
207 ok($orig_unambig_url,
'Given URL could be parsed');
209 ok($returned_url,
'pipeline initialized on '.$returned_url);
212 # Both $url and $returned_url MAY contain the password (if applicable for the driver) but can be missing the port number assuming a default
213 # Both $orig_unambig_url and $returned_unambig_url SHOULD contain the port number (if applicable for the driver) but WILL NOT contain a password
214 is($returned_unambig_url, $orig_unambig_url,
'pipeline initialized on '.$url);
215 }, sprintf(
'init_pipeline("%s", %s)', $file_or_module, stringify($options)));
219 =head2 _test_ehive_script
221 Arg[1] : String $script_name. The name of the script (assumed to be found in
222 ensembl-hive/scripts/ once the .pl suffix added)
223 Arg[2] : String $url. The location of the database
224 Arg[3] : Arrayref $args. Extra arguments given to the script
225 Arg[4] : String $test_name (optional). The name of the test
226 Description : Generic method that can
run any eHive script and check its
return status
228 Exceptions : TAP-style
229 Caller : other methods in Utils::Test
234 sub _test_ehive_script {
235 my ($script_name, $url, $args, $test_name) = @_;
237 my @ext_args = ( defined($url) ? (-url => $url) : (), @$args );
238 $test_name ||=
'Can run '.$script_name.(@ext_args ?
' with the following cmdline options: '.join(
' ', @ext_args) :
'');
240 ok(!system($ENV{
'EHIVE_ROOT_DIR'}.
'/scripts/'.$script_name.
'.pl', @ext_args), $test_name);
246 Arg[1] : String $url. The location of the database
247 Arg[2] : Arrayref $args. Extra arguments given to runWorker
248 Arg[3] : String $test_name (optional). The name of the test
249 Example : runWorker($url, [ -can_respecialize => 1 ]);
250 Description : Run a worker on the given pipeline in the current process.
251 The worker options have been divided in three groups: the ones affecting its specialization,
252 the ones affecting its
"life" (how
long it lasts), and the ones controlling its execution mode.
254 Exceptions : TAP-style
261 my ($pipeline, $specialization_options, $life_options, $execution_options) = @_;
262 if (ref($pipeline)) {
263 # Probably the old syntax
264 warn
"The runWorker(\$pipeline, \$specialization_options, \$life_options, \$execution_options) interface is deprecated. You should now give a \$url parameter and combine all the options\n";
265 my %combined_params = (%{$specialization_options||{}}, %{$life_options||{}}, %{$execution_options||{}});
266 unless ($pipeline->hive_dba) {
267 die
"The pipeline doesn't have a hive_dba(). This is required by runWorker()\n";
269 my $url = $pipeline->hive_dba->dbc->url;
270 return _test_ehive_script(
'runWorker', $url, [
map {(
"-$_" => $combined_params{$_})} keys %combined_params]);
272 return _test_ehive_script(
'runWorker', @_);
278 Arg[1] : String $url. The location of the database
279 Arg[2] : Arrayref $args. Extra arguments given to seed_pipeline
280 Arg[3] : String $test_name (optional). The name of the test
281 Example : $seed_pipeline($url, [$arg1, $arg2],
'Run seed_pipeline with two arguments');
282 Description : Very
generic function to
run seed_pipeline on the given database with the given arguments
284 Exceptions : TAP-style
291 my ($url, $logic_name, $input_id, $test_name, @other_options) = @_;
292 return _test_ehive_script(
'seed_pipeline', $url, [-logic_name => $logic_name, -input_id => $input_id, @other_options], $test_name);
298 Arg[1] : String $url. The location of the database
299 Arg[2] : Arrayref $args. Extra arguments given to beekeeper.pl
300 Arg[3] : String $test_name (optional). The name of the test
301 Example : beekeeper($url, [$arg1, $arg2],
'Run beekeeper with two arguments');
302 Description : Very
generic function to
run beekeeper on the given database with the given arguments
304 Exceptions : TAP-style
311 return _test_ehive_script(
'beekeeper', @_);
314 =head2 tweak_pipeline
316 Arg[1] : String $url. The location of the database
317 Arg[2] : Arrayref $args. Extra arguments given to beekeeper.pl
318 Arg[3] : String $test_name (optional). The name of the test
319 Example : tweak_pipeline($url, [$arg1, $arg2],
'Run tweak_pipeline with two arguments');
320 Description : Very
generic function to
run tweak_pipeline on the given database with the given arguments
322 Exceptions : TAP-style
329 return _test_ehive_script(
'tweak_pipeline', @_);
333 =head2 generate_graph
335 Arg[1] : String $url or undef. The location of the database
336 Arg[2] : Arrayref $args. Extra arguments given to generate_graph.pl
337 Arg[3] : String $test_name (optional). The name of the test
338 Example : generate_graph($url, [-output =>
'lm_analyses.png'],
'Generate a PNG A-diagram');
339 Description : Very
generic function to
run generate_graph.pl on the given database with the given arguments
341 Exceptions : TAP-style
348 return _test_ehive_script(
'generate_graph', @_);
352 =head2 visualize_jobs
354 Arg[1] : String $url. The location of the database
355 Arg[2] : Arrayref $args. Extra arguments given to visualize_jobs.pl
356 Arg[3] : String $test_name (optional). The name of the test
357 Example : visualize_jobs($url, [-output =>
'lm_jobs.png', -accu_values],
'Generate a PNG J-diagram with accu values');
358 Description : Very
generic function to
run visualize_jobs.pl on the given database with the given arguments
360 Exceptions : TAP-style
367 return _test_ehive_script(
'visualize_jobs', @_);
372 Arg[1] : String $url. The location of the database
373 Arg[2] : Arrayref $args. Extra arguments given to peekJob.pl
374 Arg[3] : String $test_name (optional). The name of the test
375 Example : peekJob($url, [-job_id => 1],
'Check params for job 1');
376 Description : Very
generic function to
run peekJob.pl on the given database with the given arguments
378 Exceptions : TAP-style
385 return _test_ehive_script(
'peekJob', @_);
391 Arg[1] : String $url. The location of the database
392 Arg[2] : Arrayref $args. Extra arguments given to db_cmd.pl
393 Arg[3] : String $test_name (optional). The name of the test
394 Example : db_cmd($url, [-sql =>
'DROP DATABASE'],
'Drop the database');
395 Description : Very
generic function to
run db_cmd.pl on the given database with the given arguments
397 Exceptions : TAP-style
404 return _test_ehive_script(
'db_cmd', @_);
410 Arg[1] : String $url. The location of the database
411 Arg[2] : String $sql. The SQL to
run on the database
412 Arg[3] : String $test_name (optional). The name of the test
413 Example : run_sql_on_db($url,
'INSERT INTO sweets (name, quantity) VALUES (3, 'Snickers
')');
414 Description : Execute an SQL command on the given database and test its execution. This expects the
415 command-line client to
return a non-zero code in
case of a failure.
417 Exceptions : TAP-style
424 my ($url, $sql, $test_name) = @_;
425 return _test_ehive_script(
'db_cmd', $url, [-sql => $sql], $test_name
429 =head2 load_sql_in_db
431 Arg[1] : String $url. The location of the database
432 Arg[2] : String $sql_file. The location of a file to load in the database
433 Arg[3] : String $test_name (optional). The name of the test
434 Example : load_sql_in_db($url, $file_with_sql_commands);
435 Description : Execute an SQL script on the given database and test its execution.
436 This expects the command-line client to
return a non-zero code in
439 Exceptions : TAP-style
446 my ($url, $sql_file, $test_name) = @_;
447 my $cmd = $ENV{
'EHIVE_ROOT_DIR'}.
'/scripts/db_cmd.pl -url ' . $url .
' < ' . $sql_file;
448 ok(!system($cmd), $test_name
452 =head2 make_new_db_from_sqls
454 Arg[1] : String $url. The location of the database
455 Arg[2] : Arrayref of
string $sqls. Each element can be a SQL command or file to load
456 Arg[3] : String $test_name (optional). The name of the test
457 Example : make_new_db_from_sqls($url,
'CREATE TABLE sweets (name VARCHAR(40) NOT NULL, quantity INT UNSIGNED NOT NULL)');
458 Description : Create a
new database and apply a list of SQL commands
using the two above functions.
459 When an SQL command is a valid filename, the file is loaded rather than the command executed.
460 Note that it first issues a DROP DATABASE statement in
case the database already exists
462 Exceptions : TAP-style
468 sub make_new_db_from_sqls {
469 my ($url, $sqls, $test_name) = @_;
471 $sqls = [$sqls] unless ref($sqls);
475 subtest $test_name => sub {
477 ok($dbc,
'URL could be parsed to make a DBConnection object');
478 run_sql_on_db($url,
'DROP DATABASE IF EXISTS',
'Drop existing database');
479 run_sql_on_db($url,
'CREATE DATABASE',
'Create new database');
480 foreach my $s (@$sqls) {
482 load_sql_in_db($url, $s);
484 run_sql_on_db($url, $s);
495 Arg[1] : String $url. The location of the database
496 Arg[2] : Boolean $use_triggers (optional,
default 0). Whether we want to load the SQL triggers
497 Example : make_hive_db($url);
498 Description : Create a
new (empty) eHive database
using the two above functions.
499 This
function follows the same step as init_pipeline
500 Note that it first issues a DROP DATABASE statement in
case the database already exists
502 Exceptions : TAP-style
509 my ($url, $use_triggers) = @_;
511 # Will insert two keys: "hive_all_base_tables" and "hive_all_views"
512 my $hive_tables_sql =
'INSERT INTO hive_meta SELECT CONCAT("hive_all_", REPLACE(LOWER(TABLE_TYPE), " ", "_"), "s"), GROUP_CONCAT(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = "%s" GROUP BY TABLE_TYPE';
515 subtest
'Creation of a fresh eHive database' => sub {
517 ok($dbc,
'URL could be parsed to make a DBConnection object');
518 run_sql_on_db($url,
'DROP DATABASE IF EXISTS');
519 run_sql_on_db($url,
'CREATE DATABASE');
520 load_sql_in_db($url, $ENV{
'EHIVE_ROOT_DIR'} .
'/sql/tables.' . $dbc->driver);
521 load_sql_in_db($url, $ENV{
'EHIVE_ROOT_DIR'} .
'/sql/triggers.' . $dbc->driver)
if $use_triggers;
522 load_sql_in_db($url, $ENV{
'EHIVE_ROOT_DIR'} .
'/sql/foreign_keys.sql')
if $dbc->driver ne
'sqlite';
523 load_sql_in_db($url, $ENV{
'EHIVE_ROOT_DIR'} .
'/sql/procedures.' . $dbc->driver);
524 run_sql_on_db($url, $hive_tables_sql)
if $dbc->driver eq
'mysql';
533 Arg [1] : -driver => driver, -tag => tag, -no_user_prefix => 1
534 Example : my @urls = get_test_urls(-driver =>
'mysql', -tag =>
'longmult')
535 Example : my @urls = get_test_urls(-tag => 'gcpct')
536 Example : my @urls = get_test_urls(-driver => 'sqlite')
537 Example : my @urls = get_test_urls()
538 Description : Creates a listref containing db urls based on the drivers specified in
539 : the environment variable EHIVE_TEST_PIPELINE_URLS.
540 : The URLs will be standard eHive URLs, looking like driver:
541 : A database name consisting of [username]_ehive_test will be created
542 : and placed in the URL
543 : For example - mysql:
545 : If -tag is specified, then the list will have db names appended with '_tag'
546 : For example - (-tag => 'longmult') giving mysql:
548 : If -driver is specified, then the list will be restricted to urls for the
549 : particular driver or comma-separated list of drivers specified (e.g. 'mysql,pgsql')
551 : If -no_user_prefix is specified, then the automatically-generated database names
552 : won't be prefixed with the name of the current user
554 : If no drivers are specified in EHIVE_TEST_PIPELINE_URLS, it will check
555 : to see if sqlite is available in the current path, and return a sqlite url
556 : in the listref. Otherwise it will return an empty listref.
558 Returntype : listref of db connection URLs as strings
563 croak
"wrong number of arguments for get_test_urls(); has to be even" if (scalar(@_) % 2);
565 my %argcheck = %args;
566 delete(@argcheck{qw(-driver -tag -no_user_prefix)});
567 croak
"get_test_urls only accepts -driver and -tag as arguments" if (scalar(keys(%argcheck)) > 0);
570 my %url_parses_by_driver;
571 if (defined($ENV{EHIVE_TEST_PIPELINE_URLS})) {
572 my @urls = split( /[\s,]+/, $ENV{EHIVE_TEST_PIPELINE_URLS} );
573 foreach my $url (@urls) {
575 push(@{$url_parses_by_driver{$parse->{driver}}}, $parse);
577 croak
"badly formed url \"$url\" in EHIVE_TEST_PIPELINE_URLS";
581 my ($fh, $filename) = tempfile(UNLINK => 1);
585 my $constructed_db_name = ($args{-no_user_prefix} ?
'' : whoami().
'_') .
'ehive_test';
588 if (defined($args{-driver})) {
589 my @requested_drivers = split(/,/, $args{-driver});
591 foreach my $requested_driver (@requested_drivers) {
592 $requested_driver =~ s/^\s+|\s+$
593 if (defined($url_parses_by_driver{$requested_driver})) {
594 push(@driver_parses, @{$url_parses_by_driver{$requested_driver}});
598 foreach my $parses_for_driver (values(%url_parses_by_driver)) {
599 push (@driver_parses, @{$parses_for_driver});
604 foreach my $parsed_url (@driver_parses) {
606 ## Use the default database name if needed, and append the tag (if given)
607 $parsed_url->{
'dbname'} ||= $constructed_db_name;
608 $parsed_url->{
'dbname'} .= $constructed_db_name
if ($parsed_url->{
'driver'} eq
'sqlite') && ($parsed_url->{
'dbname'} =~ /\/$/);
609 $parsed_url->{
'dbname'} .=
'_'.$args{-tag}
if defined $args{-tag};
613 push (@list_of_urls, $final_url);
616 return \@list_of_urls;
620 =head2 get_test_url_or_die
622 Arg [1] : see get_test_urls()
623 Example : my $url = get_test_url_or_die(-driver => 'mysql', -tag => 'longmult')
624 Example : my $url = get_test_url_or_die(-tag => 'gcpct')
625 Example : my $url = get_test_url_or_die(-driver => 'sqlite')
626 Example : my $url = get_test_url_or_die()
627 Description : Wrapper around get_test_urls() that returns one of the test URLs, or
628 die if no databases are available
629 Returntype : db connection URL as a
string
633 sub get_test_url_or_die {
634 my $list_of_urls = get_test_urls(@_);
635 croak
"No test databases are available" unless scalar(@$list_of_urls);
636 return (sort @$list_of_urls)[0];
640 =head2 safe_drop_database
642 Arg[1] : DBAdaptor $hive_dba
643 Example : safe_drop_database( $hive_dba );
644 Description : Wait
for all workers to complete, disconnect from the database and drop it.
646 Caller : test scripts
650 sub safe_drop_database {
651 my $hive_dba = shift;
653 # In case workers are still alive:
654 my $worker_adaptor = $hive_dba->get_WorkerAdaptor;
655 while( $worker_adaptor->count_all(
"status != 'DEAD'") ) {
659 my $dbc = $hive_dba->dbc;
660 $dbc->disconnect_if_idle();
661 run_sql_on_db($dbc->url,
'DROP DATABASE');
665 =head2 all_source_files
667 Arg [n] : Directories to scan.
668 Example : my @files = all_source_files(
'modules');
669 Description: Scans the given directories and returns all found instances of
670 source code. This includes Perl (pl,pm,t), C(c,h) and
671 SQL (sql) suffixed files.
672 Returntype : Array of all found files
676 sub all_source_files {
677 my @starting_dirs = @_;
679 my @dirs = @starting_dirs;
680 my %excluded_dir =
map {$_ => 1} qw(_build build target .git __pycache__ bioperl-live cover_db deps);
681 while ( my $file = shift @dirs ) {
683 opendir my $dir, $file or next;
685 grep { !$excluded_dir{$_} && $_ !~ /^\./ }
686 File::Spec->no_upwards(readdir $dir);
688 push(@dirs,
map {File::Spec->catfile($file, $_)} @new_files);
691 #next unless $file =~ /(?-xism:\.(?:[cht]|p[lm]|sql))/;