Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.790
diff -u -p -r1.790 common.inc
--- includes/common.inc	6 Sep 2008 15:06:10 -0000	1.790
+++ includes/common.inc	6 Sep 2008 23:13:41 -0000
@@ -587,43 +587,101 @@ function drupal_http_request($url, $head
  * - 0 = Log errors to database.
  * - 1 = Log errors to database and to screen.
  */
-function drupal_error_handler($errno, $message, $filename, $line, $context) {
+function drupal_error_handler($error, $message = '', $filename = '', $line = 0, $context = array()) {
   // If the @ error suppression operator was used, error_reporting will have
   // been temporarily set to 0.
   if (error_reporting() == 0) {
     return;
   }
 
-  if ($errno & (E_ALL)) {
-    $types = array(1 => 'error', 2 => 'warning', 4 => 'parse error', 8 => 'notice', 16 => 'core error', 32 => 'core warning', 64 => 'compile error', 128 => 'compile warning', 256 => 'user error', 512 => 'user warning', 1024 => 'user notice', 2048 => 'strict warning', 4096 => 'recoverable fatal error');
+  if (is_object($error) || ($error & E_ALL)) {
+    $types = array(-1 => 'uncaught exception', 1 => 'error', 2 => 'warning', 4 => 'parse error', 8 => 'notice', 16 => 'core error', 32 => 'core warning', 64 => 'compile error', 128 => 'compile warning', 256 => 'user error', 512 => 'user warning', 1024 => 'user notice', 2048 => 'strict warning', 4096 => 'recoverable fatal error');
 
-    // For database errors, we want the line number/file name of the place that
-    // the query was originally called, not _db_query().
-    if (isset($context[DB_ERROR])) {
-      $backtrace = array_reverse(debug_backtrace());
-
-      // List of functions where SQL queries can originate.
-      $query_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
-
-      // Determine where query function was called, and adjust line/file
-      // accordingly.
-      foreach ($backtrace as $index => $function) {
-        if (in_array($function['function'], $query_functions)) {
-          $line = $backtrace[$index]['line'];
-          $filename = $backtrace[$index]['file'];
-          break;
+    if (!is_object($error)) {
+      // This is an error handler.
+      $errno = $error;
+      $backtrace = debug_backtrace();
+    }
+    else {
+      // This is an exception handler.
+      $errno = -1;
+      $message = $error->getMessage();
+
+      $backtrace = $error->getTrace();
+      
+      // Add the line throwing the exception.
+      array_unshift($backtrace, array(
+        'line' => $error->getLine(),
+        'file' => $error->getFile()
+      ));
+
+      // For PDOException errors, we try to return the initial caller,
+      // not internal functions of the database layer.
+      if ($error instanceof PDOException) {
+        $db_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
+        while (($caller = next($backtrace)) &&
+             ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE)) ||
+              in_array($caller['function'], $db_functions))) {
+          array_shift($backtrace);
         }
       }
     }
 
-    $entry = $types[$errno] . ': ' . $message . ' in ' . $filename . ' on line ' . $line . '.';
+    $error_call = reset($backtrace);
+    $line = $error_call['line'];
+    $filename = $error_call['file'];
+    $function = _get_caller_name_from_backtrace($backtrace);
+
+    $entry = $types[$errno] . ': ' . $message . ' in ' . $function . ' (line ' . $line . ' of '. $filename . ').';
 
     // Force display of error messages in update.php.
     if (variable_get('error_level', 1) == 1 || strstr($_SERVER['SCRIPT_NAME'], 'update.php')) {
       drupal_set_message($entry, 'error');
     }
 
+    // When running inside the testing framework, we relay the errors
+    // to the tested site by the way of HTTP headers.
+    if (preg_match("/^simpletest\d+/", $GLOBALS['db_prefix'])) {
+      static $number = 0;
+      $error_line = array();
+      foreach (array('message', 'filename', 'line', 'function') as $variable) {
+        $error_line[] = $variable . '=' . rawurlencode($$variable);
+      }
+      header('X-Error-' . $number . ': ' . implode('&', $error_line));
+      $number++;
+    }
+
     watchdog('php', '%message in %file on line %line.', array('%error' => $types[$errno], '%message' => $message, '%file' => $filename, '%line' => $line), WATCHDOG_ERROR);
+
+    // Uncaught exceptions are always fatal in PHP: the execution stops
+    // as soon as the error handler returns.
+    // We try our best to display a meaningful message to the user.
+    if ($errno < 0) {
+      if (!isset($GLOBALS['theme'])) {
+        drupal_maintenance_theme();
+        $type = 'maintenance_page';
+      }
+      else {
+        $type = 'page';
+      }
+      drupal_set_header('HTTP/1.1 503 Service unavailable');
+      drupal_set_title(t('Error'));
+      print theme($type, t('The website encountered an unexpected error. Please try again later.'));
+    }
+  }
+}
+
+function _get_caller_name_from_backtrace($backtrace) {
+  reset($backtrace);
+  if ($trace = next($backtrace)) {
+    $function_name = $trace['function'];
+    if (isset($trace['class'])) {
+      $function_name = $trace['class'] . $trace['type'] . $function_name . '()';
+    }
+    return $function_name;
+  }
+  else {
+    return 'main()';
   }
 }
 
@@ -2479,6 +2537,7 @@ function _drupal_bootstrap_full() {
   require_once './includes/actions.inc';
   // Set the Drupal custom error handler.
   set_error_handler('drupal_error_handler');
+  set_exception_handler('drupal_error_handler');
   // Emit the correct charset HTTP header.
   drupal_set_header('Content-Type: text/html; charset=utf-8');
   // Detect string handling method
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.37
diff -u -p -r1.37 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	6 Sep 2008 08:36:20 -0000	1.37
+++ modules/simpletest/drupal_web_test_case.php	6 Sep 2008 21:49:06 -0000
@@ -72,13 +72,13 @@ class DrupalWebTestCase {
     $db_prefix = $this->db_prefix_original;
     db_insert('simpletest')->fields(array(
       'test_id' => $this->test_id,
-    'test_class' => get_class($this), 
-    'status' => $status, 
-    'message' => substr($message, 0, 255),  // Some messages are too long for the database.
-    'message_group' => $group, 
-    'caller' => $function['function'], 
-    'line' => $function['line'], 
-    'file' => $function['file'],
+      'test_class' => get_class($this),
+      'status' => $status,
+      'message' => $message,
+      'message_group' => $group, 
+      'caller' => $function['function'], 
+      'line' => $function['line'], 
+      'file' => $function['file'],
     ))->execute();
     $this->_assertions[] = array(
       'status' => $status,
@@ -754,6 +754,7 @@ class DrupalWebTestCase {
         CURLOPT_RETURNTRANSFER => TRUE,
         CURLOPT_SSL_VERIFYPEER => FALSE,  // Required to make the tests run on https://
         CURLOPT_SSL_VERIFYHOST => FALSE,  // Required to make the tests run on https://
+        CURLOPT_HEADERFUNCTION => array($this, 'curlHeaderCallback'),
       );
       if (preg_match('/simpletest\d+/', $db_prefix)) {
         $curl_options[CURLOPT_USERAGENT] = $db_prefix;
@@ -786,6 +787,30 @@ class DrupalWebTestCase {
   }
 
   /**
+   * Reads headers and registers errors received from the tested site.
+   *
+   * @param $ch the cURL handler.
+   * @param $header a header.
+   */
+  protected function curlHeaderCallback($ch, $header) {
+    if (preg_match('/^X-Error-[0-9]+: (.*)$/', $header, $matches)) {
+      $parts = array();
+      foreach (explode('&', $matches[1]) as $part) {
+        list($name, $value) = explode('=', $part);
+        $parts[$name] = rawurldecode($value);
+      }
+      if (isset($parts['message'])) {
+        $this->error($parts['message'], 'Other', array(
+          'file' => isset($parts['filename']) ? $parts['filename'] : '',
+          'line' => isset($parts['line']) ? $parts['line'] : 0,
+          'function' => isset($parts['function']) ? $parts['function'] : 0,
+        ));
+      }
+    }
+    return strlen($header);
+  }
+
+  /**
    * Close the cURL handler and unset the handler.
    */
   protected function curlClose() {
