diff --git dbtng_example/dbtng_example.info dbtng_example/dbtng_example.info
new file mode 100644
index 0000000..6a3346f
--- /dev/null
+++ dbtng_example/dbtng_example.info
@@ -0,0 +1,8 @@
+; $Id$
+name = DBTNG example
+description = An example module showing how use the database API: DBTNG.
+package = Example modules
+core = 7.x
+files[] = dbtng_example.module
+files[] = dbtng_example.install
+files[] = dbtng_example.test
diff --git dbtng_example/dbtng_example.install dbtng_example/dbtng_example.install
new file mode 100644
index 0000000..f41eaa3
--- /dev/null
+++ dbtng_example/dbtng_example.install
@@ -0,0 +1,116 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Install, update and uninstall functions for the dbtng example module.
+ */
+
+/**
+ * Implementation of hook_install().
+ *
+ * In Drupal 7, there is no need to install schema using this hook, the schema
+ * is already installed before this hook is called.
+ *
+ * We will create a default entry in the database.
+ *
+ * @see hook_install()
+ */
+function dbtng_example_install() {
+  // Outside of the .install file we would use drupal_write_record() to
+  // populate the database, but it cannot be used here, so we'll use
+  // db_insert().
+
+  // Add a default entry.
+  $fields = array(
+    'name'    => 'John',
+    'surname' => 'Doe',
+    'age'     => 0,
+  );
+  db_insert('dbtng_example')
+    ->fields($fields)
+    ->execute();
+
+  // Add another entry.
+  $fields = array(
+    'name'    => 'John',
+    'surname' => 'Roe',
+    'age'     => 100,
+    'uid'     => 1,
+  );
+  db_insert('dbtng_example')
+    ->fields($fields)
+    ->execute();
+
+}
+
+/**
+ * Implementation of hook_uninstall().
+ *
+ * As in hook_install, there is no need to uninstall schema, Drupal will do it
+ * for us.
+ *
+ * @see hook_uninstall()
+ */
+function dbtng_example_uninstall() {
+}
+
+
+/**
+ * Implements hook_schema().
+ *
+ * Define the database tables used by this module.
+ * Remember that the easiest way to create the code for hook_schema is with
+ * the schema module:
+ * @link http://drupal.org/project/schema @endlink
+ *
+ * @see hook_schema()
+ */
+function dbtng_example_schema() {
+
+  $schema['dbtng_example'] = array(
+    'description' => 'Stores example person entries for demonstration purposes.',
+    'fields' => array(
+      'pid'  => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => 'Primary Key: Unique person ID.',
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => "Creator user's {users}.uid",
+      ),
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Name of the person.',
+      ),
+      'surname' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Surname of the person.',
+      ),
+      'age' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => 'The age of the person in years.',
+      )
+    ),
+    'primary key' => array('pid'),
+    'indexes' => array(
+      'name'    => array('name'),
+      'surname' => array('surname'),
+      'age'     => array('age'),
+    ),
+  );
+
+  return $schema;
+}
diff --git dbtng_example/dbtng_example.module dbtng_example/dbtng_example.module
new file mode 100644
index 0000000..7328aa3
--- /dev/null
+++ dbtng_example/dbtng_example.module
@@ -0,0 +1,608 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * This is an example outlining how a module can make use of the new DBTNG
+ * database API in Drupal 7.
+ *
+ * @todo Demonstrate transaction usage.
+ *
+ * General documentation is available at
+ * @link database Database abstraction layer documentation @endlink and
+ * at @link http://drupal.org/node/310069 @endlink.
+ *
+ */
+
+/**
+ * @defgroup database_examples Database Examples
+ * @{
+ * These examples show basic database examples, including DBTNG.
+ *
+ * General documentation is available at
+ * @link database Database abstraction layer documentation @endlink and
+ * at @link http://drupal.org/node/310069 @endlink.
+ *
+ * The several examples here demonstrate basic database usage.
+ *
+ * In Drupal 6, the recommended method to save or update an entry in the
+ * database was @link drupal_write_record() drupal_write_record() @endlink, but
+ * common use was to use @link db_query() db_query() @endlink for many use
+ * cases including inserts.
+ *
+ * @code
+ *   $query = "INSERT INTO {dbtng_example} (name, surname) VALUES ('%s','%s')";
+ *   db_query($query, $name, $surname);
+ * @endcode
+ *
+ * In Drupal 7 and forward, the usage of @link db_query() db_query() @endlink
+ * for INSERT, UPDATE, or DELETE
+ * is deprecated, because it is database-dependent. Instead specific functions
+ * are provided to perform these operations: @link db_insert() db_insert()
+ * @endlink, @link db_update() db_update() @endlink and
+ * @link db_delete() db_delete() @endlink.
+ *
+ * db_insert() example:
+ * @code
+ *   // INSERT INTO {dbtng_example} (name, surname) VALUES('John, 'Doe')
+ *   db_insert('dbtng_example')
+ *     ->fields(array('name' => 'John', 'surname' => 'Doe'))
+ *     ->execute();
+ * @endcode
+ *
+ * db_update() example:
+ * @code
+ *   // UPDATE {dbtng_example} SET name = 'Jane' WHERE name = 'John'
+ *   db_update('dbtng_example')
+ *     ->fields(array('name' => 'Jane'))
+ *     ->condition('name', 'John')
+ *     ->execute();
+ * @endcode
+ *
+ * db_delete() example:
+ * @code
+ *   // DELETE FROM {dbtng_example} WHERE name = 'Jane'
+ *   db_delete('dbtng_example')
+ *     ->condition('name', 'Jane')
+ *     ->execute();
+ * @endcode
+ *
+ * See @link database Database Abstraction Layer @endlink
+ * @see drupal_write_record()
+ * @see db_insert()
+ * @see db_update()
+ * @see db_delete()
+ * @}
+*/
+
+
+
+/**
+ * Save entry in the database.
+ *
+ *  Shows both the recommended drupal_write_record() and the lower-level
+ *  DBTNG equivalent.
+ *
+ * The preferred API where possible is drupal_write_record().
+ * The underlying DBTNG version is db_insert().
+ *
+ * This example will show both.
+ *
+ * In Drupal 6, this would have been:
+ * @code
+ *   db_query(
+ *     "INSERT INTO {dbtng_example} (name, surname, age)
+ *       VALUES ('%s', '%s', '%d')",
+ *     $entry['name'],
+ *     $entry['surname'],
+ *     $entry['age']
+ *   );
+ *
+ * Exception handling is shown in this example. It can be simplified
+ * without the try/catch blocks, but since an insert will throw and exception
+ * and terminate your application if the exception is not handled, it's
+ * best to employ try/catch.
+ *
+ * @param $entry
+ *   an array containing all the fields of the database record.
+ *
+ * @ingroup database_examples
+ * @see db_insert()
+ * @see drupal_write_record()
+ */
+function dbtng_example_entry_insert($entry) {
+  // The third argument, $primary_keys, is omitted, so this is an insert. The
+  // insert will throw an exception if the key already exists, so we'll wrap
+  // it in a try/catch.
+  try {
+    $return_value = drupal_write_record('dbtng_example', $entry);
+  } catch (Exception $e) {
+    // This would be a watchdog() call in a real application.
+    drupal_set_message(t('drupal_write_record failed. Message = %message, query= %query',
+      array('%message' => $e->getMessage(), '%query' => $e->query_string)), 'error');
+  }
+
+  $entry = (array)$entry; // drupal_write_record can silently convert.
+  // Now delete the entry we just created, so we can insert with db_insert().
+  $return_value = db_delete('dbtng_example')
+    ->condition('pid', $entry['pid'])
+    ->execute();
+
+  // Insert again, this time using the lower-level DBTNG call.
+  try {
+    $return_value = db_insert('dbtng_example')
+                    ->fields($entry)
+                    ->execute();
+  }
+  catch (Exception $e) {
+    drupal_set_message(t('db_insert failed. Message = %message, query= %query',
+      array('%message' => $e->getMessage(), '%query' => $e->query_string)), 'error');
+  }
+}
+
+/**
+ * Update an entry in the database.
+ *
+ * Updating can be done using drupal_write_record (preferred) or the lower-level
+ * db_update. This example will show both.
+ *
+ * The former, deprecated technique was:
+ * @code
+ * db_query(
+ *   "UPDATE {dbtng_example}
+ *    SET name = '%s', surname = '%s', age = '%d'
+ *    WHERE pid = %d",
+ *    $entry['pid']
+ * );
+ * @endcode
+ *
+ * @param $entry
+ *   an array containing all the fields of the item to be updated.
+ *
+ * @ingroup database_examples
+ * @see db_write_record
+ * @see db_update
+ */
+function dbtng_example_entry_update($entry) {
+
+  // Preferred technique is drupal_write_record:
+  try {
+    $result = drupal_write_record('dbtng_example', $entry, array('pid'));
+    drupal_set_message(t("drupal_write_record() attempted to update record for entry @entry", array('@entry' => print_r($entry, TRUE))));
+  }
+  catch (Exception $e) {
+    // This would be a watchdog() call or other handling in a real application.
+    drupal_set_message(t('drupal_write_record failed. Message = %message, query= %query',
+      array('%message' => $e->getMessage(), '%query' => $e->query_string)), 'error');
+  }
+
+  $entry = (array)$entry; // drupal_write_record silently converts to object!
+  // Alternate, lower-level technique with DBTNG:
+  try {
+    $result = db_update('dbtng_example')
+              ->fields($entry)
+              ->condition('pid', $entry['pid'])
+              ->execute();
+  }
+  catch(Exception $e) {
+    drupal_set_message(t('db_update failed. Message = %message, query= %query',
+      array('%message' => $e->getMessage(), '%query' => $e->query_string)), 'error');
+  }
+}
+
+/**
+ * Delete an entry in the database.
+ *
+ * @param $entry
+ *   an array containing at least the person identifier 'pid' element.
+ *
+ * @ingroup database_examples
+ * @see db_delete
+ */
+function dbtng_example_entry_delete($entry) {
+  // The usage of db_query is deprecated.
+  // Formerly, a deletion might have been accomplished like this:
+  // @code
+  //   db_query("DELETE FROM {dbtng_example} WHERE pid = %d", $entry['pid]);
+  // @endcode
+  //
+  // In Drupal 7:
+  db_delete('dbtng_example')
+    ->condition('pid', $entry['pid'])
+    ->execute();
+
+}
+
+
+/**
+ * Read from the database using a filter array.
+ *
+ * In Drupal 6, the standard function to perform reads was db_query().
+ *
+ * db_query() used an SQL query with placeholders and arguments as parameters.
+
+ * @code
+ *  // Old way
+ *  $query = "SELECT * FROM {dbtng_example} n WHERE n.uid = %d AND name = '%s'";
+ *  $result = db_query($query, $uid, $name);
+ * @endcode
+ *
+ * Drupal 7 DBTNG provides an abstracted interface that will work with a wide
+ * variety of database engines.
+ *
+ * db_query() is deprecated for selects, but can be used as here:
+ *
+ * @code
+ *   // SELECT * FROM {dbtng_example} WHERE uid = 0 AND name = 'John'
+ *   db_query(
+ *     "SELECT * FROM {dbtng_example} WHERE uid = :uid and name = :name",
+ *     array(':uid' => 0, ':name' => 'John')
+ *   )->execute();
+ * @endcode
+ *
+ * But for select statements, Drupal provides the db_select() API method. This
+ * end up in several ways to perform the same SQL query.
+ *
+ * @code
+ *   // SELECT * FROM {dbtng_example} WHERE uid = 0 AND name = 'John'
+ *   db_select('dbtng_example')
+ *     ->condition('uid', 0)
+ *     ->condition('name', 'John')
+ *     ->execute();
+ * @endcode
+ *
+ *
+ * Here is db_select with named placeholders:
+ * @code
+ *   // SELECT * FROM {dbtng_example} WHERE uid = 0 AND name = 'John'
+ *   $arguments = array(':name' => 'John', ':uid' => 0);
+ *   db_select('dbtng_example')
+ *     ->where('uid = :uid AND name = :name', $arguments)
+ *     ->execute();
+ * @endcode
+ *
+ * Conditions are stacked and evaluated as AND and OR depending on the type of
+ * query. For more information, read the conditional queries
+ * handbook page at: http://drupal.org/node/310086
+ *
+ * The condition argument is an 'equal' evaluation by default, but this can be
+ * altered:
+ * @code
+ *   // SELECT * FROM {dbtng_example} WHERE age > 18
+ *   db_select('dbtng_example')
+ *     ->condition('age', 18, '>')
+ *     ->execute();
+ * @endcode
+ *
+ * @param $entry
+ *   an array containing all the fields used to search the entries in the table.
+ * @return
+ *   an object containing the loaded entries if found.
+ *
+ * @ingroup database_examples
+ * @see db_select()
+ * @see db_query()
+ */
+function dbtng_example_entry_load($entry = array()) {
+  // Read from the dbtng_example table, read all fields of this table.
+  $select = db_select('dbtng_example', 'example');
+  $select->fields('example');
+
+  // Add each field and value as a condition to this query.
+  foreach ($entry as $field => $value) {
+    $select->condition($field, $value);
+  }
+  // Return the result in object format.
+  return $select->execute()->fetchAll();
+}
+
+
+/**
+ * Render a filtered list of entries in the database.
+ *
+ * DBTNG also helps processing queries returning several rows, providing the
+ * found objects in the same query execution call.
+ *
+ * This function queries the database using a JOIN between users table and the
+ * example entries, to provide the username that created the entry, and creates
+ * a table with the results, processing each row.
+ *
+ * SELECT
+ *  e.pid as pid, e.name as name, e.surname as surname, e.age as age
+ *  u.name as username
+ * FROM
+ *  {dbtng_example} e
+ * JOIN
+ *  users u ON e.uid = u.uid
+ * WHERE
+ *  e.name = 'John' AND e.age > 18
+ *
+ * @see db_select()
+ */
+function dbtng_example_advanced_list() {
+  $output = '';
+
+  $select = db_select('dbtng_example', 'e');
+  // Join the users table, so we can get the entry creator's username
+  $select->join('users', 'u', 'e.uid = u.uid');
+  // Select these specific fields for the output
+  $select->addField('e', 'pid');
+  $select->addField('u', 'name', 'username');
+  $select->addField('e', 'name');
+  $select->addField('e', 'surname');
+  $select->addField('e', 'age');
+  // Filter only persons named "John"
+  $select->condition('e.name', 'John');
+  // Filter only persons older than 18 years
+  $select->condition('e.age', 18, '>');
+
+  // Now, loop all these entries an show them in a table. Note that there is no
+  // db_fetch_* object or array function being called here.
+  $entries = $select->execute()->fetchAll();
+  if (!empty($entries)) {
+    $rows = array();
+    foreach ($entries as $entry) {
+      // Each entry is a row, forcing to be array.
+      $rows[] = (array) $entry;
+    }
+    // Make a table for them.
+    $header = array(t('Id'), t('Created by'), t('Name'), t('Surname'), t('Age'));
+    $output .= theme('table', array('header' => $header, 'rows' => $rows));
+  }
+  else {
+    drupal_set_message(t('No entries meet the filter criteria (Name = "John" and Age > 18).'));
+  }
+  return $output;
+}
+
+//// Helper functions ////
+
+/**
+ * Implements hook_help().
+ *
+ * Show some help on each form provided by this module.
+ */
+function dbtng_example_help($path) {
+  $output = '';
+  switch ($path) {
+    case 'examples/dbtng':
+      $output = t('Generate a list of all entries in the database. There is no filter in the query.');
+      break;
+    case 'examples/dbtng/advanced':
+      $output  = t('A more complex list of entries in the database. ');
+      $output .= t('Only the entries with name = "John" and older than 18 years are shown, and the username of the person who created the entry is also shown.');
+      break;
+    case 'examples/dbtng/update':
+      $output = t('Demonstrates an update operation.');
+      break;
+    case 'examples/dbtng/add':
+      $output = t('Add an entry to the dbtng_example table.');
+      break;
+  }
+  return $output;
+}
+
+
+/**
+ * Set up calls to drupal_get_form() for all our example cases.
+ *
+ * Implements hook_menu().
+ */
+function dbtng_example_menu() {
+  $items = array();
+
+  $items['examples/dbtng'] = array(
+    'title' => 'DBTNG Example',
+    'page callback' => 'dbtng_example_list',
+    'access callback' => TRUE,
+  );
+  $items['examples/dbtng/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['examples/dbtng/advanced'] = array(
+    'title' => 'Advanced list',
+    'page callback' => 'dbtng_example_advanced_list',
+    'access callback' => TRUE,
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['examples/dbtng/add'] = array(
+    'title' => 'Add entry',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('dbtng_example_form_add'),
+    'access callback' => TRUE,
+    'type' => MENU_LOCAL_TASK,
+    'weight' => -9,
+  );
+  $items['examples/dbtng/update'] = array(
+    'title' => 'Update entry',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('dbtng_example_form_update'),
+    'type' => MENU_LOCAL_TASK,
+    'access callback' => TRUE,
+    'weight' => -5,
+  );
+
+  return $items;
+}
+
+/**
+ * Render a list of entries in the database.
+ */
+function dbtng_example_list() {
+  $output = '';
+
+  // Get all entries in the dbtng_example table
+  if ($entries = dbtng_example_entry_load()) {
+    $rows = array();
+    foreach ($entries as $entry) {
+      // Each entry is a row, forcing to be array.
+      // TODO: Sanitize output. Look at how other places sanitize.
+      $rows[] = (array) $entry;
+    }
+    // Make a table for them.
+    $header = array(t('Id'), t('uid'), t('Name'), t('Surname'), t('Age'));
+    $output .= theme('table', array('header' => $header, 'rows' => $rows));
+  }
+  else {
+    drupal_set_message(t('No entries have been added yet.'));
+  }
+  return $output;
+}
+
+
+
+/**
+ * Prepare a simple form to add an entry, with all the interesting fields.
+ */
+function dbtng_example_form_add($form, &$form_state) {
+  $form = array();
+
+  $form['add'] = array(
+    '#type'  => 'fieldset',
+    '#title' => t('Add a person entry'),
+  );
+  $form['add']['name'] = array(
+    '#type'  => 'textfield',
+    '#title' => t('Name'),
+    '#size'  => 15,
+  );
+  $form['add']['surname'] = array(
+    '#type'  => 'textfield',
+    '#title' => t('Surname'),
+    '#size'  => 15,
+  );
+  $form['add']['age'] = array(
+    '#type'  => 'textfield',
+    '#title' => t('Age'),
+    '#size'  => 5,
+    '#description' => t("Values greater than 127 will cause an exception. Try it - it's a great example why exception handling is needed with DTBNG."),
+  );
+  $form['add']['submit'] = array(
+    '#type'  => 'submit',
+    '#value' => t('Add'),
+  );
+
+  return $form;
+}
+
+/**
+ * Submit handler for 'add entry' form.
+ */
+function dbtng_example_form_add_submit($form, $form_state){
+global $user;
+
+  // Save the submitted entry.
+  $entry = array(
+    'name'    => $form_state['values']['name'],
+    'surname' => $form_state['values']['surname'],
+    'age'     => $form_state['values']['age'],
+    'uid'     => $user->uid,
+  );
+  dbtng_example_entry_insert($entry);
+}
+
+/**
+ * Sample UI to update a record.
+ */
+function dbtng_example_form_update($form, &$form_state) {
+  $form = array(
+    '#prefix' => '<div id="updateform">',
+    '#suffix' => '</div>',
+  );
+
+  $entries = dbtng_example_entry_load();
+  $keyed_entries = array();
+  if (empty($entries)) {
+    $form['no_values'] = array(
+      '#value' => t("No entries exist in the table dbtng_example table"),
+    );
+    return $form;
+  }
+
+  foreach ($entries as $entry) {
+    $options[$entry->pid] = t("@pid: @name @surname (@age)",
+      array('@pid' => $entry->pid, '@name' => $entry->name, '@surname' => $entry->surname,
+        '@age' => $entry->age));
+      $keyed_entries[$entry->pid] = $entry;
+  }
+  $default_entry = !empty($form_state['values']['pid']) ? $keyed_entries[$form_state['values']['pid']] : $entries[0];
+
+  $form['entry'] = array(
+    '#type' => 'value',
+    '#value' => $default_entry,
+ );
+
+ $form['pid'] = array(
+    '#type'  => 'select',
+    '#options' => $options,
+    '#title' => t('Choose entry to update'),
+    '#default_value' => $default_entry->pid,
+    '#ajax' => array(
+      'wrapper' => 'updateform',
+      'callback' => 'dbtng_example_form_update_callback',
+    ),
+  );
+
+  $form['name'] = array(
+    '#type'  => 'textfield',
+    '#title' => t('Updated first name'),
+    '#size'  => 15,
+    '#default_value' => $default_entry->name,
+  );
+
+  $form['surname'] = array(
+    '#type'  => 'textfield',
+    '#title' => t('Updated last name'),
+    '#size'  => 15,
+    '#default_value' => $default_entry->surname,
+  );
+  $form['age'] = array(
+    '#type'  => 'textfield',
+    '#title' => t('Updated age'),
+    '#size'  => 4,
+    '#default_value' => $default_entry->age,
+    '#description' => t("Values greater than 127 will cause an exception"),
+  );
+
+  $form['submit'] = array(
+    '#type'  => 'submit',
+    '#value' => t('Update'),
+  );
+  return $form;
+}
+
+/**
+ * AJAX callback handler for the pid select.
+ *
+ * When the pid changes, populates the defaults from the database in the form.
+ */
+function dbtng_example_form_update_callback($form, $form_state) {
+  $entry = $form_state['values']['entry'];
+  // Setting the #value of items is the only way I was able to figure out
+  // to get replaced defaults on these items. #default_value will not do it
+  // and shouldn't.
+  foreach (array('name', 'surname', 'age') as $item) {
+    $form[$item]['#value'] = $entry->$item;
+  }
+  return $form;
+}
+
+/**
+ * Submit handler for 'update entry' form.
+ */
+function dbtng_example_form_update_submit($form, $form_state){
+global $user;
+
+  // Save the submitted entry.
+  $entry = array(
+    'pid'     => $form_state['values']['pid'],
+    'name'    => $form_state['values']['name'],
+    'surname' => $form_state['values']['surname'],
+    'age'     => $form_state['values']['age'],
+    'uid'     => $user->uid,
+  );
+  dbtng_example_entry_update($entry);
+  drupal_set_message(t("Updated entry @entry", array('@entry' => print_r($entry, TRUE))));
+}
diff --git dbtng_example/dbtng_example.test dbtng_example/dbtng_example.test
new file mode 100644
index 0000000..755d805
--- /dev/null
+++ dbtng_example/dbtng_example.test
@@ -0,0 +1,182 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * test file for dbtng_example module.
+ */
+
+/**
+ * Default test case for the dbtng_example module.
+ */
+class DBTNGExampleUnitTestCase extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'DBTNG example unit and UI tests',
+      'description' => 'Various unit tests on the dbtng example module.' ,
+      'group' => 'Examples',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('dbtng_example');
+  }
+
+  /**
+   * Test default module installation, two entries in the database table.
+   */
+  function testInstall() {
+    $result = dbtng_example_entry_load();
+    $this->assertEqual(
+      count($result),
+      2,
+      t('Found two entries in the table after installing the module.')
+    );
+  }
+
+
+  /**
+   * Test the UI.
+   */
+  function testUI() {
+    // Test the basic list.
+    $this->drupalGet('examples/dbtng');
+    $this->assertPattern("/John[td\/<>\w]+Doe/", t("Text 'John Doe' found in table"));
+
+    //Test the add tab.
+    // Add the new entry.
+    $this->drupalPost('examples/dbtng/add',
+      array('name'  => 'Some', 'surname' => 'Anonymous', 'age' => 33), t('Add'));
+    // Now find the new entry.
+    $this->drupalGet('examples/dbtng');
+    $this->assertPattern("/Some[td\/<>\w]+Anonymous/", t("Text 'Some Anonymous' found in table"));
+
+
+    // Try the update tab.
+    // Find out the pid of our "anonymous" guy.
+    $result = dbtng_example_entry_load(array('surname' => 'Anonymous'));
+    $this->drupalGet("examples/dbtng");
+    $this->assertEqual(
+      count($result),
+      1,
+      t('Found one entry in the table with surname = "Anonymous".')
+    );
+    $entry = $result[0];
+    unset($entry->uid);
+    $entry->name = 'NewFirstName';
+    $this->drupalPost('examples/dbtng/update',
+      (array)$entry, t('Update'));
+    // Now find the new entry.
+    $this->drupalGet('examples/dbtng');
+    $this->assertPattern("/NewFirstName[td\/<>\w]+Anonymous/", t("Text 'NewFirstName Anonymous' found in table"));
+
+    // Try the advanced tab.
+    $this->drupalGet('examples/dbtng/advanced');
+    $rows = $this->xpath("//*[@id='block-system-main']/div/table[1]/tbody/tr");
+    $this->assertEqual(count($rows), 1, t("One row found in advanced view"));
+    $this->assertFieldByXPath("//*[@id='block-system-main']/div/table[1]/tbody/tr/td[4]", "Roe", "Name 'Roe' Exists in advanced list");
+  }
+
+  /**
+   * Test several combinations, adding entries, updating and deleting.
+   */
+  function testAPIExamples() {
+    // Create a new entry.
+    $entry = array(
+      'name' => 'James',
+      'surname' => 'Doe',
+      'age' => 23,
+    );
+    dbtng_example_entry_insert($entry);
+
+    // Save another entry
+    $entry = array(
+      'name' => 'Jane',
+      'surname' => 'NotDoe',
+      'age' => 19,
+    );
+    dbtng_example_entry_insert($entry);
+
+    // Verify that 4 records are found in the database
+    $result = dbtng_example_entry_load();
+    $this->assertEqual(
+      count($result),
+      4,
+      t('Found a total of four entries in the table after creating two additional entries.')
+    );
+
+    // Verify 2 of these records have 'Doe' as surname
+    $result = dbtng_example_entry_load(array('surname' => 'Doe'));
+    $this->assertEqual(
+      count($result),
+      2,
+      t('Found two entries in the table with surname = "Doe".')
+    );
+
+    // Now find our not-Doe entry.
+    $result = dbtng_example_entry_load(array('surname' => 'NotDoe'));
+    $this->assertEqual(
+      count($result),
+      1,
+      t('Found one entry in the table with surname "NotDoe'));
+    // Our NotDoe will be changed to "NowDoe".
+    $entry = $result[0];
+    $entry->surname = "NowDoe";
+    dbtng_example_entry_update($entry);
+
+    $result = dbtng_example_entry_load(array('surname' => 'NowDoe'));
+    $this->assertEqual(
+      count($result),
+      1,
+      t("Found renamed 'NowDoe' surname"));
+
+    // Read only John Doe entry.
+    $result = dbtng_example_entry_load(array('name' => 'John', 'surname' => 'Doe'));
+    $this->assertEqual(
+      count($result),
+      1,
+      t('Found one entry for John Doe.')
+    );
+    // Get the entry
+    $entry = (array) end($result);
+    // Change age to 45
+    $entry['age'] = 45;
+    // Update entry in database
+    dbtng_example_entry_update($entry);
+
+    // Find entries with age = 45
+    // Read only John Doe entry.
+    $result = dbtng_example_entry_load(array('surname' => 'NowDoe'));
+    $this->assertEqual(
+      count($result),
+      1,
+      t('Found one entry with surname = Nowdoe.')
+    );
+
+    // Verify it is Jane NowDoe.
+    $entry = (array) end($result);
+    $this->assertEqual(
+      $entry['name'],
+      'Jane',
+      t('The name Jane is found in the entry')
+    );
+    $this->assertEqual(
+      $entry['surname'],
+      'NowDoe',
+      t('The surname NowDoe is found in the entry')
+    );
+
+    // Delete the entry.
+    dbtng_example_entry_delete($entry);
+
+    // Verify that now there are only 3 records
+    $result = dbtng_example_entry_load();
+    $this->assertEqual(
+      count($result),
+      3,
+      t('Found only three records, a record was deleted.')
+    );
+  }
+}
+
diff --git trigger_example/trigger_example.info trigger_example/trigger_example.info
index 4054aab..0a2f656 100644
--- trigger_example/trigger_example.info
+++ trigger_example/trigger_example.info
@@ -2,7 +2,7 @@
 name = Trigger example
 description = An example showing how a module can provide triggers that can have actions associated with them.
 package = Example modules
