Index: drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.2.2.3.2.38
diff -u -r1.2.2.3.2.38 drupal_web_test_case.php
--- drupal_web_test_case.php	25 Apr 2009 22:14:54 -0000	1.2.2.3.2.38
+++ drupal_web_test_case.php	3 Sep 2009 15:17:13 -0000
@@ -1,6 +1,7 @@
 <?php
-// $Id: drupal_web_test_case.php,v 1.2.2.3.2.38 2009/04/25 22:14:54 boombatower Exp $
-// Core: Id: drupal_web_test_case.php,v 1.96 2009/04/22 09:57:10 dries Exp
+// $Id$
+// Core: Id: drupal_web_test_case.php,v 1.146 2009/08/31 18:30:26 webchick Exp $
+
 /**
  * @file
  * Provide required modifications to Drupal 7 core DrupalWebTestCase in order
@@ -12,10 +13,11 @@
 module_load_include('function.inc', 'simpletest');
 
 /**
- * Test case for typical Drupal tests.
+ * Base class for Drupal tests.
+ *
+ * Do not extend this class, use one of the subclasses in this file.
  */
-class DrupalWebTestCase {
-
+abstract class DrupalTestCase {
   /**
    * The test run ID.
    *
@@ -24,70 +26,6 @@
   protected $testId;
 
   /**
-   * The URL currently loaded in the internal browser.
-   *
-   * @var string
-   */
-  protected $url;
-
-  /**
-   * The handle of the current cURL connection.
-   *
-   * @var resource
-   */
-  protected $curlHandle;
-
-  /**
-   * The headers of the page currently loaded in the internal browser.
-   *
-   * @var Array
-   */
-  protected $headers;
-
-  /**
-   * The content of the page currently loaded in the internal browser.
-   *
-   * @var string
-   */
-  protected $content;
-
-  /**
-   * The content of the page currently loaded in the internal browser (plain text version).
-   *
-   * @var string
-   */
-  protected $plainTextContent;
-
-  /**
-   * The parsed version of the page.
-   *
-   * @var SimpleXMLElement
-   */
-  protected $elements = NULL;
-
-  /**
-   * The current user logged in using the internal browser.
-   *
-   * @var bool
-   */
-  protected $loggedInUser = FALSE;
-
-  /**
-   * The current cookie file used by cURL.
-   *
-   * We do not reuse the cookies in further runs, so we do not need a file
-   * but we still need cookie handling, so we set the jar to NULL.
-   */
-  protected $cookieFile = NULL;
-
-  /**
-   * Additional cURL options.
-   *
-   * DrupalWebTestCase itself never sets this but always obeys what is set.
-   */
-  protected $additionalCurlOptions = array();
-
-  /**
    * The original database prefix, before it was changed for testing purposes.
    *
    * @var string
@@ -102,11 +40,9 @@
   protected $originalFileDirectory = NULL;
 
   /**
-   * The original user, before it was changed to a clean uid = 1 for testing purposes.
-   *
-   * @var object
+   * Time limit for the test.
    */
-  protected $originalUser = NULL;
+  protected $timeLimit = 180;
 
   /**
    * Current results of this test case.
@@ -117,6 +53,7 @@
     '#pass' => 0,
     '#fail' => 0,
     '#exception' => 0,
+    '#debug' => 0,
   );
 
   /**
@@ -127,10 +64,14 @@
   protected $assertions = array();
 
   /**
-   * Time limit for the test.
+   * This class is skipped when looking for the source of an assertion.
+   *
+   * When displaying which function an assert comes from, it's not too useful
+   * to see "drupalWebTestCase->drupalLogin()', we would like to see the test
+   * that called it. So we need to skip the classes defining these helper
+   * methods.
    */
-  protected $timeLimit = 180;
-
+  protected $skipClasses = array(__CLASS__ => TRUE);
 
   /**
    * Constructor for DrupalWebTestCase.
@@ -159,7 +100,7 @@
    *   the name of the source file, 'line' is the line number and 'function'
    *   is the caller function itself.
    */
-  private function assert($status, $message = '', $group = 'Other', array $caller = NULL) {
+  protected function assert($status, $message = '', $group = 'Other', array $caller = NULL) {
     global $db_prefix;
 
     // Convert boolean status to string status.
@@ -192,14 +133,66 @@
     );
 
     // Store assertion for display after the test has completed.
-//    db_insert('simpletest')->fields($assertion)->execute();
+//    db_insert('simpletest')
+//      ->fields($assertion)
+//      ->execute();
     db_query("INSERT INTO {simpletest}
               (test_id, test_class, status, message, message_group, function, line, file)
               VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", array_values($assertion));
 
     // Return to testing prefix.
     $db_prefix = $current_db_prefix;
-    return $status == 'pass' ? TRUE : FALSE;
+    // We do not use a ternary operator here to allow a breakpoint on
+    // test failure.
+    if ($status == 'pass') {
+      return TRUE;
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  /**
+   * 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 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('Unknown'),
+      'line' => 0,
+      'file' => t('Unknown'),
+    );
+
+    $assertion = array(
+      'test_id' => $test_id,
+      'test_class' => $test_class,
+      'status' => $status,
+      'message' => $message,
+      'message_group' => $group,
+      'function' => $caller['function'],
+      'line' => $caller['line'],
+      'file' => $caller['file'],
+    );
+
+//    db_insert('simpletest')
+//      ->fields($assertion)
+//      ->execute();
+    db_query("INSERT INTO {simpletest}
+              (test_id, test_class, status, message, message_group, function, line, file)
+              VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", array_values($assertion));
   }
 
   /**
@@ -212,11 +205,11 @@
     $backtrace = debug_backtrace();
 
     // The first element is the call. The second element is the caller.
-    // We skip calls that occurred in one of the methods of DrupalWebTestCase
+    // We skip calls that occurred in one of the methods of our base classes
     // or in an assertion function.
-    while (($caller = $backtrace[1]) &&
-          ((isset($caller['class']) && $caller['class'] == 'DrupalWebTestCase') ||
-            substr($caller['function'], 0, 6) == 'assert')) {
+   while (($caller = $backtrace[1]) &&
+         ((isset($caller['class']) && isset($this->skipClasses[$caller['class']])) ||
+           substr($caller['function'], 0, 6) == 'assert')) {
       // We remove that call.
       array_shift($backtrace);
     }
@@ -401,6 +394,12 @@
    *   FALSE.
    */
   protected function error($message = '', $group = 'Other', array $caller = NULL) {
+    if ($group == 'User notice') {
+      // Since 'User notice' is set by trigger_error() which is used for debug
+      // set the message to a status of 'debug'.
+      return $this->assert('debug', $message, 'Debug', $caller);
+    }
+
     return $this->assert('exception', $message, $group, $caller);
   }
 
@@ -408,6 +407,17 @@
    * Run all tests in this class.
    */
   public function run() {
+    // Initialize verbose debugging.
+    simpletest_verbose(NULL, file_directory_path(), get_class($this));
+
+    // HTTP auth settings (<username>:<password>) for the simpletest browser
+    // when sending requests to the test site.
+    $username = variable_get('simpletest_username', NULL);
+    $password = variable_get('simpletest_password', NULL);
+    if ($username && $password) {
+      $this->httpauth_credentials = $username . ':' . $password;
+    }
+
     set_error_handler(array($this, 'errorHandler'));
     $methods = array();
     // Iterate through all the methods in this class.
@@ -473,6 +483,206 @@
   }
 
   /**
+   * Generates a random string of ASCII characters of codes 32 to 126.
+   *
+   * The generated string includes alpha-numeric characters and common misc
+   * characters. Use this method when testing general input where the content
+   * is not restricted.
+   *
+   * @param $length
+   *   Length of random string to generate which will be appended to $db_prefix.
+   * @return
+   *   Randomly generated string.
+   */
+  public static function randomString($length = 8) {
+    global $db_prefix;
+
+    $str = '';
+    for ($i = 0; $i < $length; $i++) {
+      $str .= chr(mt_rand(32, 126));
+    }
+    return str_replace('simpletest', 's', $db_prefix) . $str;
+  }
+
+  /**
+   * Generates a random string containing letters and numbers.
+   *
+   * The letters may be upper or lower case. This method is better for
+   * restricted inputs that do not accept certain characters. For example,
+   * when testing input fields that require machine readable values (ie without
+   * spaces and non-standard characters) this method is best.
+   *
+   * @param $length
+   *   Length of random string to generate which will be appended to $db_prefix.
+   * @return
+   *   Randomly generated string.
+   */
+  public static function randomName($length = 8) {
+    global $db_prefix;
+
+    $values = array_merge(range(65, 90), range(97, 122), range(48, 57));
+    $max = count($values) - 1;
+    $str = '';
+    for ($i = 0; $i < $length; $i++) {
+      $str .= chr($values[mt_rand(0, $max)]);
+    }
+    return str_replace('simpletest', 's', $db_prefix) . $str;
+  }
+
+}
+
+/**
+ * Test case for Drupal unit tests.
+ *
+ * These tests can not access the database nor files. Calling any Drupal
+ * function that needs the database will throw exceptions. These include
+ * watchdog(), function_exists(), module_implements(),
+ * module_invoke_all() etc.
+ */
+class DrupalUnitTestCase extends DrupalTestCase {
+
+  /**
+   * Constructor for DrupalUnitTestCase.
+   */
+  function __construct($test_id = NULL) {
+    parent::__construct($test_id);
+    $this->skipClasses[__CLASS__] = TRUE;
+  }
+
+  function setUp() {
+    global $db_prefix, $conf;
+
+    // Store necessary current values before switching to prefixed database.
+    $this->originalPrefix = $db_prefix;
+    $this->originalFileDirectory = file_directory_path();
+
+    // Generate temporary prefixed database to ensure that tests have a clean starting point.
+    $db_prefix = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}');
+    $conf['file_public_path'] = $this->originalFileDirectory . '/' . $db_prefix;
+
+    // If locale is enabled then t() will try to access the database and
+    // subsequently will fail as the database is not accessible.
+    $module_list = module_list();
+    if (isset($module_list['locale'])) {
+      $this->originalModuleList = $module_list;
+      unset($module_list['locale']);
+      module_list(TRUE, FALSE, FALSE, $module_list);
+    }
+  }
+
+  function tearDown() {
+    global $db_prefix, $conf;
+    if (preg_match('/simpletest\d+/', $db_prefix)) {
+      $conf['file_public_path'] = $this->originalFileDirectory;
+      // Return the database prefix to the original.
+      $db_prefix = $this->originalPrefix;
+      // Restore modules if necessary.
+      if (isset($this->originalModuleList)) {
+        module_list(TRUE, FALSE, FALSE, $this->originalModuleList);
+      }
+    }
+  }
+}
+
+/**
+ * Test case for typical Drupal tests.
+ */
+class DrupalWebTestCase extends DrupalTestCase {
+  /**
+   * The URL currently loaded in the internal browser.
+   *
+   * @var string
+   */
+  protected $url;
+
+  /**
+   * The handle of the current cURL connection.
+   *
+   * @var resource
+   */
+  protected $curlHandle;
+
+  /**
+   * The headers of the page currently loaded in the internal browser.
+   *
+   * @var Array
+   */
+  protected $headers;
+
+  /**
+   * The content of the page currently loaded in the internal browser.
+   *
+   * @var string
+   */
+  protected $content;
+
+  /**
+   * The content of the page currently loaded in the internal browser (plain text version).
+   *
+   * @var string
+   */
+  protected $plainTextContent;
+
+  /**
+   * The parsed version of the page.
+   *
+   * @var SimpleXMLElement
+   */
+  protected $elements = NULL;
+
+  /**
+   * The current user logged in using the internal browser.
+   *
+   * @var bool
+   */
+  protected $loggedInUser = FALSE;
+
+  /**
+   * The current cookie file used by cURL.
+   *
+   * We do not reuse the cookies in further runs, so we do not need a file
+   * but we still need cookie handling, so we set the jar to NULL.
+   */
+  protected $cookieFile = NULL;
+
+  /**
+   * Additional cURL options.
+   *
+   * DrupalWebTestCase itself never sets this but always obeys what is set.
+   */
+  protected $additionalCurlOptions = array();
+
+  /**
+   * The original user, before it was changed to a clean uid = 1 for testing purposes.
+   *
+   * @var object
+   */
+  protected $originalUser = NULL;
+
+  /**
+   * HTTP authentication credentials (<username>:<password>).
+   */
+  protected $httpauth_credentials = NULL;
+
+  /**
+   * The current session name, if available.
+   */
+  protected $session_name = NULL;
+
+  /**
+   * The current session ID, if available.
+   */
+  protected $session_id = NULL;
+
+  /**
+   * Constructor for DrupalWebTestCase.
+   */
+  function __construct($test_id = NULL) {
+    parent::__construct($test_id);
+    $this->skipClasses[__CLASS__] = TRUE;
+  }
+
+  /**
    * Get a node from the database based on its title.
    *
    * @param title
@@ -494,13 +704,14 @@
    *
    * @param $settings
    *   An associative array of settings to change from the defaults, keys are
-   *   node properties, for example 'body' => 'Hello, world!'.
+   *   node properties, for example 'title' => 'Hello, world!'.
    * @return
    *   Created node object.
    */
   protected function drupalCreateNode($settings = array()) {
-    // Populate defaults array
+    // Populate defaults array.
     $settings += array(
+//      'body'      => array(FIELD_LANGUAGE_NONE => array(array())),
       'body'      => $this->randomName(32),
       'title'     => $this->randomName(8),
       'comment'   => 2,
@@ -523,11 +734,6 @@
       $settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O');
     }
 
-    // Add the default teaser.
-    if (!isset($settings['teaser'])) {
-      $settings['teaser'] = $settings['body'];
-    }
-
     // If the node's user uid is not specified manually, use the currently
     // logged in user if available, or else the user running the test.
     if (!isset($settings['uid'])) {
@@ -540,11 +746,21 @@
       }
     }
 
+//    // Merge body field value and format separately.
+//    $body = array(
+//      'value' => $this->randomName(32),
+//      'format' => FILTER_FORMAT_DEFAULT
+//    );
+//    $settings['body'][FIELD_LANGUAGE_NONE][0] += $body;
+
     $node = (object) $settings;
     node_save($node);
 
-    // small hack to link revisions to our test user
-//    db_query('UPDATE {node_revision} SET uid = %d WHERE vid = %d', $node->uid, $node->vid);
+    // Small hack to link revisions to our test user.
+//    db_update('node_revision')
+//      ->fields(array('uid' => $node->uid))
+//      ->condition('vid', $node->vid)
+//      ->execute();
     db_query('UPDATE {node_revisions} SET uid = %d WHERE vid = %d', $node->uid, $node->vid);
     return $node;
   }
@@ -559,24 +775,26 @@
    *   Created content type.
    */
   protected function drupalCreateContentType($settings = array()) {
-    // find a non-existent random type name.
+    // Find a non-existent random type name.
     do {
+//      $name = strtolower($this->randomName(8));
+//    } while (node_type_get_type($name));
       $name = strtolower($this->randomName(3, 'type_'));
     } while (node_get_types('type', $name));
 
-    // Populate defaults array
+    // Populate defaults array.
     $defaults = array(
       'type' => $name,
       'name' => $name,
       'description' => '',
       'help' => '',
-      'min_word_count' => 0,
+      'min_word_count' => 0, // Drupal 6.
       'title_label' => 'Title',
       'body_label' => 'Body',
       'has_title' => 1,
       'has_body' => 1,
     );
-    // imposed values for a custom type
+    // Imposed values for a custom type.
     $forced = array(
       'orig_type' => '',
       'old_type' => '',
@@ -615,7 +833,7 @@
 
     // Make sure type is valid.
     if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) {
-     // Use original file directory instead of one created during setUp().
+      // Use original file directory instead of one created during setUp().
       $path = $this->originalFileDirectory . '/simpletest';
 //      $files = file_scan_directory($path, '/' . $type . '\-.*/');
       $files = file_scan_directory($path, '' . $type . '\-.*');
@@ -623,10 +841,10 @@
       // If size is set then remove any files that are not of that size.
       if ($size !== NULL) {
         foreach ($files as $file) {
-//          $stats = stat($file->filepath);
+//          $stats = stat($file->uri);
           $stats = stat($file->filename);
           if ($stats['size'] != $size) {
-//            unset($files[$file->filepath]);
+//            unset($files[$file->uri]);
             unset($files[$file->filename]);
           }
         }
@@ -640,7 +858,7 @@
    * Compare two files based on size and file name.
    */
   protected function drupalCompareFiles($file1, $file2) {
-//    $compare_size = filesize($file1->filepath) - filesize($file2->filepath);
+//    $compare_size = filesize($file1->uri) - filesize($file2->uri);
     $compare_size = filesize($file1->filename) - filesize($file2->filename);
     if ($compare_size) {
       // Sort by file size.
@@ -653,27 +871,6 @@
   }
 
   /**
-   * Generates a random string.
-   *
-   * @param $number
-   *   Number of characters in length to append to the prefix.
-   * @param $prefix
-   *   Prefix to use.
-   * @return
-   *   Randomly generated string.
-   */
-  public static function randomName($number = 4, $prefix = 'simpletest_') {
-    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_';
-    for ($x = 0; $x < $number; $x++) {
-      $prefix .= $chars{mt_rand(0, strlen($chars) - 1)};
-      if ($x == 0) {
-        $chars .= '0123456789';
-      }
-    }
-    return $prefix;
-  }
-
-  /**
    * Create a user with a given set of permissions. The permissions correspond to the
    * names given on the privileges page.
    *
@@ -683,9 +880,9 @@
    *   A fully loaded user object with pass_raw property, or FALSE if account
    *   creation fails.
    */
-  protected function drupalCreateUser($permissions = NULL) {
+  protected function drupalCreateUser($permissions = array('access comments', 'access content', 'post comments', 'post comments without approval')) {
     // Create a role with the given permission set.
-    if (!($rid = $this->_drupalCreateRole($permissions))) {
+    if (!($rid = $this->drupalCreateRole($permissions))) {
       return FALSE;
     }
 
@@ -714,32 +911,34 @@
    *
    * @param $permissions
    *   Array of permission names to assign to role.
+   * @param $name
+   *   (optional) String for the name of the role.  Defaults to a random string.
    * @return
    *   Role ID of newly created role, or FALSE if role creation failed.
    */
-  protected function _drupalCreateRole(array $permissions = NULL) {
-    // Generate string version of permissions list.
-    if ($permissions === NULL) {
-      $permissions = array('access comments', 'access content', 'post comments', 'post comments without approval');
+  protected function drupalCreateRole(array $permissions, $name = NULL) {
+    // Generate random name if it was not passed.
+    if (!$name) {
+      $name = $this->randomName();
     }
 
+    // Check the all the permissions strings are valid.
     if (!$this->checkPermissions($permissions)) {
       return FALSE;
     }
 
     // Create new role.
-    $role_name = $this->randomName();
-    db_query("INSERT INTO {role} (name) VALUES ('%s')", $role_name);
-    $role = db_fetch_object(db_query("SELECT * FROM {role} WHERE name = '%s'", $role_name));
-    $this->assertTrue($role, t('Created role of name: @role_name, id: @rid', array('@role_name' => $role_name, '@rid' => (isset($role->rid) ? $role->rid : t('-n/a-')))), t('Role'));
+//    $role = new stdClass();
+//    $role->name = $name;
+//    user_role_save($role);
+//    user_role_set_permissions($role->name, $permissions);
+    db_query("INSERT INTO {role} (name) VALUES ('%s')", $name);
+    $role = db_fetch_object(db_query("SELECT * FROM {role} WHERE name = '%s'", $name));
+
+    $this->assertTrue(isset($role->rid), t('Created role of name: @name, id: @rid', array('@name' => $name, '@rid' => (isset($role->rid) ? $role->rid : t('-n/a-')))), t('Role'));
     if ($role && !empty($role->rid)) {
-//      // Assign permissions to role and mark it for clean-up.
-//      foreach ($permissions as $permission_string) {
-//        db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", $role->rid, $permission_string);
-//      }
-//      $count = db_result(db_query("SELECT COUNT(*) FROM {role_permission} WHERE rid = %d", $role->rid));
+//      $count = db_query('SELECT COUNT(*) FROM {role_permission} WHERE rid = :rid', array(':rid' => $role->rid))->fetchField();
 //      $this->assertTrue($count == count($permissions), t('Created permissions: @perms', array('@perms' => implode(', ', $permissions))), t('Role'));
-//      return $role->rid;
 
       // Assign permissions to role and mark it for clean-up.
       db_query("INSERT INTO {permission} (rid, perm) VALUES (%d, '%s')", $role->rid, implode(', ', $permissions));
@@ -763,10 +962,11 @@
    *   TRUE or FALSE depending on whether the permissions are valid.
    */
   protected function checkPermissions(array $permissions, $reset = FALSE) {
+//    $available = &drupal_static(__FUNCTION__);
     static $available;
 
     if (!isset($available) || $reset) {
-//      $available = array_keys(module_invoke_all('perm'));
+//      $available = array_keys(module_invoke_all('permission'));
       $available = module_invoke_all('perm');
     }
 
@@ -818,25 +1018,32 @@
     );
     $this->drupalPost('user', $edit, t('Log in'));
 
-    $pass = $this->assertText($user->name, t('Found name: %name', array('%name' => $user->name)), t('User login'));
-    $pass = $pass && $this->assertNoText(t('The username %name has been blocked.', array('%name' => $user->name)), t('No blocked message at login page'), t('User login'));
-    $pass = $pass && $this->assertNoText(t('The name %name is a reserved username.', array('%name' => $user->name)), t('No reserved message at login page'), t('User login'));
+    // If a "log out" link appears on the page, it is almost certainly because
+    // the login was successful.
+    $pass = $this->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array('%name' => $user->name)), t('User login'));
 
     if ($pass) {
       $this->loggedInUser = $user;
     }
   }
 
+  /**
+   * Generate a token for the currently logged in user.
+   */
+  protected function drupalGetToken($value = '') {
+    $private_key = drupal_get_private_key();
+    return md5($this->session_id . $value . $private_key);
+  }
+
   /*
    * Logs a user out of the internal browser, then check the login page to confirm logout.
    */
   protected function drupalLogout() {
-    // Make a request to the logout page.
-//    $this->drupalGet('user/logout');
-    $this->drupalGet('logout');
-
-    // Load the user page, the idea being if you were properly logged out you should be seeing a login screen.
-    $this->drupalGet('user');
+    // Make a request to the logout page, and redirect to the user page, the
+    // idea being if you were properly logged out you should be seeing a login
+    // screen.
+//    $this->drupalGet('user/logout', array('query' => 'destination=user'));
+    $this->drupalGet('logout', array('query' => 'destination=user'));
     $pass = $this->assertField('name', t('Username field found.'), t('Logout'));
     $pass = $pass && $this->assertField('pass', t('Password field found.'), t('Logout'));
 
@@ -856,22 +1063,43 @@
    *   List of modules to enable for the duration of the test.
    */
   protected function setUp() {
-    global $db_prefix, $user, $language; // $language (Drupal 6).
+    global $db_prefix, $user, $language;
 
     // Store necessary current values before switching to prefixed database.
+    $this->originalLanguage = $language;
+    $this->originalLanguageDefault = variable_get('language_default');
     $this->originalPrefix = $db_prefix;
     $this->originalFileDirectory = file_directory_path();
+    $this->originalProfile = drupal_get_profile();
     $clean_url_original = variable_get('clean_url', 0);
 
-    // Must reset locale here, since schema calls t().  (Drupal 6)
+    // Must reset locale here, since schema calls t(). (Drupal 6)
     if (module_exists('locale')) {
       $language = (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => '');
       locale(NULL, NULL, TRUE);
     }
 
     // Generate temporary prefixed database to ensure that tests have a clean starting point.
-//    $db_prefix = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}');
-    $db_prefix = 'simpletest' . mt_rand(1000, 1000000);
+    $db_prefix_new = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}');
+    $db_prefix_new = $db_prefix . 'simpletest' . mt_rand(1000, 1000000);
+//    db_update('simpletest_test_id')
+//      ->fields(array('last_prefix' => $db_prefix_new))
+//      ->condition('test_id', $this->testId)
+//      ->execute();
+    db_query("UPDATE {simpletest_test_id}
+              SET last_prefix = '%s'
+              WHERE test_id = %d", $db_prefix_new, $this->testId);
+    $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_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+    file_check_directory($directory, FILE_CREATE_DIRECTORY);
+
+    // Log fatal errors.
+    ini_set('log_errors', 1);
+    ini_set('error_log', $directory . '/error.log');
 
 //    include_once DRUPAL_ROOT . '/includes/install.inc';
     include_once './includes/install.inc';
@@ -879,12 +1107,24 @@
 
 //    $this->preloadRegistry();
 
+//    // Include the default profile
+//    variable_set('install_profile', 'default');
+//    $profile_details = install_profile_info('default', 'en');
+
     // Add the specified modules to the list of modules in the default profile.
-    $args = func_get_args();
-//    $modules = array_unique(array_merge(drupal_get_profile_modules('default', 'en'), $args));
-    $modules = array_unique(array_merge(drupal_verify_profile('default', 'en'), $args));
-//    drupal_install_modules($modules, TRUE);
-    drupal_install_modules($modules);
+    // Install the modules specified by the default profile.
+//    drupal_install_modules($profile_details['dependencies'], TRUE);
+    drupal_install_modules(drupal_verify_profile('default', 'en'));
+
+//    node_type_clear();
+
+    // Install additional modules one at a time in order to make sure that the
+    // list of modules is updated between each module's installation.
+    $modules = func_get_args();
+    foreach ($modules as $module) {
+//      drupal_install_modules(array($module), TRUE);
+      drupal_install_modules(array($module));
+    }
 
     // Because the schema is static cached, we need to flush
     // it between each run. If we don't, then it will contain
@@ -893,10 +1133,13 @@
     drupal_get_schema(NULL, TRUE);
 
     // Run default profile tasks.
+//    $install_state = array();
+//    drupal_install_modules(array('default'), TRUE);
     $task = 'profile';
     default_profile_tasks($task, '');
 
     // Rebuild caches.
+//    node_types_rebuild();
     actions_synchronize();
     _drupal_flush_css_js();
     $this->refreshVariables();
@@ -912,28 +1155,46 @@
 
     // Restore necessary variables.
     variable_set('install_profile', 'default');
+//    variable_set('install_task', 'done');
     variable_set('install_task', 'profile-finished');
     variable_set('clean_url', $clean_url_original);
     variable_set('site_mail', 'simpletest@example.com');
-
-    // Use temporary files directory with the same prefix as database.
-    variable_set('file_directory_path', $this->originalFileDirectory . '/' . $db_prefix);
-    $directory = file_directory_path();
-    // Create the files directory.
+//    // Set up English language.
+//    unset($GLOBALS['conf']['language_default']);
+//    $language = language_default();
+
+    // Use the test mail class instead of the default mail handler class.
+    variable_set('mail_sending_system', array('default-system' => 'TestingMailSystem'));
+
+    // Use temporary files directory with the same prefix as the database.
+//    $public_files_directory  = $this->originalFileDirectory . '/' . $db_prefix;
+//    $private_files_directory = $public_files_directory . '/private';
+    $directory = $this->originalFileDirectory . '/' . $db_prefix;
+
+    // Set path variables
+//    variable_set('file_public_path', $public_files_directory);
+//    variable_set('file_private_path', $private_files_directory);
+    variable_set('file_directory_path', $directory);
+
+    // Create the directories
+//    $directory = file_directory_path('public');
+//    file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+//    file_prepare_directory($private_files_directory, FILE_CREATE_DIRECTORY);
     file_check_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
 
+//    drupal_set_time_limit($this->timeLimit);
     set_time_limit($this->timeLimit);
   }
 
-  /**
-   * This method is called by DrupalWebTestCase::setUp, and preloads the
-   * registry from the testing site to cut down on the time it takes to
-   * setup a clean environment for the current test run.
-   */
-  protected function preloadRegistry() {
-    db_query('INSERT INTO {registry} SELECT * FROM ' . $this->originalPrefix . 'registry');
-    db_query('INSERT INTO {registry_file} SELECT * FROM ' . $this->originalPrefix . 'registry_file');
-  }
+//  /**
+//   * This method is called by DrupalWebTestCase::setUp, and preloads the
+//   * registry from the testing site to cut down on the time it takes to
+//   * setup a clean environment for the current test run.
+//   */
+//  protected function preloadRegistry() {
+//    db_query('INSERT INTO {registry} SELECT * FROM ' . $this->originalPrefix . 'registry');
+//    db_query('INSERT INTO {registry_file} SELECT * FROM ' . $this->originalPrefix . 'registry_file');
+//  }
 
   /**
    * Refresh the in-memory set of variables. Useful after a page request is made
@@ -950,7 +1211,8 @@
   protected function refreshVariables() {
     global $conf;
     cache_clear_all('variables', 'cache');
-    $conf = variable_init();
+//    $conf = variable_initialize();
+    $conf = variable_initialize();
   }
 
   /**
@@ -958,12 +1220,25 @@
    * and reset the database prefix.
    */
   protected function tearDown() {
-    global $db_prefix, $user;
+    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('drupal_test_email_collector', 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));
+      $this->pass($message, t('E-mail'));
+    }
+
     if (preg_match('/simpletest\d+/', $db_prefix)) {
-      // Delete temporary files directory and reset files directory path.
+      // Delete temporary files directory.
 //      file_unmanaged_delete_recursive(file_directory_path());
       simpletest_clean_temporary_directory(file_directory_path());
-      variable_set('file_directory_path', $this->originalFileDirectory);
 
       // Remove all prefixed tables (all the tables in the schema).
       $schema = drupal_get_schema(NULL, TRUE);
@@ -993,7 +1268,7 @@
       // Reload module list and implementations to ensure that test module hooks
       // aren't called after tests.
       module_list(TRUE);
-//      module_implements(MODULE_IMPLEMENTS_CLEAR_CACHE);
+//      module_implements('', FALSE, TRUE);
       module_implements('', '', TRUE);
 
       // Reset the Field API.
@@ -1002,6 +1277,12 @@
       // Rebuild caches.
       $this->refreshVariables();
 
+      // Reset language.
+      $language = $this->originalLanguage;
+      if ($this->originalLanguageDefault) {
+        $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault;
+      }
+
       // Close the CURL handler.
       $this->curlClose();
     }
@@ -1010,12 +1291,14 @@
   /**
    * Initializes the cURL connection.
    *
-   * This function will add authentication headers as specified in the
-   * simpletest_httpauth_username and simpletest_httpauth_pass variables. Also,
-   * see the description of $curl_options among the properties.
+   * If the simpletest_httpauth_credentials variable is set, this function will
+   * add HTTP authentication headers. This is necessary for testing sites that
+   * are protected by login credentials from public access.
+   * See the description of $curl_options for other options.
    */
   protected function curlInitialize() {
     global $base_url, $db_prefix;
+
     if (!isset($this->curlHandle)) {
       $this->curlHandle = curl_init();
       $curl_options = $this->additionalCurlOptions + array(
@@ -1024,20 +1307,22 @@
         CURLOPT_FOLLOWLOCATION => TRUE,
         CURLOPT_MAXREDIRS => 5,
         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_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, $matches)) {
-        $curl_options[CURLOPT_USERAGENT] = $matches[0];
-      }
-      if (!isset($curl_options[CURLOPT_USERPWD]) && ($auth = variable_get('simpletest_httpauth_username', ''))) {
-        if ($pass = variable_get('simpletest_httpauth_pass', '')) {
-          $auth .= ':' . $pass;
-        }
-        $curl_options[CURLOPT_USERPWD] = $auth;
+      if (isset($this->httpauth_credentials)) {
+        $curl_options[CURLOPT_USERPWD] = $this->httpauth_credentials;
       }
       curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
+
+      // By default, the child session name should be the same as the parent.
+      $this->session_name = session_name();
+    }
+    // We set the user agent header on each request so as to use the current
+    // time and a new uniqid.
+    if (preg_match('/simpletest\d+/', $db_prefix, $matches)) {
+      curl_setopt($this->curlHandle, CURLOPT_USERAGENT, drupal_generate_test_ua($matches[0]));
     }
   }
 
@@ -1061,7 +1346,11 @@
       $curl_options[CURLOPT_HTTPHEADER][] = 'Expect:';
     }
     curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
+
+    // Reset headers and the session ID.
+    $this->session_id = NULL;
     $this->headers = array();
+
     $this->drupalSetContent(curl_exec($this->curlHandle), curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL));
     $message_vars = array(
       '!method' => !empty($curl_options[CURLOPT_NOBODY]) ? 'HEAD' : (empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'),
@@ -1086,6 +1375,7 @@
    */
   protected function curlHeaderCallback($curlHandler, $header) {
     $this->headers[] = $header;
+
     // Errors are being sent via X-Drupal-Assertion-* headers,
     // generated by _drupal_log_error() in the exact form required
     // by DrupalWebTestCase::error().
@@ -1093,6 +1383,17 @@
       // Call DrupalWebTestCase::error() with the parameters from the header.
       call_user_func_array(array(&$this, 'error'), unserialize(urldecode($matches[1])));
     }
+
+    // Save the session cookie, if set.
+    if (preg_match('/^Set-Cookie: ' . preg_quote($this->session_name) . '=([a-z90-9]+)/', $header, $matches)) {
+      if ($matches[1] != 'deleted') {
+        $this->session_id = $matches[1];
+      }
+      else {
+        $this->session_id = NULL;
+      }
+    }
+
     // This is required by cURL.
     return strlen($header);
   }
@@ -1158,6 +1459,9 @@
     if (($new = $this->checkForMetaRefresh())) {
       $out = $new;
     }
+    $this->verbose('GET request to: ' . $path .
+                   '<hr />Ending URL: ' . $this->getUrl() .
+                   '<hr />' . $out);
     return $out;
   }
 
@@ -1216,12 +1520,13 @@
         // We post only if we managed to handle every field in edit and the
         // submit button matches.
         if (!$edit && $submit_matches) {
+          $post_array = $post;
           if ($upload) {
             // TODO: cURL handles file uploads for us, but the implementation
             // is broken. This is a less than elegant workaround. Alternatives
             // are being explored at #253506.
             foreach ($upload as $key => $file) {
-              $file = realpath($file);
+              $file = drupal_realpath($file);
               if ($file && is_file($file)) {
                 $post[$key] = '@' . $file;
               }
@@ -1244,6 +1549,10 @@
           if (($new = $this->checkForMetaRefresh())) {
             $out = $new;
           }
+          $this->verbose('POST request to: ' . $path .
+                         '<hr />Ending URL: ' . $this->getUrl() .
+                         '<hr />Fields: ' . highlight_string('<?php ' . var_export($post_array, TRUE), TRUE) .
+                         '<hr />' . $out);
           return $out;
         }
       }
@@ -1436,8 +1745,8 @@
    *   The xpath string to use in the search.
    * @return
    *   The return value of the xpath search. For details on the xpath string
-   *   format and return values see the SimpleXML documentation.
-   *   http://us.php.net/manual/function.simplexml-element-xpath.php
+   *   format and return values see the SimpleXML documentation,
+   *   http://us.php.net/manual/function.simplexml-element-xpath.php.
    */
   protected function xpath($xpath) {
     if ($this->parse()) {
@@ -1482,11 +1791,13 @@
    *   Message to display.
    * @param $group
    *   The group this message belongs to, defaults to 'Other'.
+   * @return
+   *   TRUE if the assertion succeeded, FALSE otherwise.
    */
   protected function assertLink($label, $index = 0, $message = '', $group = 'Other') {
     $links = $this->xpath('//a[text()="' . $label . '"]');
     $message = ($message ?  $message : t('Link with label "!label" found.', array('!label' => $label)));
-    $this->assert(isset($links[$index]), $message, $group);
+    return $this->assert(isset($links[$index]), $message, $group);
   }
 
   /**
@@ -1500,11 +1811,13 @@
    *   Message to display.
    * @param $group
    *   The group this message belongs to, defaults to 'Other'.
+   * @return
+   *   TRUE if the assertion succeeded, FALSE otherwise.
    */
   protected function assertNoLink($label, $message = '', $group = 'Other') {
     $links = $this->xpath('//a[text()="' . $label . '"]');
     $message = ($message ?  $message : t('Link with label "!label" not found.', array('!label' => $label)));
-    $this->assert(empty($links), $message, $group);
+    return $this->assert(empty($links), $message, $group);
   }
 
   /**
@@ -1670,6 +1983,30 @@
   }
 
   /**
+   * Gets an array containing all e-mails sent during this test case.
+   *
+   * @param $filter
+   *   An array containing key/value pairs used to filter the e-mails that are returned.
+   * @return
+   *   An array containing e-mail messages captured during the current test.
+   */
+  protected function drupalGetMails($filter = array()) {
+    $captured_emails = variable_get('drupal_test_email_collector', array());
+    $filtered_emails = array();
+
+    foreach ($captured_emails as $message) {
+      foreach ($filter as $key => $value) {
+        if (!isset($message[$key]) || $message[$key] != $value) {
+          continue 2;
+        }
+      }
+      $filtered_emails[] = $message;
+    }
+
+    return $filtered_emails;
+  }
+
+  /**
    * Sets the raw HTML content. This can be useful when a page has been fetched
    * outside of the internal browser and assertions need to be made on the
    * returned page.
@@ -1698,7 +2035,10 @@
    * @return
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertRaw($raw, $message = '%s found', $group = 'Other') {
+  protected function assertRaw($raw, $message = '', $group = 'Other') {
+    if (!$message) {
+      $message = t('Raw "@raw" found', array('@raw' => check_plain($raw)));
+    }
     return $this->assert(strpos($this->content, $raw) !== FALSE, $message, $group);
   }
 
@@ -1715,7 +2055,10 @@
    * @return
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertNoRaw($raw, $message = '%s found', $group = 'Other') {
+  protected function assertNoRaw($raw, $message = '', $group = 'Other') {
+    if (!$message) {
+      $message = t('Raw "@raw" not found', array('@raw' => check_plain($raw)));
+    }
     return $this->assert(strpos($this->content, $raw) === FALSE, $message, $group);
   }
 
@@ -1776,7 +2119,7 @@
       $this->plainTextContent = filter_xss($this->content, array());
     }
     if (!$message) {
-      $message = '"' . $text . '"' . ($not_exists ? ' not found' : ' found');
+      $message = !$not_exists ? t('"@text" found', array('@text' => $text)) : t('"@text" not found', array('@text' => $text));
     }
     return $this->assert($not_exists == (strpos($this->plainTextContent, $text) === FALSE), $message, $group);
   }
@@ -1842,7 +2185,7 @@
       $this->plainTextContent = filter_xss($this->content, array());
     }
     if (!$message) {
-      $message = '"' . $text . '"'. ($be_unique ? ' found only once' : ' found more than once');
+      $message = '"' . $text . '"' . ($be_unique ? ' found only once' : ' found more than once');
     }
     $first_occurance = strpos($this->plainTextContent, $text);
     if ($first_occurance === FALSE) {
@@ -1865,7 +2208,10 @@
    * @return
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertPattern($pattern, $message = 'Pattern %s found', $group = 'Other') {
+  protected function assertPattern($pattern, $message = '', $group = 'Other') {
+    if (!$message) {
+      $message = t('Pattern "@pattern" found', array('@pattern' => $pattern));
+    }
     return $this->assert((bool) preg_match($pattern, $this->drupalGetContent()), $message, $group);
   }
 
@@ -1881,7 +2227,10 @@
    * @return
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertNoPattern($pattern, $message = 'Pattern %s not found', $group = 'Other') {
+  protected function assertNoPattern($pattern, $message = '', $group = 'Other') {
+    if (!$message) {
+      $message = t('Pattern "@pattern" not found', array('@pattern' => $pattern));
+    }
     return $this->assert(!preg_match($pattern, $this->drupalGetContent()), $message, $group);
   }
 
@@ -2094,6 +2443,36 @@
   }
 
   /**
+   * Assert that a checkbox field in the current page is checked.
+   *
+   * @param $id
+   *   Id of field to assert.
+   * @param $message
+   *   Message to display.
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertFieldChecked($id, $message = '') {
+    $elements = $this->xpath('//input[@id="' . $id . '"]');
+    return $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), $message ? $message : t('Checkbox field @id is checked.', array('@id' => $id)), t('Browser'));
+  }
+
+  /**
+   * Assert that a checkbox field in the current page is not checked.
+   *
+   * @param $id
+   *   Id of field to assert.
+   * @param $message
+   *   Message to display.
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertNoFieldChecked($id, $message = '') {
+    $elements = $this->xpath('//input[@id="' . $id . '"]');
+    return $this->assertTrue(isset($elements[0]) && empty($elements[0]['checked']), $message ? $message : t('Checkbox field @id is not checked.', array('@id' => $id)), t('Browser'));
+  }
+
+  /**
    * Assert that a field exists with the given name or id.
    *
    * @param $field
@@ -2155,4 +2534,83 @@
     $match = is_array($code) ? in_array($curl_code, $code) : $curl_code == $code;
     return $this->assertTrue($match, $message ? $message : t('HTTP response expected !code, actual !curl_code', array('!code' => $code, '!curl_code' => $curl_code)), t('Browser'));
   }
+
+  /**
+   * Assert that the most recently sent e-mail message has a field with the given value.
+   *
+   * @param $name
+   *   Name of field or message property to assert. Examples: subject, body, id, ...
+   * @param $value
+   *   Value of the field to assert.
+   * @param $message
+   *   Message to display.
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertMail($name, $value = '', $message = '') {
+    $captured_emails = variable_get('drupal_test_email_collector', array());
+    $email = end($captured_emails);
+    return $this->assertTrue($email && isset($email[$name]) && $email[$name] == $value, $message, t('E-mail'));
+  }
+
+  /**
+   * Log verbose message in a text file.
+   *
+   * The a link to the vebose message will be placed in the test results via
+   * as a passing assertion with the text '[verbose message]'.
+   *
+   * @param $message
+   *   The verbose message to be stored.
+   * @see simpletest_verbose()
+   */
+  protected function verbose($message) {
+    if ($id = simpletest_verbose($message)) {
+      $this->pass(l(t('Verbose message'), $this->originalFileDirectory . '/simpletest/verbose/' . get_class($this) . '-' . $id . '.html', array('attributes' => array('target' => '_blank'))), 'Debug');
+    }
+  }
+
+}
+
+/**
+ * Log verbose message in a text file.
+ *
+ * If verbose mode is enabled then page requests will be dumped to a file and
+ * presented on the test result screen. The messages will be placed in a file
+ * located in the simpletest directory in the original file system.
+ *
+ * @param $message
+ *   The verbose message to be stored.
+ * @param $original_file_directory
+ *   The original file directory, before it was changed for testing purposes.
+ * @param $test_class
+ *   The active test case class.
+ * @return
+ *   The ID of the message to be placed in related assertion messages.
+ * @see DrupalTestCase->originalFileDirectory
+ * @see DrupalWebTestCase->verbose()
+ */
+function simpletest_verbose($message, $original_file_directory = NULL, $test_class = NULL) {
+  static $file_directory = NULL, $class = NULL, $id = 1;
+//  $verbose = &drupal_static(__FUNCTION__);
+  static $verbose;
+
+  // Will pass first time during setup phase, and when verbose is TRUE.
+  if (!isset($original_file_directory) && !$verbose) {
+    return FALSE;
+  }
+
+  if ($message && $file_directory) {
+    $message = '<hr />ID #' . $id . ' (<a href="' . $class . '-' . ($id - 1) . '.html">Previous</a> | <a href="' . $class . '-' . ($id + 1) . '.html">Next</a>)<hr />' . $message;
+    file_put_contents($file_directory . "/simpletest/verbose/$class-$id.html", $message, FILE_APPEND);
+    return $id++;
+  }
+
+  if ($original_file_directory) {
+    $file_directory = $original_file_directory;
+    $class = $test_class;
+    $verbose = variable_get('simpletest_verbose', FALSE);
+    $directory = $file_directory . '/simpletest/verbose';
+    return file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
+  }
+  return FALSE;
 }
