Index: scripts/run-tests.sh
===================================================================
RCS file: /cvs/drupal/drupal/scripts/run-tests.sh,v
retrieving revision 1.31
diff -u -r1.31 run-tests.sh
--- scripts/run-tests.sh	20 Jul 2009 18:51:36 -0000	1.31
+++ scripts/run-tests.sh	23 Jul 2009 07:54:43 -0000
@@ -83,6 +83,9 @@
 // Execute tests.
 simpletest_script_command($args['concurrency'], $test_id, implode(",", $test_list));
 
+list($last_prefix, $last_test_class) = simpletest_last_test_get($test_id);
+simpletest_log_read($test_id, $last_prefix, $last_test_class);
+
 // Display results before database is cleared.
 simpletest_script_reporter_display_results();
 
@@ -466,8 +469,6 @@
 function simpletest_script_reporter_display_results() {
   global $args, $test_id, $results_map;
 
-  simpletest_log_read($test_id);
-
   echo "\n";
   $end = timer_stop('run-tests');
   echo "Test run duration: " . format_interval($end['time'] / 1000);
Index: modules/simpletest/simpletest.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.module,v
retrieving revision 1.61
diff -u -r1.61 simpletest.module
--- modules/simpletest/simpletest.module	20 Jul 2009 18:51:34 -0000	1.61
+++ modules/simpletest/simpletest.module	23 Jul 2009 07:54:43 -0000
@@ -203,7 +203,8 @@
   }
   else {
     // Use the test_id passed as a parameter to _simpletest_batch_operation().
-    simpletest_log_read($operations[0][1][1]);
+    list($last_prefix, $last_test_class) = simpletest_last_test_get($operations[0][1][1]);
+    simpletest_log_read($operations[0][1][1], $last_prefix, $last_test_class);
 
     drupal_set_message(t('The test run did not successfully finish.'), 'error');
     drupal_set_message(t('Please use the <em>Clean environment</em> button to clean-up temporary files and tables.'), 'warning');
@@ -212,34 +213,68 @@
 }
 
 /**
+ * Get information about the last test that ran given a test ID.
+ *
+ * @param $test_id
+ *   The test ID to get the last test from.
+ * @return
+ *   Array containing the last database prefix used and the last test class
+ *   that ran.
+ */
+function simpletest_last_test_get($test_id) {
+  $last_prefix = db_result(db_query_range('SELECT last_prefix FROM {simpletest_test_id} WHERE test_id = :test_id', array(':test_id' => $test_id), 0, 1));
+  $last_test_class = db_result(db_query_range('SELECT test_class FROM {simpletest} WHERE test_id = :test_id ORDER BY message_id DESC', array(':test_id' => $test_id), 0, 1));
+  return array($last_prefix, $last_test_class);
+}
+
+/**
  * Read the error log and report any errors as assertion failures.
  *
  * The errors in the log should only be fatal errors since any other errors
  * will have been recorded by the error handler.
  *
  * @param $test_id
- *   The test ID to read log file for.
+ *   The test ID to which the log relates.
+ * @param $prefix
+ *   The database prefix to which the log relates.
+ * @param $test_class
+ *   The test class to which the log relates.
+ * @param $in_test_directory
+ *   Indicates that the current file directory path is the test directory.
+ * @return
+ *   Found any entries in log.
  */