-version = VERSION
 core = 7.x
 dependencies[] = trigger
 files[] = trigger_example.module
+files[] = trigger_example.test
diff --git trigger_example/trigger_example.module trigger_example/trigger_example.module
index d794427..b6d4e48 100644
--- trigger_example/trigger_example.module
+++ trigger_example/trigger_example.module
@@ -2,153 +2,280 @@
 // $Id$
 
 /**
- * Implementation of hook_help().
- */
-function trigger_example_help($path, $arg) {
-  switch ($path) {
-    case 'trigger_example':
-      return '<p>'. t('Use this page to call hook_trigger_example() with the ping and pong operations. You can create actions to display a message or email the user on the <a href="@actions-url">Actions settings page</a> and assign these actions with the ping and pong events on the <a href="@triggers-url">Triggers settings page</a>.', array('@actions-url' => url('admin/settings/actions'), '@triggers-url' => url('admin/build/trigger/trigger_example'))) .'</p>';
-    case 'admin/build/trigger/trigger_example':
-      $explanation = '<p>'. t('Triggers are system events, such as when new content is added or when a user logs in. Trigger module combines these triggers with actions (functional tasks), such as unpublishing content or e-mailing an administrator. The <a href="@url">Actions settings page</a> contains a list of existing actions and provides the ability to create and configure additional actions.', array('@url' => url('admin/settings/actions'))) .'</p>';
-      return $explanation .'<p>'. t('Below you can assign actions to run when certain comment-related triggers happen. For example, you could promote a post to the front page when a comment is added.') .'</p>';
-  }
-}
-
-/**
- * Implementation of hook_menu().
+ * @file
+ * Trigger definition example module.
  *
- * Provide a form that can be used to fire the module's triggers.
+ * Triggers and actions are a pair of special-purpose functions allowing some
+ * Drupal programming without using PHP. Using the
+ * appropriate action in a specific event, a site administrator can
+ * add new functionality. Examples are:
+ *  - Send an email after a node is published or edited.
+ *  - Display a message after a user has logged in.
+ *  - Display a message and send an email after a node has been deleted.
+ *
+ * A Trigger is a special function able to enqueue actions. The trigger module
+ * provides the interface allowing us to associate certain actions with certain
+ * triggers.
+ *
+ * Actions are functions intended to be run by triggers.
+ *
+ * A trigger should build the appropriate context for the action to be fired.
+ * Actions are very often
+ * grouped by functionality: examples are 'user', 'node', 'taxonomy'. When some
+ * actions are grouped it is because they expect the same arguments. This way,
+ * you can enqueue as many actions understanding the 'user' object as you want.
+ *
+ * Not all actions can be used in all triggers because they require different
+ * contexts. But some actions
+ * are generic enough to not require special objects in their
+ * contexts, and so can be used on every available trigger. This 'group' type
+ * is used by actions to be available for this trigger.
+ *
+ * What are good candidates to be triggers? Any function can be a trigger, as
+ * long as it has the code to call the enqueued actions, but to make Drupal
+ * more extensible, you will find hooks (from Drupal and contributed modules)
+ * very good candidates. A trigger should build the arguments, ask for enqueued
+ * actions and run them. You may define a function being a trigger, and run it
+ * through a button in the front page, or you may prepare a trigger for a hook,
+ * and everytime that hook is fired, your trigger will be.
+ *
+ * What are good candidates to be actions? any function is a possible action,
+ * the only problem is finding a trigger able to run it.
+ *
+ * This module describes how to create triggers and actions for Drupal. In this
+ * example we are providing two triggers:
+ *
+ * - A custom trigger, in its simplest form. We provide a page with a button.
+ *   This button does nothing at all, but when you click (and submit the empty
+ *   form), any actions you have configured will be executed.
+ *
+ * - A trigger which extends the capabilities of User triggers.
+ *   This creates a new event which fires the first time a user ever logs in.
+ *   In the module we will create it, and then provide a trigger for
+ *   the administrator to be able to enqueue actions. They will be executed
+ *   only the first time the user logs in the system.
+ *
+ * See:
+ *
+ * @link http://drupal.org/node/375833 Creating Triggers @endlink
+ *
+ * @link http://drupal.org/node/172152 Writing Actions @endlink
+ *
+ * @link http://drupal.org/node/199254 Triggers and Actions in Drupal 6 @endlink
+ *
+ * @see hook_trigger_info()
  */
