diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh index f0949a7e3332a9b8a942f71f1aa4490bebd55650..f4a71f1338feeb8ef606cc9ac5f0e61059d074f0 100755 --- a/core/scripts/run-tests.sh +++ b/core/scripts/run-tests.sh @@ -20,6 +20,7 @@ // Masquerade as Apache for running tests. simpletest_script_init("Apache"); simpletest_script_run_one_test($args['test-id'], $args['execute-test']); + // Sub-process script execution ends here. } else { // Run administrative functions as CLI. @@ -74,17 +75,8 @@ simpletest_script_reporter_init(); -// Setup database for test results. -$test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute(); - // Execute tests. -simpletest_script_execute_batch($test_id, simpletest_script_get_test_list()); - -// Retrieve the last database prefix used for testing and the last test class -// that was run from. Use the information to read the lgo file in case any -// fatal errors caused the test to crash. -list($last_prefix, $last_test_class) = simpletest_last_test_get($test_id); -simpletest_log_read($test_id, $last_prefix, $last_test_class); +simpletest_script_execute_batch(simpletest_script_get_test_list()); // Stop the timer. simpletest_script_reporter_timer_stop(); @@ -96,8 +88,8 @@ simpletest_script_reporter_write_xml_results(); } -// Cleanup our test results. -simpletest_clean_results_table($test_id); +// Clean up all test results. +simpletest_clean_results_table(); // Test complete, exit. exit; @@ -308,8 +300,8 @@ function simpletest_script_init($server_software) { /** * Execute a batch of tests. */ -function simpletest_script_execute_batch($test_id, $test_classes) { - global $args; +function simpletest_script_execute_batch($test_classes) { + global $args, $test_ids; // Multi-process execution. $children = array(); @@ -320,6 +312,8 @@ function simpletest_script_execute_batch($test_id, $test_classes) { } // Fork a child process. + $test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute(); + $test_ids[] = $test_id; $test_class = array_shift($test_classes); $command = simpletest_script_command($test_id, $test_class); $process = proc_open($command, array(), $pipes, NULL, NULL, array('bypass_shell' => TRUE)); @@ -332,6 +326,7 @@ function simpletest_script_execute_batch($test_id, $test_classes) { // Register our new child. $children[] = array( 'process' => $process, + 'test_id' => $test_id, 'class' => $test_class, 'pipes' => $pipes, ); @@ -347,8 +342,12 @@ function simpletest_script_execute_batch($test_id, $test_classes) { // The child exited, unregister it. proc_close($child['process']); if ($status['exitcode']) { - echo 'FATAL ' . $test_class . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').' . "\n"; + echo 'FATAL ' . $child['class'] . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').' . "\n"; } + // Free-up space by removing any potentially created resources. + simpletest_script_cleanup($child['test_id'], $child['class'], $status['exitcode']); + + // Remove this child. unset($children[$cid]); } } @@ -377,6 +376,8 @@ function simpletest_script_run_one_test($test_id, $test_class) { // Finished, kill this runner. exit(0); } + // DrupalTestCase::run() catches exceptions already, so this is only reached + // when an exception is thrown in the wrapping test runner environment. catch (Exception $e) { echo (string) $e; exit(1); @@ -402,6 +403,83 @@ function simpletest_script_command($test_id, $test_class) { return $command; } +/** + * Removes all remnants of a test runner. + * + * In case a (e.g., fatal) error occurs after the test site has been fully setup + * and the error happens in many tests, the environment that executes the tests + * can easily run out of memory or disk space. This function ensures that all + * created resources are properly cleaned up after every executed test. + * + * This clean-up only exists in this script, since SimpleTest module itself does + * not use isolated sub-processes for each test being run, so a fatal error + * halts not only the test, but also the test runner (i.e., the parent site). + * + * @param int $test_id + * The test ID of the test run. + * @param string $test_class + * The class name of the test run. + * @param int $exitcode + * The exit code of the test runner. + * + * @see simpletest_script_run_one_test() + */ +function simpletest_script_cleanup($test_id, $test_class, $exitcode) { + // Retrieve the last database prefix used for testing. + list($db_prefix, ) = simpletest_last_test_get($test_id); + + // If no database prefix was found, then the test was not set up correctly. + if (empty($db_prefix)) { + echo "\nFATAL $test_class: Found no database prefix for test ID $test_id. (Check whether setUp() is invoked correctly.)"; + return; + } + + // Do not output verbose cleanup messages in case of a positive exitcode. + $output = !empty($exitcode); + $messages = array(); + + $messages[] = "- Found database prefix '$db_prefix' for test ID $test_id."; + + // Read the log file in case any fatal errors caused the test to crash. + simpletest_log_read($test_id, $db_prefix, $test_class); + + // Check whether a test file directory was setup already. + // @see prepareEnvironment() + $public_files = variable_get('file_public_path', conf_path() . '/files'); + $test_directory = $public_files . '/simpletest/' . substr($db_prefix, 10); + if (is_dir($test_directory)) { + // Output the error_log. + if (is_file($test_directory . '/error.log')) { + if ($errors = file_get_contents($test_directory . '/error.log')) { + $output = TRUE; + $messages[] = $errors; + } + } + + // Delete the test files directory. + // simpletest_clean_temporary_directories() cannot be used here, since it + // would also delete file directories of other tests that are potentially + // running concurrently. + file_unmanaged_delete_recursive($test_directory); + $messages[] = "- Removed test files directory."; + } + + // Clear out all database tables from the test. + $count = 0; + foreach (db_find_tables($db_prefix . '%') as $table) { + db_drop_table($table); + $count++; + } + if ($count) { + $messages[] = "- " . format_plural($count, 'Removed 1 leftover table.', 'Removed @count leftover tables.'); + } + + if ($output) { + echo implode("\n", $messages); + echo "\n"; + } +} + /** * Get list of tests based on arguments. If --all specified then * returns all available tests, otherwise reads list of tests. @@ -504,9 +582,9 @@ function simpletest_script_reporter_init() { * Display jUnit XML test results. */ function simpletest_script_reporter_write_xml_results() { - global $args, $test_id, $results_map; + global $args, $test_ids, $results_map; - $results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id)); + $results = db_query("SELECT * FROM {simpletest} WHERE test_id IN (:test_ids) ORDER BY test_class, message_id", array(':test_ids' => $test_ids)); $test_class = ''; $xml_files = array(); @@ -584,14 +662,14 @@ function simpletest_script_reporter_timer_stop() { * Display test results. */ function simpletest_script_reporter_display_results() { - global $args, $test_id, $results_map; + global $args, $test_ids, $results_map; if ($args['verbose']) { // Report results. echo "Detailed test results\n"; echo "---------------------\n"; - $results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id)); + $results = db_query("SELECT * FROM {simpletest} WHERE test_id IN (:test_ids) ORDER BY test_class, message_id", array(':test_ids' => $test_ids)); $test_class = ''; foreach ($results as $result) { if (isset($results_map[$result->status])) {