-function simpletest_log_read($test_id) {
-  $last_prefix = db_result(db_query('SELECT last_prefix FROM {simpletest_test_id} WHERE test_id = :test_id', array(':test_id' => $test_id)));
-  $last_prefix = substr($last_prefix, 10);
-
-  $test_class = db_result(db_query('SELECT test_class FROM {simpletest} WHERE test_id = :test_id ORDER BY message_id', array(':test_id' => $test_id)));
-  $log = file_directory_path() . "/simpletest/$last_prefix/error.log";
+function simpletest_log_read($test_id, $prefix, $test_class, $in_test_directory = FALSE) {
+  $prefix = substr($prefix, 10);
+  $log = file_directory_path() . ($in_test_directory ? '' : '/simpletest/' . $prefix) . '/error.log';
+  $found = FALSE;
   if (file_exists($log)) {
     foreach (file($log) as $line) {
-      if (preg_match('/PHP Fatal error: (.*?) in (.*) on line (\d+)/', $line, $match)) {
+      if (preg_match('/\[.*?\] (.*?): (.*?) in (.*) on line (\d+)/', $line, $match)) {
+        // Parse PHP fatal errors for example: PHP Fatal error: Call to
+        // undefined function break_me() in /path/to/file.php on line 17
+        $caller = array(
+          'line' => $match[4],
+          'file' => $match[3],
+        );
+        DrupalTestCase::insertAssert($test_id, $test_class, FALSE, $match[2], $match[1], $caller);
+      }
+      elseif (preg_match('/\[.*?\] (.*?) \/(.*?):(\d+)/', $line, $match)) {
+        // Parse XDebug statements.
         $caller = array(
           'line' => $match[3],
           'file' => $match[2],
         );
-        DrupalTestCase::assertStatic($test_id, $test_class, FALSE, $match[1], 'Fatal error', $caller);
+        DrupalTestCase::insertAssert($test_id, $test_class, FALSE, $match[1], 'PHP stack', $caller);
       }
       else {
-        DrupalTestCase::assertStatic($test_id, $test_class, FALSE, $line, 'Fatal error');
+        // Unkown format, place the entire message in the log.
+        DrupalTestCase::insertAssert($test_id, $test_class, FALSE, $line, 'Fatal error');
       }
+      $found = TRUE;
     }
   }
+  return $found;
 }
 
 /**
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.128
diff -u -r1.128 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	22 Jul 2009 04:45:35 -0000	1.128
+++ modules/simpletest/drupal_web_test_case.php	23 Jul 2009 07:54:43 -0000
@@ -138,20 +138,27 @@
   }
 
   /**
-   * Make assertions from outside the test case.
+   * Store an assertion from outside the testing context.
+   *
+   * This is useful for inserting assertions that can only be recorded after
+   * the test case has been destroyed, such as PHP fatal errors. The caller
+   * information is not automatically gathered since the caller is most likely
+   * inserting the assertion on behalf of other code. In all other respects
+   * the method behaves just like DrupalTestCase::assert() in terms of storing
+   * the assertion.
    *
    * @see DrupalTestCase::assert()
    */
-  public static function assertStatic($test_id, $test_class, $status, $message = '', $group = 'Other', array $caller = NULL) {
+  public static function insertAssert($test_id, $test_class, $status, $message = '', $group = 'Other', array $caller = array()) {
     // Convert boolean status to string status.
     if (is_bool($status)) {
       $status = $status ? 'pass' : 'fail';
     }
 
     $caller += array(
-      'function' => t('N/A'),
-      'line' => -1,
-      'file' => t('N/A'),
+      'function' => t('Unknown'),
+      'line' => 0,
+      'file' => t('Unknown'),
     );
 
     $assertion = array(
@@ -1028,13 +1035,22 @@
       ->execute();
     $db_prefix = $db_prefix_new;
 
+    // Create test directory ahead of installation so fatal errors and debug
+    // information can be logged during installation process.
+    $directory = $this->originalFileDirectory . '/simpletest/' . substr($db_prefix, 10);
+    file_check_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+
+    // Log fatal errors.
+    ini_set('log_errors', 1);
+    ini_set('error_log', $directory . '/error.log');
+
     include_once DRUPAL_ROOT . '/includes/install.inc';
     drupal_install_system();
 
     $this->preloadRegistry();
 
     // Include the default profile
-    require_once("./profiles/default/default.profile");
+    require_once('./profiles/default/default.profile');
     $profile_details = install_profile_info('default', 'en');
 
     // Add the specified modules to the list of modules in the default profile.
@@ -1085,15 +1101,9 @@
     // default mail handler.
     variable_set('smtp_library', drupal_get_path('module', 'simpletest') . '/drupal_web_test_case.php');
 
-    // Use temporary files directory with the same prefix as database.
-    variable_set('file_directory_path', $this->originalFileDirectory . '/simpletest/' . substr($db_prefix, 10));
-    $directory = file_directory_path();
-    // Create the files directory.
-    file_check_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
-
-    // Log fatal errors.
-    ini_set('log_errors', 1);
-    ini_set('error_log', $directory . '/error.log');
+    // Use temporary files directory with the same prefix as database. The
+    // directory will have been created already.
+    variable_set('file_directory_path', $directory);
 
     set_time_limit($this->timeLimit);
   }
@@ -1133,6 +1143,13 @@
   protected function tearDown() {
     global $db_prefix, $user, $language;
 
+    // In case a fatal error occured that was not in the test process read the
+    // log to pick up any fatal errors.
+    $db_prefix_temp = $db_prefix;
+    $db_prefix = $this->originalPrefix;
+    simpletest_log_read($this->testId, $db_prefix, get_class($this), TRUE);
+    $db_prefix = $db_prefix_temp;
+
     $emailCount = count(variable_get('simpletest_emails', array()));
     if ($emailCount) {
       $message = format_plural($emailCount, t('!count e-mail was sent during this test.'), t('!count e-mails were sent during this test.'), array('!count' => $emailCount));
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.938
diff -u -r1.938 common.inc
--- includes/common.inc	22 Jul 2009 04:45:35 -0000	1.938
+++ includes/common.inc	23 Jul 2009 07:54:42 -0000
@@ -3434,6 +3434,14 @@
   set_error_handler('_drupal_error_handler');
   set_exception_handler('_drupal_exception_handler');
 
+  if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'simpletest') !== FALSE) {
+    // Valid SimpleTest user-agent, log fatal errors to test specific file
+    // directory. The user-agent is validated in DRUPAL_BOOTSTRAP_DATABASE
+    // phase so as long as it is a SimpleTest user-agent it is valid.
+    ini_set('log_errors', 1);
+    ini_set('error_log', file_directory_path() . '/error.log');
+  }
+
   // Emit the correct charset HTTP header.
   drupal_set_header('Content-Type', 'text/html; charset=utf-8');
   // Detect string handling method