-function trigger_example_menu() {
-  $items['trigger_example'] = array(
-    'title' => t('Trigger Example'),
-    'description' => t('Provides a form to test the triggers.'),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('trigger_example_form'),
-    'access callback' => TRUE,
-  );
-  return $items;
-}
 
 /**
- * Implementation of hook_hook_info().
+ * Implements hook_trigger_info().
  *
- * This hook is used to tell Drupal which hooks we provide that can be used
- * as triggers for actions. We provide a text description of when each of the
- * operations occurs.
+ * We call hook_trigger_info when we are defining the triggers we provide.
+ * Triggers are the events that make fire any number of assigned actions. In
+ * this example, we are registering our three new triggers, providing the group
+ * (system, user..), the callback 'hook' (only informative, does not require a
+ * real hook) function and the label to be shown in the triggers interface.
+ *
+ * Example: In the group (a tab) 'system', for the 'mail' functionality, show:
+ * An email is sent by Drupal.
  */
-function trigger_example_hook_info() {
+function trigger_example_trigger_info() {
   return array(
+    'user' => array(
+      'user_first_time_login' => array(
+        'label' => t('After a user has logged in for the first time'),
+      ),
+    ),
     'trigger_example' => array(
-      // One important thing to note here. If you want to use the trigger
-      // module's default menu and forms handlers your hook needs to have the
-      // same name as your module. If you've got the "foo" module with a hook
-      // "hook_fooapi" then you're going to have to implement hook_forms and
-      // map the form ids back to the trigger_assign_form callback.
-      'trigger_example' => array(
-        'ping' => array(
-          'runs when' => t('A ping happens'),
-        ),
-        'pong' => array(
-          'runs when' => t('A pong happens'),
-        ),
+      'triggersomething' => array(
+        'label' => t('After the triggersomething button is clicked'),
       ),
     ),
   );
 }
 
 /**
- * Function to generate ping events and invoke hook_trigger_example().
+ * Triggers are used most of the time to do something when an event happens.
+ * The most common type of event is a hook invocation,
+ * but that is not the only possibility.
+ *
  */
-function trigger_example_ping() {
-  $count = variable_get('trigger_example_pings', 0) + 1;
-
-  // Invoke hook_trigger_example() with the ping operation.
-  module_invoke_all('trigger_example', 'ping', $count);
-
-  variable_set('trigger_example_pings', $count);
-}
 
 /**
- * Function to generate pong events and invoke hook_trigger_example().
+ * triggersomething trigger: Run actions associated with an arbitrary event.
+ *
+ * Here pressing a button is a trigger. We have defined a
+ * custom function as a trigger (trigger_example_triggersomething).
+ * It will ask for all actions attached to the 'triggersomething' event,
+ * prepare a basic 'context' for them
+ * and run all of them. This could have been implemented by a hook
+ * implementation, but in this demonstration, it will just be called in a
+ * form's submit.
+ *
+ * This function is executed during the submission of the example form defined
+ * in this module.
+ *
+ * @param array $options arguments used to call the triggersomething function, if any.
  */
-function trigger_example_pong() {
-  $count = variable_get('trigger_example_pings', 0);
+function trigger_example_triggersomething($options = array()) {
+  // Ask the trigger module for all actions enqueued for the 'triggersomething' trigger.
+  $aids = trigger_get_assigned_actions('triggersomething');
+  // prepare a basic context, indicating group and "hook", and call all the
+  // actions with this context as arguments.
+  $context = array(
+    'group' => 'trigger_example',
+    'hook' => 'triggersomething'
+  );
+  actions_do(array_keys($aids), (object) $options, $context);
+}
 
-  // Invoke hook_trigger_example() with the pong operation.
-  module_invoke_all('trigger_example', 'pong', $count);
 
-  variable_set('trigger_example_pings', max(0, $count - 1));
-}
+/**
+ * The next trigger is more complex, we are providing a trigger for a
+ * new event: "user first time login". We need to create this event
+ * first.
+ */
 
 /**
- * Implementation of hook_trigger_example().
+ * Implements hook_user_login().
+ *
+ * User first login trigger: Run actions on user first login.
+ *
+ * The event "User first time login" does not exist, we should create it before
+ * it can be used. We use hook_user_login to be informed when a user logs in and
+ * try to find if the user has previously logged in before. If the user has not
+ * accessed previously, we make a call to our trigger function.
+ *
  */
-function trigger_example_trigger_example($op, $count) {
-  // Our module is dependent on the trigger module but other modules that this
-  // code get's copied into might not so it's a good idea to check first.
-  if (!module_exists('trigger')) {
-    break;
+function trigger_example_user_login(&$edit, $account, $category = NULL) {
+  // Verify user has never accessed the site: last access was creation date.
+  if ($account->created == $account->access) {
+    // Call the aproppriate trigger function
+    _trigger_example_first_time_login('user_first_time_login', $edit, $account, $category);
   }
+}
 
-  // Find any the ids of any actions associated with this hook/operation pair.
-  if ($aids = _trigger_get_hook_aids('trigger_example', $op)) {
-    // Setup the context for our trigger.
-    $context = array(
-      'hook' => 'trigger_example',
-      'op' => $op,
-      'count' => $count
-    );
-
-    // Since we're not operating on an object we need to create a dummy.
-    $dummy = new stdClass();
-    foreach ($aids as $aid => $action_info) {
-      actions_do($aid, $dummy, $context);
+/**
+ * Trigger function for "User first time login"
+ *
+ * This trigger is a user-type triggers, so is grouped with other user-type
+ * triggers. It needs to provide all the context that user-type triggers
+ * provide.  For this example, we are going to copy the trigger.module
+ * implementation for the 'User has logged in' event.
+ *
+ * This function will run all the actions assigned to the
+ * 'user_first_time_login' trigger.
+ *
+ * For testing you can use an update query like this to reset a user to
+ * "never logged in":
+ * @code
+ * update users set access=created where name='test1';
+ * @endcode
+ *
+ * @param string $hook
+ *   the trigger identification.
+ * @param array  $edit
+ *   modifications for the account object (should be empty).
+ * @param object $account
+ *   user object that has logged in.
+ * @param string $category
+ *   category of the profile.
+ *
+ */
+function _trigger_example_first_time_login($hook, &$edit, $account, $category = NULL) {
+  // Keep objects for reuse so that changes actions make to objects can persist.
+  static $objects;
+  // Get all assigned actions for the 'user_first_time_login' trigger.
+  $aids = trigger_get_assigned_actions($hook);
+  $context = array(
+    'group' => 'user',
+    'hook' => $hook,
+    'form_values' => &$edit,
+  );
+  // Instead of making a call to actions_do for all triggers, doing this loop
+  // we provide the oportunity for actions to alter the account object, and
+  // the next action should have this altered account object as argument.
+  foreach ($aids as $aid => $info) {
+    $type = $info['type'];
+    if ($type != 'user') {
+      if (!isset($objects[$type])) {
+        $objects[$type] = _trigger_normalize_user_context($type, $account);
+      }
+      $context['user'] = $account;
+      actions_do($aid, $objects[$type], $context);
+    }
+    else {
+      actions_do($aid, $account, $context, $category);
     }
   }
 }
 
 /**
-* Implementation of hook_action_info_alter().
-*
-* None of the built-in actions will be enabled for our hook by default. We
-* need to implement hook_action_info_alter() so that we can enable a couple.
-*/
-function trigger_example_action_info_alter(&$info) {
-  if (isset($info['system_message_action']['hooks'])) {
-    $info['system_message_action']['hooks']['trigger_example'] = array('ping', 'pong');
-  }
-  if (isset($info['system_send_email_action']['hooks'])) {
-    $info['system_send_email_action']['hooks']['trigger_example'] = array('ping', 'pong');
+ * Helper functions for the module interface to test the triggersomething trigger.
+ */
+
+/**
+ * Implementation of hook_help().
+ */
+function trigger_example_help($path, $arg) {
+  switch ($path) {
+    case 'examples/trigger_example':
+      $explanation = t(
+        'Click the button on this page to call trigger_example_triggersomething()
+        and fire the triggersomething event. First, you need to create an action
+        and assign it to the "After the triggersomething button is clicked" trigger,
+        or nothing will happen.  Use the <a href="@actions-url">Actions settings page</a>
+        and assign these actions to the triggersomething event on the
+        <a href="@triggers-url">Triggers settings page</a>. <br/><br/>
+        The other example is the "user never logged in before" example. For that one,
+        assign an action to the "After a user has logged in for the first time" trigger
+        and then log a user in.', array('@actions-url' => url('admin/config/system/actions'), '@triggers-url' => url('admin/structure/trigger/trigger_example')));
+      return "<p>$explanation</p>";
+    case 'admin/structure/trigger/system':
+      return t('you can assign actions to run everytime an email is sent by Drupal');
+    case 'admin/structure/trigger/trigger_example':
+      $explanation = t(
+      "A trigger is a system event. For the trigger example, it's just a button-press.
+      To demonstrate the trigger example, choose to associate the 'display a message to the user'
+      action with the 'after the triggersomething button is pressed' trigger.");
+      return "<p>$explanation</p>";
   }
 }
 
 /**
- * A form to help fire our triggers.
+ * Implementation of hook_menu().
+ *
+ * Provide a form that can be used to fire the module's triggers.
  */
-function trigger_example_form(&$form_state) {
-  $form['help'] = array(
-    '#type' => 'item',
-    '#value' => format_plural(variable_get('trigger_example_pings', 0), 'There is only @count ping out there.', 'There are @count pings out there. Come on pong them back.'),
+function trigger_example_menu() {
+  $items['examples/trigger_example'] = array(
+    'title'           => 'Trigger Example',
+    'description'     => 'Provides a form to demonstrate the trigger example.',
+    'page callback'   => 'drupal_get_form',
+    'page arguments'  => array('trigger_example_form'),
+    'access callback' => TRUE,
   );
-  $form['ping'] = array(
-    '#type' => 'submit',
-    '#value' => t('Ping'),
+  return $items;
+}
+
+/**
+ * Trigger example test form
+ *
+ * Provide a button to run the triggersomething event.
+ */
+function trigger_example_form($form_state) {
+  $form['triggersomething'] = array(
+    '#type'  => 'submit',
+    '#value' => t('Run triggersomething event'),
   );
-  if (variable_get('trigger_example_pings', 0)) {
-    $form['pong'] = array(
-      '#type' => 'submit',
-      '#value' => t('Pong'),
-    );
-  }
   return $form;
 }
 
-function trigger_example_form_submit($form, &$form_state) {
-  if ($form_state['values']['op'] == t('Ping')) {
-    trigger_example_ping();
-  }
-  else {
-    trigger_example_pong();
+/**
+ * Submit handler for the trigger_example_form.
+ */
+function trigger_example_form_submit($form, $form_state) {
+  // If the user clicked the button, then run the triggersomething trigger.
+  if ($form_state['values']['op'] == t('Run triggersomething event')) {
+    trigger_example_triggersomething();
   }
 }
diff --git trigger_example/trigger_example.test trigger_example/trigger_example.test
new file mode 100644
index 0000000..0d67472
--- /dev/null
+++ trigger_example/trigger_example.test
@@ -0,0 +1,167 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * test file for trigger_example module.
+ */
+
+/**
+ * Default test case for the trigger_example module.
+ */
+class TriggerExampleTestCase extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Trigger example',
+      'description' => 'Perform various tests on trigger_example module.' ,
+      'group' => 'Examples',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('trigger', 'trigger_example');
+  }
+
+  /**
+   * Test assigning a configurable action to the triggersomething event.
+   */
+  function testTriggersomethingEvent() {
+    // Create an administrative user.
+    $test_user = $this->drupalCreateUser(array('administer actions'));
+    $this->drupalLogin($test_user);
+
+    // Create a configurable action for display a message to the user
+    $hash = md5('system_message_action');
+    $action_label = $this->randomName();
+    $edit = array(
+      'actions_label' => $action_label,
+      'message' => $action_label,
+    );
+    $this->drupalPost('admin/config/system/actions/configure/' . $hash, $edit, t('Save'));
+    $aid = db_query('SELECT aid FROM {actions} WHERE callback = :callback', array(':callback' => 'system_message_action'))->fetchField();
+    // $aid is likely 3 but if we add more uses for the sequences table in
+    // core it might break, so it is easier to get the value from the database.
+    $edit = array('aid' => md5($aid));
+
+    // Note that this only works because there's just one item on the page.
+    $this->drupalPost('admin/structure/trigger/trigger_example', $edit, t('Assign'));
+
+    // Request triggersomething form and submit.
+    $this->drupalPost('examples/trigger_example', array(), t('Run triggersomething event'));
+    // Verify the message is shown to the user.
+    $this->assertText($action_label, t('The triggersomething event executed the action.'));
+  }
+
+  function testUserLogin() {
+    // Create an administrative user.
+    $admin_user = $this->drupalCreateUser(array('administer actions'));
+    $this->drupalLogin($admin_user);
+
+    // Create a configurable action for display a message to the user
+    $hash = md5('system_message_action');
+    $action_label = $this->randomName();
+    $edit = array(
+      'actions_label' => $action_label,
+      'message' => $action_label,
+    );
+    $this->drupalPost('admin/config/system/actions/configure/' . $hash, $edit, t('Save'));
+    $aid = db_query('SELECT aid FROM {actions} WHERE callback = :callback', array(':callback' => 'system_message_action'))->fetchField();
+    $edit = array('aid' => md5($aid));
+
+    // Unfortunately there is no current way in simpletest to choose the correct
+    // form to submit to, since all the forms on this page have the same name
+    // for their submit button. See http://drupal.org/node/709852
+    // Find the correct trigger
+//    $this->drupalGet('admin/structure/trigger/user');
+//    $form = $this->xpath("//*[@id='edit-user-first-time-login']");
+//    $button_id = (string)$form[0]->div->input->attributes()->id;
+//
+//    $this->drupalPost('admin/structure/trigger/user', $edit, t('Assign'));
+//
+//
+//    $test_user = $this->drupalCreateUser();
+//    $this->drupalLogin($test_user);
+//    $this->assertText($action_label, "Found the random message on the page after login.");
+  }
+
+  function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array()) {
+    $submit_matches = FALSE;
+    $ajax = is_array($submit);
+    if (isset($path)) {
+      $this->drupalGet($path, $options);
+    }
+    if ($this->parse()) {
+      $edit_save = $edit;
+      // Let's iterate over all the forms.
+      $forms = $this->xpath('//form');
+      foreach ($forms as $form) {
+        // We try to set the fields of this form as specified in $edit.
+        $edit = $edit_save;
+        $post = array();
+        $upload = array();
+        $submit_matches = $this->handleForm($post, $edit, $upload, $ajax ? NULL : $submit, $form);
+        $action = isset($form['action']) ? $this->getAbsoluteUrl($form['action']) : $this->getUrl();
+        if ($ajax) {
+          $action = $this->getAbsoluteUrl(!empty($submit['path']) ? $submit['path'] : 'system/ajax');
+          // AJAX callbacks verify the triggering element if necessary, so while
+          // we may eventually want extra code that verifies it in the
+          // handleForm() function, it's not currently a requirement.
+          $submit_matches = TRUE;
+        }
+
+        // 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 = drupal_realpath($file);
+              if ($file && is_file($file)) {
+                $post[$key] = '@' . $file;
+              }
+            }
+          }
+          else {
+            foreach ($post as $key => $value) {
+              // Encode according to application/x-www-form-urlencoded
+              // Both names and values needs to be urlencoded, according to
+              // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
+              $post[$key] = urlencode($key) . '=' . urlencode($value);
+            }
+            if ($ajax && isset($submit['triggering_element'])) {
+              $post['ajax_triggering_element'] = 'ajax_triggering_element=' . urlencode($submit['triggering_element']);
+            }
+            $post = implode('&', $post);
+          }
+          $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HTTPHEADER => $headers));
+          // Ensure that any changes to variables in the other thread are picked up.
+          $this->refreshVariables();
+
+          // Replace original page output with new output from redirected page(s).
+          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;
+        }
+      }
+      // We have not found a form which contained all fields of $edit.
+      foreach ($edit as $name => $value) {
+        $this->fail(t('Failed to set field @name to @value', array('@name' => $name, '@value' => $value)));
+      }
+      if (!$ajax) {
+        $this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit)));
+      }
+      $this->fail(t('Found the requested form fields at @path', array('@path' => $path)));
+    }
+  }
+
+}
+
