Merge branch 'MDL-32220' of git://github.com/timhunt/moodle
authorDan Poltawski <dan@moodle.com>
Tue, 3 Apr 2012 03:09:31 +0000 (11:09 +0800)
committerDan Poltawski <dan@moodle.com>
Tue, 3 Apr 2012 03:09:31 +0000 (11:09 +0800)
124 files changed:
admin/tool/qeupgradehelper/lang/en/tool_qeupgradehelper.php
admin/tool/unittest/ex_reporter.php
admin/tool/unittest/ex_simple_test.php
admin/tool/unittest/simpletestcoveragelib.php
admin/tool/unittest/simpletestlib.php
lib/db/access.php
lib/db/events.php
lib/db/install.php
lib/db/log.php
lib/db/messages.php
lib/db/services.php
lib/db/upgrade.php
lib/db/upgradelib.php
lib/externallib.php
lib/form/yui/checkboxcontroller/checkboxcontroller.js
lib/formslib.php
lib/javascript-static.js
lib/moodlelib.php
lib/simpletest/testcompletionlib.php
lib/simpletestlib/HELP_MY_TESTS_DONT_WORK_ANYMORE [changed mode: 0644->0755]
lib/simpletestlib/LICENSE [changed mode: 0644->0755]
lib/simpletestlib/README [changed mode: 0644->0755]
lib/simpletestlib/VERSION [changed mode: 0644->0755]
lib/simpletestlib/arguments.php [new file with mode: 0755]
lib/simpletestlib/authentication.php
lib/simpletestlib/autorun.php
lib/simpletestlib/browser.php
lib/simpletestlib/collector.php
lib/simpletestlib/compatibility.php [changed mode: 0644->0755]
lib/simpletestlib/cookies.php
lib/simpletestlib/default_reporter.php
lib/simpletestlib/detached.php [changed mode: 0644->0755]
lib/simpletestlib/docs/en/authentication_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/browser_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/docs.css [new file with mode: 0755]
lib/simpletestlib/docs/en/expectation_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/form_testing_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/group_test_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/index.html [new file with mode: 0644]
lib/simpletestlib/docs/en/mock_objects_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/overview.html [new file with mode: 0644]
lib/simpletestlib/docs/en/partial_mocks_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/reporter_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/unit_test_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/web_tester_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/authentication_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/browser_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/docs.css [new file with mode: 0755]
lib/simpletestlib/docs/fr/expectation_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/form_testing_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/group_test_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/index.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/mock_objects_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/overview.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/partial_mocks_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/reporter_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/unit_test_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/web_tester_documentation.html [new file with mode: 0644]
lib/simpletestlib/dumper.php [changed mode: 0644->0755]
lib/simpletestlib/eclipse.php
lib/simpletestlib/encoding.php
lib/simpletestlib/errors.php
lib/simpletestlib/exceptions.php [changed mode: 0644->0755]
lib/simpletestlib/expectation.php [changed mode: 0644->0755]
lib/simpletestlib/extensions/pear_test_case.php [deleted file]
lib/simpletestlib/extensions/phpunit_test_case.php [deleted file]
lib/simpletestlib/extensions/testdox.php [deleted file]
lib/simpletestlib/extensions/testdox/test.php [deleted file]
lib/simpletestlib/form.php
lib/simpletestlib/frames.php [changed mode: 0644->0755]
lib/simpletestlib/http.php
lib/simpletestlib/invoker.php [changed mode: 0644->0755]
lib/simpletestlib/mock_objects.php [changed mode: 0644->0755]
lib/simpletestlib/page.php [changed mode: 0644->0755]
lib/simpletestlib/php_parser.php [moved from lib/simpletestlib/parser.php with 51% similarity, mode: 0755]
lib/simpletestlib/readme_moodle.txt
lib/simpletestlib/recorder.php [new file with mode: 0644]
lib/simpletestlib/reflection_php4.php
lib/simpletestlib/reflection_php5.php
lib/simpletestlib/remote.php
lib/simpletestlib/reporter.php [changed mode: 0644->0755]
lib/simpletestlib/scorer.php
lib/simpletestlib/selector.php [changed mode: 0644->0755]
lib/simpletestlib/shell_tester.php
lib/simpletestlib/simpletest.php
lib/simpletestlib/socket.php [changed mode: 0644->0755]
lib/simpletestlib/tag.php
lib/simpletestlib/test_case.php
lib/simpletestlib/tidy_parser.php [new file with mode: 0755]
lib/simpletestlib/ui/colortext_reporter.php [deleted file]
lib/simpletestlib/ui/css/webunit.css [deleted file]
lib/simpletestlib/ui/img/wait.gif [deleted file]
lib/simpletestlib/ui/js/webunit.js [deleted file]
lib/simpletestlib/ui/js/x.js [deleted file]
lib/simpletestlib/ui/webunit_reporter.php [deleted file]
lib/simpletestlib/unit_tester.php [changed mode: 0644->0755]
lib/simpletestlib/url.php
lib/simpletestlib/user_agent.php
lib/simpletestlib/web_tester.php
lib/simpletestlib/xml.php [changed mode: 0644->0755]
lib/thirdpartylibs.xml
lib/upgradelib.php
mod/data/version.php
mod/feedback/backup/moodle2/backup_feedback_stepslib.php
mod/feedback/backup/moodle2/restore_feedback_stepslib.php
mod/feedback/complete.php
mod/feedback/complete_guest.php
mod/feedback/lib.php
mod/feedback/mod_form.php
mod/feedback/view.php
mod/glossary/print.php
mod/quiz/lib.php
mod/quiz/report/overview/report.php
question/editlib.php
question/engine/simpletest/testunitofwork.php
question/engine/upgrade/upgradelib.php
question/export.php
question/question.php
question/type/edit_question_form.php
question/type/questiontypebase.php
repository/wikimedia/wikimedia.php
theme/afterburner/config.php
theme/afterburner/style/afterburner_styles.css
theme/standard/style/calendar.css

index 3010666..7bd7c13 100644 (file)
@@ -48,16 +48,16 @@ $string['gotoindex'] = 'Back to the list of quizzes that can be upgraded';
 $string['gotoquizreport'] = 'Go to the reports for this quiz, to check the upgrade';
 $string['gotoresetlink'] = 'Go to the list of quizzes that can be reset';
 $string['includedintheupgrade'] = 'Included in the upgrade?';
-$string['invalidquizid'] = 'Invaid quiz id. Either the quiz does not exist, or it has no attempts to convert.';
+$string['invalidquizid'] = 'Invalid quiz id. Either the quiz does not exist, or it has no attempts to convert.';
 $string['listpreupgrade'] = 'List quizzes and attempts';
 $string['listpreupgrade_desc'] = 'This will show a report of all the quizzes on the system and how many attempts they have. This will give you an idea of the scope of the upgrade you have to do.';
 $string['listpreupgradeintro'] = 'These are the number of quiz attempts that will need to be processed when you upgrade your site. A few tens of thousands is no worry. Much beyond that and you need to think about how long the upgrade will take.';
 $string['listtodo'] = 'List quizzes still to upgrade';
 $string['listtodo_desc'] = 'This will show a report of all the quizzes on the system (if any) that have attempts that still need to be upgraded to the new question engine.';
 $string['listtodointro'] = 'These are all the quizzes with attempt data that still needs to be converted. You can convert the attempts by clicking the link.';
-$string['listupgraded'] = 'List already upgrade quizzes that can be reset';
+$string['listupgraded'] = 'List already upgraded quizzes that can be reset';
 $string['listupgraded_desc'] = 'This will show a report of all the quizzes on the system whose attempts have been upgraded, and where the old data is still present so the upgrade could be reset and redone.';
-$string['listupgradedintro'] = 'These are all the quizzes that have attempts that were upgraded, and where the old attempt data is so there, so they could be reset, and the upgrade re-done.';
+$string['listupgradedintro'] = 'These are all the quizzes that have attempts that were upgraded, and where the old attempt data is still there, so they could be reset, and the upgrade re-done.';
 $string['noquizattempts'] = 'Your site does not have any quiz attempts at all!';
 $string['nothingupgradedyet'] = 'No upgraded attempts that can be reset';
 $string['notupgradedsiterequired'] = 'This script can only work before the site has been upgraded.';
@@ -80,4 +80,4 @@ $string['resettingquizattemptsprogress'] = 'Resetting attempt {$a->done} / {$a->
 $string['upgradingquizattempts'] = 'Upgrading the attempts for quiz \'{$a->name}\' in course {$a->shortname}';
 $string['upgradedsitedetected'] = 'This appears to be a site that has been upgraded to include the new question engine.';
 $string['upgradedsiterequired'] = 'This script can only work after the site has been upgraded.';
-$string['veryoldattemtps'] = 'Your site has {$a} quiz attempts that were never completely updated during the upgrade from Moodle 1.4 to Moodle 1.5. These attempts will be dealt wiht before the main upgrade. You need to to consider the extra time required for this.';
+$string['veryoldattemtps'] = 'Your site has {$a} quiz attempts that were never completely updated during the upgrade from Moodle 1.4 to Moodle 1.5. These attempts will be dealt with before the main upgrade. You need to to consider the extra time required for this.';
index 5d440f3..b7b296c 100644 (file)
@@ -55,8 +55,8 @@ class ExHtmlReporter extends HtmlReporter {
      *
      * @param bool $showpasses Whether this reporter should output anything for passes.
      */
-    function ExHtmlReporter($showpasses) {
-        $this->HtmlReporter();
+    function __construct($showpasses) {
+        parent::__construct('UTF-8');
         $this->showpasses = $showpasses;
 
         $this->strrunonlyfolder = $this->get_string('runonlyfolder');
@@ -254,7 +254,7 @@ class ExHtmlReporter extends HtmlReporter {
         echo '<div class="performanceinfo">',
                 $this->get_string('runat', userdate($this->timestart)), ' ',
                 $this->get_string('timetakes', format_time(time() - $this->timestart)), ' ',
-                $this->get_string('version', SimpleTestOptions::getVersion()),
+                $this->get_string('version', SimpleTest::getVersion()),
                 '</div>';
     }
 
index 1428a01..dd6ee8a 100644 (file)
@@ -46,7 +46,7 @@ class AutoGroupTest extends TestSuite {
         $this->showsearch = $showsearch;
     }
 
-    function run(&$reporter) {
+    function run($reporter) {
         global $UNITTEST;
 
         $UNITTEST->running = true;
@@ -85,7 +85,7 @@ class AutoGroupTest extends TestSuite {
 
                 $s_count++;
                 // OK, found: this shows as a 'Notice' for any 'simpletest/test*.php' file.
-                $this->addTestCase(new FindFileNotice($file_path, 'Found unit test file, '. $s_count));
+                $this->add(new FindFileNotice($file_path, 'Found unit test file, '. $s_count));
 
                 // addTestFile: Unfortunately this doesn't return fail/success (bool).
                 $this->addTestFile($file_path, true);
@@ -105,9 +105,9 @@ class AutoGroupTest extends TestSuite {
         $path = $dir;
         $count = $this->_recurseFolders($path);
         if ($count <= 0) {
-            $this->addTestCase(new BadAutoGroupTest($path, 'Search complete. No unit test files found'));
+            $this->add(new BadAutoGroupTest($path, 'Search complete. No unit test files found'));
         } else {
-            $this->addTestCase(new AutoGroupTestNotice($path, 'Search complete. Total unit test files found: '. $count));
+            $this->add(new AutoGroupTestNotice($path, 'Search complete. Total unit test files found: '. $count));
         }
         if ($this->showsearch) {
                 echo '</ul>';
@@ -116,6 +116,7 @@ class AutoGroupTest extends TestSuite {
     }
 
     function addTestFile($file, $internalcall = false) {
+
         if ($this->showsearch) {
             if ($internalcall) {
                 echo '<li><b>' . basename($file) . '</b></li>';
@@ -126,10 +127,10 @@ class AutoGroupTest extends TestSuite {
             // get blank screens because evil people turn down error_reporting elsewhere.
             error_reporting(E_ALL);
         }
-        if(!is_file($file) ){
-            parent::addTestCase(new BadTest($file, 'Not a file or does not exist'));
+        if (!is_file($file) ){
+            parent::add(new BadTest($file, 'Not a file or does not exist'));
         }
-        parent::addTestFile($file);
+        parent::addFile($file);
     }
 }
 
index 88943c7..addcd83 100644 (file)
@@ -123,7 +123,7 @@ class autogroup_test_coverage extends AutoGroupTest {
      * automatically generating the coverage report. Only supports one instrumentation
      * to be executed and reported.
      */
-    public function run(&$simpletestreporter) {
+    public function run($simpletestreporter) {
         global $CFG;
 
         if (moodle_coverage_recorder::can_run_codecoverage() && $this->performcoverage) {
@@ -148,7 +148,7 @@ class autogroup_test_coverage extends AutoGroupTest {
      * allowing further process of coverage data once tests are over. Supports multiple
      * instrumentations (code coverage gathering sessions) to be executed.
      */
-    public function run_with_external_coverage(&$simpletestreporter, &$covrecorder) {
+    public function run_with_external_coverage($simpletestreporter, $covrecorder) {
 
         if (moodle_coverage_recorder::can_run_codecoverage() && $this->performcoverage) {
             $covrecorder->setIncludePaths($this->includecoverage);
index e2c573f..e60faf7 100644 (file)
@@ -82,8 +82,8 @@ function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false
 class IgnoreWhitespaceExpectation extends SimpleExpectation {
     var $expect;
 
-    function IgnoreWhitespaceExpectation($content, $message = '%s') {
-        $this->SimpleExpectation($message);
+    function __construct($content, $message = '%s') {
+        parent::__construct($message);
         $this->expect=$this->normalise($content);
     }
 
@@ -111,8 +111,8 @@ class IgnoreWhitespaceExpectation extends SimpleExpectation {
 class ArraysHaveSameValuesExpectation extends SimpleExpectation {
     var $expect;
 
-    function ArraysHaveSameValuesExpectation($expected, $message = '%s') {
-        $this->SimpleExpectation($message);
+    function __construct($expected, $message = '%s') {
+        parent::__construct($message);
         if (!is_array($expected)) {
             trigger_error('Attempt to create an ArraysHaveSameValuesExpectation ' .
                     'with an expected value that is not an array.');
@@ -149,8 +149,8 @@ class ArraysHaveSameValuesExpectation extends SimpleExpectation {
 class CheckSpecifiedFieldsExpectation extends SimpleExpectation {
     var $expect;
 
-    function CheckSpecifiedFieldsExpectation($expected, $message = '%s') {
-        $this->SimpleExpectation($message);
+    function __construct($expected, $message = '%s') {
+        parent::__construct($message);
         if (!is_object($expected)) {
             trigger_error('Attempt to create a CheckSpecifiedFieldsExpectation ' .
                     'with an expected value that is not an object.');
@@ -651,7 +651,7 @@ class UnitTestCaseUsingDatabase extends UnitTestCase {
         }
 
         // Only do this after the above text.
-        parent::UnitTestCase($label);
+        parent::__construct($label);
 
         // Create the test DB instance.
         $this->realdb = $DB;
@@ -974,7 +974,7 @@ class FakeDBUnitTestCase extends UnitTestCase {
             return;
         }
 
-        parent::UnitTestCase($label);
+        parent::__construct($label);
         // MDL-16483 Get PKs and save data to text file
 
         $this->pkfile = $CFG->dataroot.'/testtablespks.csv';
index 8fe0b86..76ebc66 100644 (file)
  *
  * The variable name for the capability definitions array is $capabilities
  *
- * @package    core_access
- * @category   access
- * @copyright  2006 onwards Martin Dougiamas  http://dougiamas.com
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * For more information, take a look to the documentation available:
+ *     - Access API: {@link http://docs.moodle.org/dev/Access_API}
+ *     - Upgrade API: {@link http://docs.moodle.org/dev/Upgrade_API}
+ *
+ * @package   core_access
+ * @category  access
+ * @copyright 2006 onwards Martin Dougiamas  http://dougiamas.com
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die();
index 768691b..51a8958 100644 (file)
 /**
  * Definition of core event handler and description of all events throws from core.
  *
- * @package core
- * @category event
+ * The handlers defined on this file are processed and registered into
+ * the Moodle DB after any install or upgrade operation. All plugins
+ * support this.
+ *
+ * For more information, take a look to the documentation available:
+ *     - Events API: {@link http://docs.moodle.org/dev/Events_API}
+ *     - Upgrade API: {@link http://docs.moodle.org/dev/Upgrade_API}
+ *
+ * @package   core
+ * @category  event
  * @copyright 2007 onwards Martin Dougiamas  http://dougiamas.com
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die();
index 8ad8f1a..f0bb45d 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 /**
  * This file is executed right after the install.xml
  *
- * @package    core
- * @subpackage admin
- * @copyright  2009 Petr Skoda (http://skodak.org)
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * For more information, take a look to the documentation available:
+ *     - Upgrade API: {@link http://docs.moodle.org/dev/Upgrade_API}
+ *
+ * @package   core_install
+ * @category  upgrade
+ * @copyright 2009 Petr Skoda (http://skodak.org)
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die();
 
+/**
+ * Main post-install tasks to be executed after the BD schema is available
+ *
+ * This function is automatically executed after Moodle core DB has been
+ * created at initial install. It's in charge of perform the initial tasks
+ * not covered by the {@link install.xml} file, like create initial users,
+ * roles, templates, moving stuff from other plugins...
+ *
+ * Note that the function is only invoked once, at install time, so if new tasks
+ * are needed in the future, they will need to be added both here (for new sites)
+ * and in the corresponding {@link upgrade.php} file (for existing sites).
+ *
+ * All plugins within Moodle (modules, blocks, reports...) support the existence of
+ * their own install.php file, using the "Frankenstyle" component name as
+ * defined at {@link http://docs.moodle.org/dev/Frankenstyle}, for example:
+ *     - {@link xmldb_page_install()}. (modules don't require the plugintype ("mod_") to be used.
+ *     - {@link xmldb_enrol_meta_install()}.
+ *     - {@link xmldb_workshopform_accumulative_install()}.
+ *     - ....
+ *
+ * Finally, note that it's also supported to have one uninstall.php file that is
+ * executed also once, each time one plugin is uninstalled (before the DB schema is
+ * deleted). Those uninstall files will contain one function, using the "Frankenstyle"
+ * naming conventions, like {@link xmldb_enrol_meta_uninstall()} or {@link xmldb_workshop_uninstall()}.
+ */
 function xmldb_main_install() {
     global $CFG, $DB, $SITE, $OUTPUT;
 
-    /// Make sure system context exists
+    // Make sure system context exists
     $syscontext = context_system::instance(0, MUST_EXIST, false);
     if ($syscontext->id != SYSCONTEXTID) {
         throw new moodle_exception('generalexceptionmessage', 'error', '', 'Unexpected new system context id!');
     }
 
 
-    /// Create site course
+    // Create site course
     if ($DB->record_exists('course', array())) {
         throw new moodle_exception('generalexceptionmessage', 'error', '', 'Can not create frontpage course, courses already exist.');
     }
@@ -69,7 +96,7 @@ function xmldb_main_install() {
     $SITE = $DB->get_record('course', array('id'=>$newsite->id), '*', MUST_EXIST);
 
 
-    /// Create default course category
+    // Create default course category
     if ($DB->record_exists('course_categories', array())) {
         throw new moodle_exception('generalexceptionmessage', 'error', '', 'Can not create default course category, categories already exist.');
     }
@@ -105,7 +132,7 @@ function xmldb_main_install() {
     }
 
 
-    /// Bootstrap mnet
+    // Bootstrap mnet
     $mnethost = new stdClass();
     $mnethost->wwwroot    = $CFG->wwwroot;
     $mnethost->name       = '';
@@ -160,7 +187,7 @@ function xmldb_main_install() {
     $mnetallhosts->id                 = $DB->insert_record('mnet_host', $mnetallhosts, true);
     set_config('mnet_all_hosts_id', $mnetallhosts->id);
 
-    /// Create guest record - do not assign any role, guest user gets the default guest role automatically on the fly
+    // Create guest record - do not assign any role, guest user gets the default guest role automatically on the fly
     if ($DB->record_exists('user', array())) {
         throw new moodle_exception('generalexceptionmessage', 'error', '', 'Can not create default users, users already exist.');
     }
@@ -186,7 +213,7 @@ function xmldb_main_install() {
     context_user::instance($guest->id);
 
 
-    /// Now create admin user
+    // Now create admin user
     $admin = new stdClass();
     $admin->auth         = 'manual';
     $admin->firstname    = get_string('admin');
@@ -209,13 +236,13 @@ function xmldb_main_install() {
         echo $OUTPUT->notification('Nonconsecutive id generated for the Admin account. Your database configuration or clustering setup may not be fully supported.', 'notifyproblem');
     }
 
-    /// Store list of admins
+    // Store list of admins
     set_config('siteadmins', $admin->id);
     // Make sure user context exists
     context_user::instance($admin->id);
 
 
-    /// Install the roles system.
+    // Install the roles system.
     $managerrole        = create_role(get_string('manager', 'role'), 'manager', get_string('managerdescription', 'role'), 'manager');
     $coursecreatorrole  = create_role(get_string('coursecreators'), 'coursecreator', get_string('coursecreatorsdescription'), 'coursecreator');
     $editteacherrole    = create_role(get_string('defaultcourseteacher'), 'editingteacher', get_string('defaultcourseteacherdescription'), 'editingteacher');
@@ -225,10 +252,10 @@ function xmldb_main_install() {
     $userrole           = create_role(get_string('authenticateduser'), 'user', get_string('authenticateduserdescription'), 'user');
     $frontpagerole      = create_role(get_string('frontpageuser', 'role'), 'frontpage', get_string('frontpageuserdescription', 'role'), 'frontpage');
 
-    /// Now is the correct moment to install capabilities - after creation of legacy roles, but before assigning of roles
+    // Now is the correct moment to install capabilities - after creation of legacy roles, but before assigning of roles
     update_capabilities('moodle');
 
-    /// Default allow assign
+    // Default allow assign
     $defaultallowassigns = array(
         array($managerrole, $managerrole),
         array($managerrole, $coursecreatorrole),
@@ -244,7 +271,7 @@ function xmldb_main_install() {
         allow_assign($fromroleid, $toroleid);
     }
 
-    /// Default allow override
+    // Default allow override
     $defaultallowoverrides = array(
         array($managerrole, $managerrole),
         array($managerrole, $coursecreatorrole),
@@ -264,7 +291,7 @@ function xmldb_main_install() {
         allow_override($fromroleid, $toroleid); // There is a rant about this in MDL-15841.
     }
 
-    /// Default allow switch.
+    // Default allow switch.
     $defaultallowswitch = array(
         array($managerrole, $editteacherrole),
         array($managerrole, $noneditteacherrole),
@@ -283,7 +310,7 @@ function xmldb_main_install() {
         allow_switch($fromroleid, $toroleid);
     }
 
-    /// Set up the context levels where you can assign each role.
+    // Set up the context levels where you can assign each role.
     set_role_contextlevels($managerrole,        get_default_contextlevels('manager'));
     set_role_contextlevels($coursecreatorrole,  get_default_contextlevels('coursecreator'));
     set_role_contextlevels($editteacherrole,    get_default_contextlevels('editingteacher'));
index 7549e34..f404e01 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Definition of log events
+ * Definition of log events associated with the current component
+ *
+ * The log events defined on this file are processed and stored into
+ * the Moodle DB after any install or upgrade operation. All plugins
+ * support this.
+ *
+ * For more information, take a look to the documentation available:
+ *     - Logging API: {@link http://docs.moodle.org/dev/Logging_API}
+ *     - Upgrade API: {@link http://docs.moodle.org/dev/Upgrade_API}
  *
- * @package    core
- * @subpackage admin
- * @copyright  2010 Petr Skoda (http://skodak.org)
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package   core
+ * @category  log
+ * @copyright 2010 Petr Skoda (http://skodak.org)
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die();
index db087fb..30bc820 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 /**
  * Defines message providers (types of messages being sent)
  *
- * @package    core
- * @subpackage message
- * @copyright  2008 onwards Martin Dougiamas  http://dougiamas.com
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * The providers defined on this file are processed and registered into
+ * the Moodle DB after any install or upgrade operation. All plugins
+ * support this.
+ *
+ * For more information, take a look to the documentation available:
+ *     - Message API: {@link http://docs.moodle.org/dev/Message_API}
+ *     - Upgrade API: {@link http://docs.moodle.org/dev/Upgrade_API}
+ *
+ * @package   core
+ * @category  message
+ * @copyright 2008 onwards Martin Dougiamas  http://dougiamas.com
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die();
 
 $messageproviders = array (
 
-/// Notices that an admin might be interested in
+    // Notices that an admin might be interested in
     'notices' => array (
          'capability'  => 'moodle/site:config'
     ),
 
-/// Important errors that an admin ought to know about
+    // Important errors that an admin ought to know about
     'errors' => array (
          'capability'  => 'moodle/site:config'
     ),
@@ -49,17 +56,17 @@ $messageproviders = array (
         'capability'  => 'moodle/site:config'
     ),
 
-    //course creation request notification
+    // Course creation request notification
     'courserequested' => array (
         'capability'  => 'moodle/site:approvecourse'
     ),
 
-    //course request approval notification
+    // Course request approval notification
     'courserequestapproved' => array (
          'capability'  => 'moodle/course:request'
     ),
 
-    //course request rejection notification
+    // Course request rejection notification
     'courserequestrejected' => array (
         'capability'  => 'moodle/course:request'
     )
index 328d29d..cdb6a57 100644 (file)
 /**
  * Core external functions and service definitions.
  *
+ * The functions and services defined on this file are
+ * processed and registered into the Moodle DB after any
+ * install or upgrade operation. All plugins support this.
+ *
+ * For more information, take a look to the documentation available:
+ *     - Webservices API: {@link http://docs.moodle.org/dev/Web_services_API}
+ *     - External API: {@link http://docs.moodle.org/dev/External_functions_API}
+ *     - Upgrade API: {@link http://docs.moodle.org/dev/Upgrade_API}
+ *
  * @package    core_webservice
  * @category   webservice
  * @copyright  2009 Petr Skodak
index 8d8ffff..2651e79 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
  * Please do not forget to use upgrade_set_timeout()
  * before any action that may take longer time to finish.
  *
- * @package    core
- * @subpackage admin
- * @copyright  2006 onwards Martin Dougiamas  http://dougiamas.com
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package   core_install
+ * @category  upgrade
+ * @copyright 2006 onwards Martin Dougiamas  http://dougiamas.com
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die();
 
 /**
+ * Main upgrade tasks to be executed on Moodle version bump
+ *
+ * This function is automatically executed after one bump in the Moodle core
+ * version is detected. It's in charge of performing the required tasks
+ * to raise core from the previous version to the next one.
+ *
+ * It's a collection of ordered blocks of code, named "upgrade steps",
+ * each one performing one isolated (from the rest of steps) task. Usually
+ * tasks involve creating new DB objects or performing manipulation of the
+ * information for cleanup/fixup purposes.
+ *
+ * Each upgrade step has a fixed structure, that can be summarised as follows:
+ *
+ * if ($oldversion < XXXXXXXXXX.XX) {
+ *     // Explanation of the update step, linking to issue in the Tracker if necessary
+ *     upgrade_set_timeout(XX); // Optional for big tasks
+ *     // Code to execute goes here, usually the XMLDB Editor will
+ *     // help you here. See {@link http://docs.moodle.org/dev/XMLDB_editor}.
+ *     upgrade_main_savepoint(true, XXXXXXXXXX.XX);
+ * }
+ *
+ * All plugins within Moodle (modules, blocks, reports...) support the existence of
+ * their own upgrade.php file, using the "Frankenstyle" component name as
+ * defined at {@link http://docs.moodle.org/dev/Frankenstyle}, for example:
+ *     - {@link xmldb_page_upgrade($oldversion)}. (modules don't require the plugintype ("mod_") to be used.
+ *     - {@link xmldb_auth_manual_upgrade($oldversion)}.
+ *     - {@link xmldb_workshopform_accumulative_upgrade($oldversion)}.
+ *     - ....
+ *
+ * In order to keep the contents of this file reduced, it's allowed to create some helper
+ * functions to be used here in the {@link upgradelib.php} file at the same directory. Note
+ * that such a file must be manually included from upgrade.php, and there are some restrictions
+ * about what can be used within it.
+ *
+ * For more information, take a look to the documentation available:
+ *     - Data definition API: {@link http://docs.moodle.org/dev/Data_definition_API}
+ *     - Upgrade API: {@link http://docs.moodle.org/dev/Upgrade_API}
  *
- * @global stdClass $CFG
- * @global stdClass $USER
- * @global moodle_database $DB
- * @global core_renderer $OUTPUT
  * @param int $oldversion
  * @return bool always true
  */
@@ -67,9 +99,8 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2011120500);
     }
 
-    ////////////////////////////////////////
-    ///upgrade supported only from 2.2.x ///
-    ////////////////////////////////////////
+    // Moodle v2.2.0 release upgrade line
+    // Put any upgrade step following this
 
     if ($oldversion < 2011120500.02) {
 
index 7960694..7696d06 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
  * because it might depend on db structures that are not yet present during upgrade.
  * (Do not use functions from accesslib.php, grades classes or group functions at all!)
  *
- * @package    core
- * @subpackage admin
- * @copyright  2007 Petr Skoda (http://skodak.org)
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package   core_install
+ * @category  upgrade
+ * @copyright 2007 Petr Skoda (http://skodak.org)
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die();
index c1f4fcd..8875307 100644 (file)
@@ -335,11 +335,11 @@ class external_api {
  * Common ancestor of all parameter description classes
  */
 abstract class external_description {
-    /** @property string $description description of element */
+    /** @var string $description description of element */
     public $desc;
-    /** @property bool $required element value required, null not allowed */
+    /** @var bool $required element value required, null not allowed */
     public $required;
-    /** @property mixed $default default value */
+    /** @var mixed $default default value */
     public $default;
 
     /**
@@ -359,9 +359,9 @@ abstract class external_description {
  * Scalar alue description class
  */
 class external_value extends external_description {
-    /** @property mixed $type value type PARAM_XX */
+    /** @var mixed $type value type PARAM_XX */
     public $type;
-    /** @property bool $allownull allow null values */
+    /** @var bool $allownull allow null values */
     public $allownull;
 
     /**
@@ -384,7 +384,7 @@ class external_value extends external_description {
  * Associative array description class
  */
 class external_single_structure extends external_description {
-     /** @property array $keys description of array keys key=>external_description */
+     /** @var array $keys description of array keys key=>external_description */
     public $keys;
 
     /**
@@ -405,7 +405,7 @@ class external_single_structure extends external_description {
  * Bulk array description class.
  */
 class external_multiple_structure extends external_description {
-     /** @property external_description $content */
+     /** @var external_description $content */
     public $content;
 
     /**
index abcbbbd..7543eb5 100644 (file)
@@ -100,7 +100,11 @@ YUI.add('moodle-form-checkboxcontroller', function(Y) {
                     controllervaluenode.set('value', '1');
                     newvalue = 'checked';
                 }
-                checkboxes.set('checked', newvalue);
+                checkboxes.each(function(checkbox){
+                    if (!checkbox.get('disabled')) {
+                        checkbox.set('checked', newvalue);
+                    }
+                });
             }
         }
     });
index 18c0c89..607fdc9 100644 (file)
@@ -1086,7 +1086,8 @@ abstract class moodleform {
         if (!is_null($contollerbutton) || is_null($selectvalue)) {
             foreach ($mform->_elements as $element) {
                 if (($element instanceof MoodleQuickForm_advcheckbox) &&
-                    $element->getAttribute('class') == $checkboxgroupclass) {
+                        $element->getAttribute('class') == $checkboxgroupclass &&
+                        !$element->isFrozen()) {
                     $mform->setConstants(array($element->getName() => $newselectvalue));
                 }
             }
index 90310a9..06dfcd9 100644 (file)
@@ -796,6 +796,9 @@ function checkall() {
     var inputs = document.getElementsByTagName('input');
     for (var i = 0; i < inputs.length; i++) {
         if (inputs[i].type == 'checkbox') {
+            if (inputs[i].disabled || inputs[i].readOnly) {
+                continue;
+            }
             inputs[i].checked = true;
         }
     }
@@ -805,6 +808,9 @@ function checknone() {
     var inputs = document.getElementsByTagName('input');
     for (var i = 0; i < inputs.length; i++) {
         if (inputs[i].type == 'checkbox') {
+            if (inputs[i].disabled || inputs[i].readOnly) {
+                continue;
+            }
             inputs[i].checked = false;
         }
     }
index fe642a9..1baadb8 100644 (file)
@@ -8692,8 +8692,9 @@ function moodle_needs_upgrading() {
  *
  * The script may be automatically aborted if upgrade times out.
  *
- * @global object
+ * @category upgrade
  * @param int $max_execution_time in seconds (can not be less than 60 s)
+ * @todo MDL-32293 - Move this function to lib/upgradelib.php
  */
 function upgrade_set_timeout($max_execution_time=300) {
     global $CFG;
index 1496494..f218c0f 100644 (file)
@@ -208,7 +208,7 @@ class completionlib_test extends UnitTestCaseUsingDatabase {
             new TimeModifiedExpectation(array('completionstate'=>COMPLETION_COMPLETE_PASS))));
         $c->update_state($cm,COMPLETION_COMPLETE_PASS);
 
-        $c->tally();
+        //$c->tally();
     }
 
     function test_internal_get_state() {
@@ -242,8 +242,8 @@ class completionlib_test extends UnitTestCaseUsingDatabase {
         // * the grade_item/grade_grade calls are static and can't be mocked
         // * the plugin_supports call is static and can't be mocked
 
-        $DB->tally();
-        $c->tally();
+        //$DB->tally();
+        //$c->tally();
     }
 
     function test_set_module_viewed() {
@@ -281,7 +281,7 @@ class completionlib_test extends UnitTestCaseUsingDatabase {
         $c->expectOnce('update_state',array($cm,COMPLETION_COMPLETE,1337));
         $c->set_module_viewed($cm,1337);
 
-        $c->tally();
+        //$c->tally();
     }
 
     function test_count_user_data() {
@@ -298,7 +298,7 @@ WHERE
         $c=new completion_info($course);
         $this->assertEqual(666,$c->count_user_data($cm));
 
-        $DB->tally();
+        //$DB->tally();
     }
 
     function test_delete_all_state() {
@@ -325,7 +325,7 @@ WHERE
         $this->assertEqual(array(13=>array(43=>'foo'),14=>array(42=>'foo')),
             $SESSION->completioncache);
 
-        $DB->tally();
+        //$DB->tally();
     }
 
     function test_reset_all_state() {
@@ -355,8 +355,8 @@ WHERE
 
         $c->reset_all_state($cm);
 
-        $DB->tally();
-        $c->tally();
+        //$DB->tally();
+        //$c->tally();
     }
 
     function test_get_data() {
@@ -447,7 +447,7 @@ WHERE
             'id'=>'0','coursemoduleid'=>14,'userid'=>314159,'completionstate'=>0,
             'viewed'=>0,'timemodified'=>0),'updated'=>$now)),$SESSION->completioncache);
 
-        $DB->tally();
+        //$DB->tally();
     }
 
     function test_internal_set_data() {
@@ -487,7 +487,7 @@ WHERE
         $DB->expectAt(1,'update_record', array('course_modules_completion', $d3));
         $c->internal_set_data($cm, $data);
 
-        $DB->tally();
+        //$DB->tally();
     }
 
     function test_get_activities() {
@@ -514,7 +514,7 @@ WHERE
         $result=$c->get_activities($modinfo);
         $this->assertEqual(array(13=>(object)array('id'=>13,'modname'=>'frog','name'=>'kermit')),$result);
 
-        $DB->tally();
+        //$DB->tally();
     }
 
     // get_tracked_users() cannot easily be tested because it uses
@@ -606,8 +606,8 @@ WHERE
         }
         $this->assertTrue($resultok);
 
-        $DB->tally();
-        $c->tally();
+        //$DB->tally();
+        //$c->tally();
     }
 
     function test_inform_grade_changed() {
@@ -652,7 +652,7 @@ WHERE
         $c->expectAt(1,'update_state',array($cm,COMPLETION_INCOMPLETE,31337));
         $c->inform_grade_changed($cm,$item,$grade,false);
 
-        $c->tally();
+        //$c->tally();
     }
 
     function test_internal_get_grade_state() {
old mode 100644 (file)
new mode 100755 (executable)
index 2d6612b..a65e83e
@@ -5,13 +5,64 @@ written with earlier versions will fail with the newest ones. The most
 dramatic changes are in the alpha releases. Here is a list of possible
 problems and their fixes...
 
+assertText() no longer finds a string inside a <script> tag
+-----------------------------------------------------------
+The assertText() method is intended to match only visible,
+human-readable text on the web page. Therefore, the contents of script
+tags should not be matched by this assertion. However there was a bug
+in the text normalisation code of simpletest which meant that <script>
+tags spanning multiple lines would not have their content stripped
+out. If you want to check the content of a <script> tag, use
+assertPattern(), or write a custom expectation.
+
+Overloaded method not working
+-----------------------------
+All protected and private methods had underscores
+removed. This means that any private/protected methods that
+you overloaded with those names need to be updated.
+
+Fatal error: Call to undefined method Classname::classname()
+------------------------------------------------------------
+SimpleTest renamed all of its constructors from 
+Classname to __construct; derived classes invoking
+their parent constructors should replace parent::Classname()
+with parent::__construct().
+
+Custom CSS in HtmlReporter not being applied
+--------------------------------------------
+Batch rename of protected and private methods 
+means that _getCss() was renamed to getCss().
+Please rename your method and it should work again.
+
+setReturnReference() throws errors in E_STRICT
+----------------------------------------------
+Happens when an object is passed by reference.
+This also happens with setReturnReferenceAt().
+If you want to return objects then replace these
+with calls to returns() and returnsAt() with the
+same arguments. This change was forced in the 1.1
+version for E_STRICT compatibility.
+
+assertReference() throws errors in E_STRICT
+-------------------------------------------
+Due to language restrictions you cannot compare
+both variables and objects in E_STRICT mode. Use
+assertSame() in this mode with objects. This change
+was forced the 1.1 version.
+
+Cannot create GroupTest
+-----------------------
+The GroupTest has been renamed TestSuite (see below).
+It was removed completely in 1.1 in favour of this
+name.
+
 No method getRelativeUrls() or getAbsoluteUrls()
 ------------------------------------------------
 These methods were always a bit weird anyway, and
 the new parsing of the base tag makes them more so.
 They have been replaced with getUrls() instead. If
 you want the old functionality then simply chop
-off the current domain from getUrl().
+off the current domain from getUrls().
 
 Method setWildcard() removed in mocks
 -------------------------------------
@@ -48,7 +99,7 @@ getTransferError() call on the web tester to see if
 there was a socket level error in a fetch. This check
 is now always carried out by the WebTestCase unless
 the fetch is prefaced with WebTestCase::ignoreErrors().
-The ignore directive only lasts for test case fetching
+The ignore directive only lasts for the next fetching
 action such as get() and click().
 
 No method SimpleTestOptions::ignore()
@@ -289,13 +340,13 @@ test case expansion against the ease of writing user interfaces.
 
 Code such as...
 
-$test = new MyTestCase();
+$test = &new MyTestCase();
 $test->attachObserver(new TestHtmlDisplay());
 $test->run();
 
 ...should be rewritten as...
 
-$test = new MyTestCase();
+$test = &new MyTestCase();
 $test->run(new HtmlReporter());
 
 If you previously attached multiple observers then the workaround
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
index c52e3f7..40f4166
@@ -1,14 +1,16 @@
 SimpleTest
 ==========
-You probably got this package from...
-http://simpletest.sourceforge.net/projects/simpletest/
+
+You probably got this package from:
+
+    http://simpletest.org/en/download.html
 
 If there is no licence agreement with this package please download
 a version from the location above. You must read and accept that
 licence to use this software. The file is titled simply LICENSE.
 
 What is it? It's a framework for unit testing, web site testing and
-mock objects for PHP 4.2.0+ (and PHP 5.0 to 5.3 without E_STRICT).
+mock objects for PHP 5.0.5+.
 
 If you have used JUnit, you will find this PHP unit testing version very
 similar. Also included is a mock objects and server stubs generator.
@@ -21,48 +23,42 @@ A web tester similar in concept to JWebUnit is also included. There is no
 JavaScript or tables support, but forms, authentication, cookies and
 frames are handled.
 
-You can see a release schedule at http://www.lastcraft.com/overview.php
+You can see a release schedule at http://simpletest.org/en/overview.html
 which is also copied to the documentation folder with this release.
 A full PHPDocumenter API documentation exists at
-http://simpletest.sourceforge.net/.
+http://simpletest.org/api/.
 
-The user interface is minimal
-in the extreme, but a lot of information flows from the test suite.
-After version 1.0 we will release a better web UI, but we are leaving XUL
-and GTk versions to volunteers as everybody has their own opinion
-on a good GUI, and we don't want to discourage development by shipping
-one with the toolkit. YOucan download an Eclipse plug-in separately.
+The user interface is minimal in the extreme, but a lot of information 
+flows from the test suite. After version 1.0 we will release a better 
+web UI, but we are leaving XUL and GTK versions to volunteers as 
+everybody has their own opinion on a good GUI, and we don't want to 
+discourage development by shipping one with the toolkit. You can 
+download an Eclipse plug-in separately. 
 
-You are looking at a second full release. The unit tests for SimpleTest
-itself can be run here...
+The unit tests for SimpleTest itself can be run here:
 
-simpletest/test/unit_tests.php
+    test/unit_tests.php
 
-And tests involving live network connections as well are here...
+And tests involving live network connections as well are here:
 
-simpletest/test/all_tests.php
+    test/all_tests.php
 
 The full tests will typically overrun the 8Mb limit often allowed
 to a PHP process. A workaround is to run the tests on the command
-with a custom php.ini file if you do not have access to your server
-version.
-
-You will have to edit the all_tests.php file if you are accesssing
-the internet through a proxy server. See the comments in all_tests.php
-for instructions.
+with a custom php.ini file or with the switch -dmemory_limit=-1
+if you do not have access to your server version.
 
-The full tests read some test data from the LastCraft site. If the site
+The full tests read some test data from simpletest.org. If the site
 is down or has been modified for a later version then you will get
 spurious errors. A unit_tests.php failure on the other hand would be
-very serious. As far as we know we haven't yet managed to check in any
-unit test failures, so please correct us if you find one.
+very serious. Please notify us if you find one.
 
 Even if all of the tests run please verify that your existing test suites
-also function as expected. If they don't see the file...
+also function as expected. The file:
 
-HELP_MY_TESTS_DONT_WORK_ANYMORE
+    HELP_MY_TESTS_DONT_WORK_ANYMORE
 
-This contains information on interface changes. It also points out
+...contains information on interface changes. It also points out
 deprecated interfaces, so you should read this even if all of
 your current tests appear to run.
 
@@ -70,19 +66,19 @@ There is a documentation folder which contains the core reference information
 in English and French, although this information is fairly basic.
 You can find a tutorial on...
 
-http://www.lastcraft.com/first_test_tutorial.php
+    http://simpletest.org/en/first_test_tutorial.html
 
 ...to get you started and this material will eventually become included
-with the project documentation. A French translation exists at...
+with the project documentation. A French translation exists at:
 
-http://www.onpk.net/index.php/2005/01/12/254-tutoriel-simpletest-decouvrir-les-tests-unitaires.
+    http://simpletest.org/fr/first_test_tutorial.html
 
 If you download and use, and possibly even extend this tool, please let us
 know. Any feedback, even bad, is always welcome and we will work to get
 your suggestions into the next release. Ideally please send your
-comments to...
+comments to:
 
-simpletest-support@lists.sourceforge.net
+    simpletest-support@lists.sourceforge.net
 
 ...so that others can read them too. We usually try to respond within 48
 hours.
@@ -93,16 +89,14 @@ status of any bugs, but if the bug is recent then it will be fixed in SVN only.
 The SVN check-ins always have all the tests passing and so SVN snapshots should
 be pretty usable, although the code may not look so good internally.
 
-Oh, yes. It is called "Simple" because it should be simple to
-use. We intend to add a complete set of tools for a test first
-and "test as you code" type of development. "Simple" does not
-mean "Lite" in this context.
+Oh, and one last thing: SimpleTest is called "Simple" because it should 
+be simple to use. We intend to add a complete set of tools for a test 
+first and "test as you code" type of development. "Simple" does not mean 
+"Lite" in this context. 
 
 Thanks to everyone who has sent comments and offered suggestions. They
 really are invaluable, but sadly you are too many to mention in full.
 Thanks to all on the advanced PHP forum on SitePoint, especially Harry
-Feucks. Early adopters are always an inspiration.
+Fuecks. Early adopters are always an inspiration.
 
-Marcus Baker, Jason Sweat, Travis Swicegood, Perrick Penet and Edward Z. Yang.
--- 
-marcus@lastcraft.com
+ -- Marcus Baker, Jason Sweat, Travis Swicegood, Perrick Penet and Edward Z. Yang.
old mode 100644 (file)
new mode 100755 (executable)
index 7f20734..9084fa2
@@ -1 +1 @@
-1.0.1
\ No newline at end of file
+1.1.0
diff --git a/lib/simpletestlib/arguments.php b/lib/simpletestlib/arguments.php
new file mode 100755 (executable)
index 0000000..89e7f9d
--- /dev/null
@@ -0,0 +1,224 @@
+<?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id: dumper.php 1909 2009-07-29 15:58:11Z dgheath $
+ */
+
+/**
+ *    Parses the command line arguments.
+ *    @package  SimpleTest
+ *    @subpackage   UnitTester
+ */
+class SimpleArguments {
+    private $all = array();
+
+    /**
+     * Parses the command line arguments. The usual formats
+     * are supported:
+     * -f value
+     * -f=value
+     * --flag=value
+     * --flag value
+     * -f           (true)
+     * --flag       (true)
+     * @param array $arguments      Normally the PHP $argv.
+     */
+    function __construct($arguments) {
+        array_shift($arguments);
+        while (count($arguments) > 0) {
+            list($key, $value) = $this->parseArgument($arguments);
+            $this->assign($key, $value);
+        }
+    }
+
+    /**
+     * Sets the value in the argments object. If multiple
+     * values are added under the same key, the key will
+     * give an array value in the order they were added.
+     * @param string $key    The variable to assign to.
+     * @param string value   The value that would norally
+     *                       be colected on the command line.
+     */
+    function assign($key, $value) {
+        if ($this->$key === false) {
+            $this->all[$key] = $value;
+        } elseif (! is_array($this->$key)) {
+            $this->all[$key] = array($this->$key, $value);
+        } else {
+            $this->all[$key][] = $value;
+        }
+    }
+
+    /**
+     * Extracts the next key and value from the argument list.
+     * @param array $arguments      The remaining arguments to be parsed.
+     *                              The argument list will be reduced.
+     * @return array                Two item array of key and value.
+     *                              If no value can be found it will
+     *                              have the value true assigned instead.
+     */
+    private function parseArgument(&$arguments) {
+        $argument = array_shift($arguments);
+        if (preg_match('/^-(\w)=(.+)$/', $argument, $matches)) {
+            return array($matches[1], $matches[2]);
+        } elseif (preg_match('/^-(\w)$/', $argument, $matches)) {
+            return array($matches[1], $this->nextNonFlagElseTrue($arguments));
+        } elseif (preg_match('/^--(\w+)=(.+)$/', $argument, $matches)) {
+            return array($matches[1], $matches[2]);
+        } elseif (preg_match('/^--(\w+)$/', $argument, $matches)) {
+            return array($matches[1], $this->nextNonFlagElseTrue($arguments));
+        }
+    }
+
+    /**
+     * Attempts to use the next argument as a value. It
+     * won't use what it thinks is a flag.
+     * @param array $arguments    Remaining arguments to be parsed.
+     *                            This variable is modified if there
+     *                            is a value to be extracted.
+     * @return string/boolean     The next value unless it's a flag.
+     */
+    private function nextNonFlagElseTrue(&$arguments) {
+        return $this->valueIsNext($arguments) ? array_shift($arguments) : true;
+    }
+
+    /**
+     * Test to see if the next available argument is a valid value.
+     * If it starts with "-" or "--" it's a flag and doesn't count.
+     * @param array $arguments    Remaining arguments to be parsed.
+     *                            Not affected by this call.
+     * boolean                    True if valid value.
+     */
+    function valueIsNext($arguments) {
+        return isset($arguments[0]) && ! $this->isFlag($arguments[0]);
+    }
+
+    /**
+     * It's a flag if it starts with "-" or "--".
+     * @param string $argument       Value to be tested.
+     * @return boolean               True if it's a flag.
+     */
+    function isFlag($argument) {
+        return strncmp($argument, '-', 1) == 0;
+    }
+
+    /**
+     * The arguments are available as individual member
+     * variables on the object.
+     * @param string $key              Argument name.
+     * @return string/array/boolean    Either false for no value,
+     *                                 the value as a string or
+     *                                 a list of multiple values if
+     *                                 the flag had been specified more
+     *                                 than once.
+     */
+    function __get($key) {
+        if (isset($this->all[$key])) {
+            return $this->all[$key];
+        }
+        return false;
+    }
+
+    /**
+     * The entire argument set as a hash.
+     * @return hash         Each argument and it's value(s).
+     */
+    function all() {
+        return $this->all;
+    }
+}
+
+/**
+ *    Renders the help for the command line arguments.
+ *    @package  SimpleTest
+ *    @subpackage   UnitTester
+ */
+class SimpleHelp {
+    private $overview;
+    private $flag_sets = array();
+    private $explanations = array();
+
+    /**
+     * Sets up the top level explanation for the program.
+     * @param string $overview        Summary of program.
+     */
+    function __construct($overview = '') {
+        $this->overview = $overview;
+    }
+
+    /**
+     * Adds the explanation for a group of flags that all
+     * have the same function.
+     * @param string/array $flags       Flag and alternates. Don't
+     *                                  worry about leading dashes
+     *                                  as these are inserted automatically.
+     * @param string $explanation       What that flag group does.
+     */
+    function explainFlag($flags, $explanation) {
+        $flags = is_array($flags) ? $flags : array($flags);
+        $this->flag_sets[] = $flags;
+        $this->explanations[] = $explanation;
+    }
+
+    /**
+     * Generates the help text.
+     * @returns string      The complete formatted text.
+     */
+    function render() {
+        $tab_stop = $this->longestFlag($this->flag_sets) + 4;
+        $text = $this->overview . "\n";
+        for ($i = 0; $i < count($this->flag_sets); $i++) {
+            $text .= $this->renderFlagSet($this->flag_sets[$i], $this->explanations[$i], $tab_stop);
+        }
+        return $this->noDuplicateNewLines($text);
+    }
+
+    /**
+     * Works out the longest flag for formatting purposes.
+     * @param array $flag_sets      The internal flag set list.
+     */
+    private function longestFlag($flag_sets) {
+        $longest = 0;
+        foreach ($flag_sets as $flags) {
+            foreach ($flags as $flag) {
+                $longest = max($longest, strlen($this->renderFlag($flag)));
+            }
+        }
+        return $longest;
+    }
+
+    /**
+     * Generates the text for a single flag and it's alternate flags.
+     * @returns string           Help text for that flag group.
+     */
+    private function renderFlagSet($flags, $explanation, $tab_stop) {
+        $flag = array_shift($flags);
+        $text = str_pad($this->renderFlag($flag), $tab_stop, ' ') . $explanation . "\n";
+        foreach ($flags as $flag) {
+            $text .= '  ' . $this->renderFlag($flag) . "\n";
+        }
+        return $text;
+    }
+
+    /**
+     * Generates the flag name including leading dashes.
+     * @param string $flag          Just the name.
+     * @returns                     Fag with apropriate dashes.
+     */
+    private function renderFlag($flag) {
+        return (strlen($flag) == 1 ? '-' : '--') . $flag;
+    }
+
+    /**
+     * Converts multiple new lines into a single new line.
+     * Just there to trap accidental duplicate new lines.
+     * @param string $text      Text to clean up.
+     * @returns string          Text with no blank lines.
+     */
+    private function noDuplicateNewLines($text) {
+        return preg_replace('/(\n+)/', "\n", $text);
+    }
+}
+?>
\ No newline at end of file
index 382f128..fcd4efd 100644 (file)
@@ -3,7 +3,7 @@
  *  Base include file for SimpleTest
  *  @package    SimpleTest
  *  @subpackage WebTester
- *  @version    $Id$
+ *  @version    $Id: authentication.php 2011 2011-04-29 08:22:48Z pp11 $
  */
 /**
  *  include http class
@@ -16,11 +16,11 @@ require_once(dirname(__FILE__) . '/http.php');
  *    @subpackage WebTester
  */
 class SimpleRealm {
-    var $_type;
-    var $_root;
-    var $_username;
-    var $_password;
-    
+    private $type;
+    private $root;
+    private $username;
+    private $password;
+
     /**
      *    Starts with the initial entry directory.
      *    @param string $type      Authentication type for this
@@ -30,21 +30,21 @@ class SimpleRealm {
      *    @access public
      */
     function SimpleRealm($type, $url) {
-        $this->_type = $type;
-        $this->_root = $url->getBasePath();
-        $this->_username = false;
-        $this->_password = false;
+        $this->type = $type;
+        $this->root = $url->getBasePath();
+        $this->username = false;
+        $this->password = false;
     }
-    
+
     /**
      *    Adds another location to the realm.
      *    @param SimpleUrl $url    Somewhere in realm.
      *    @access public
      */
     function stretch($url) {
-        $this->_root = $this->_getCommonPath($this->_root, $url->getPath());
+        $this->root = $this->getCommonPath($this->root, $url->getPath());
     }
-    
+
     /**
      *    Finds the common starting path.
      *    @param string $first        Path to compare.
@@ -52,7 +52,7 @@ class SimpleRealm {
      *    @return string              Common directories.
      *    @access private
      */
-    function _getCommonPath($first, $second) {
+    protected function getCommonPath($first, $second) {
         $first = explode('/', $first);
         $second = explode('/', $second);
         for ($i = 0; $i < min(count($first), count($second)); $i++) {
@@ -62,7 +62,7 @@ class SimpleRealm {
         }
         return implode('/', $first) . '/';
     }
-    
+
     /**
      *    Sets the identity to try within this realm.
      *    @param string $username    Username in authentication dialog.
@@ -70,28 +70,28 @@ class SimpleRealm {
      *    @access public
      */
     function setIdentity($username, $password) {
-        $this->_username = $username;
-        $this->_password = $password;
+        $this->username = $username;
+        $this->password = $password;
     }
-    
+
     /**
      *    Accessor for current identity.
      *    @return string        Last succesful username.
      *    @access public
      */
     function getUsername() {
-        return $this->_username;
+        return $this->username;
     }
-    
+
     /**
      *    Accessor for current identity.
      *    @return string        Last succesful password.
      *    @access public
      */
     function getPassword() {
-        return $this->_password;
+        return $this->password;
     }
-    
+
     /**
      *    Test to see if the URL is within the directory
      *    tree of the realm.
@@ -100,15 +100,15 @@ class SimpleRealm {
      *    @access public
      */
     function isWithin($url) {
-        if ($this->_isIn($this->_root, $url->getBasePath())) {
+        if ($this->isIn($this->root, $url->getBasePath())) {
             return true;
         }
-        if ($this->_isIn($this->_root, $url->getBasePath() . $url->getPage() . '/')) {
+        if ($this->isIn($this->root, $url->getBasePath() . $url->getPage() . '/')) {
             return true;
         }
         return false;
     }
-    
+
     /**
      *    Tests to see if one string is a substring of
      *    another.
@@ -118,7 +118,7 @@ class SimpleRealm {
      *                               in the big bit.
      *    @access private
      */
-    function _isIn($part, $whole) {
+    protected function isIn($part, $whole) {
         return strpos($whole, $part) === 0;
     }
 }
@@ -129,8 +129,8 @@ class SimpleRealm {
  *    @subpackage WebTester
  */
 class SimpleAuthenticator {
-    var $_realms;
-    
+    private $realms;
+
     /**
      *    Clears the realms.
      *    @access public
@@ -138,18 +138,18 @@ class SimpleAuthenticator {
     function SimpleAuthenticator() {
         $this->restartSession();
     }
-    
+
     /**
      *    Starts with no realms set up.
      *    @access public
      */
     function restartSession() {
-        $this->_realms = array();
+        $this->realms = array();
     }
-    
+
     /**
      *    Adds a new realm centered the current URL.
-     *    Browsers vary wildly on their behaviour in this
+     *    Browsers privatey wildly on their behaviour in this
      *    regard. Mozilla ignores the realm and presents
      *    only when challenged, wasting bandwidth. IE
      *    just carries on presenting until a new challenge
@@ -164,9 +164,9 @@ class SimpleAuthenticator {
      *    @access public
      */
     function addRealm($url, $type, $realm) {
-        $this->_realms[$url->getHost()][$realm] = new SimpleRealm($type, $url);
+        $this->realms[$url->getHost()][$realm] = new SimpleRealm($type, $url);
     }
-    
+
     /**
      *    Sets the current identity to be presented
      *    against that realm.
@@ -177,29 +177,29 @@ class SimpleAuthenticator {
      *    @access public
      */
     function setIdentityForRealm($host, $realm, $username, $password) {
-        if (isset($this->_realms[$host][$realm])) {
-            $this->_realms[$host][$realm]->setIdentity($username, $password);
+        if (isset($this->realms[$host][$realm])) {
+            $this->realms[$host][$realm]->setIdentity($username, $password);
         }
     }
-    
+
     /**
      *    Finds the name of the realm by comparing URLs.
      *    @param SimpleUrl $url        URL to test.
      *    @return SimpleRealm          Name of realm.
      *    @access private
      */
-    function _findRealmFromUrl($url) {
-        if (! isset($this->_realms[$url->getHost()])) {
+    protected function findRealmFromUrl($url) {
+        if (! isset($this->realms[$url->getHost()])) {
             return false;
         }
-        foreach ($this->_realms[$url->getHost()] as $name => $realm) {
+        foreach ($this->realms[$url->getHost()] as $name => $realm) {
             if ($realm->isWithin($url)) {
                 return $realm;
             }
         }
         return false;
     }
-    
+
     /**
      *    Presents the appropriate headers for this location.
      *    @param SimpleHttpRequest $request  Request to modify.
@@ -210,7 +210,7 @@ class SimpleAuthenticator {
         if ($url->getUsername() && $url->getPassword()) {
             $username = $url->getUsername();
             $password = $url->getPassword();
-        } elseif ($realm = $this->_findRealmFromUrl($url)) {
+        } elseif ($realm = $this->findRealmFromUrl($url)) {
             $username = $realm->getUsername();
             $password = $realm->getPassword();
         } else {
@@ -218,7 +218,7 @@ class SimpleAuthenticator {
         }
         $this->addBasicHeaders($request, $username, $password);
     }
-    
+
     /**
      *    Presents the appropriate headers for this
      *    location for basic authentication.
@@ -226,12 +226,11 @@ class SimpleAuthenticator {
      *    @param string $username            Username for realm.
      *    @param string $password            Password for realm.
      *    @access public
-     *    @static
      */
-    function addBasicHeaders(&$request, $username, $password) {
+    static function addBasicHeaders(&$request, $username, $password) {
         if ($username && $password) {
             $request->addHeaderLine(
-                    'Authorization: Basic ' . base64_encode("$username:$password"));
+                'Authorization: Basic ' . base64_encode("$username:$password"));
         }
     }
 }
index a57408d..bc906e7 100644 (file)
@@ -3,38 +3,63 @@
  *  Autorunner which runs all tests cases found in a file
  *  that includes this module.
  *  @package    SimpleTest
- *  @version    $Id$
+ *  @version    $Id: autorun.php 2037 2011-11-30 17:58:21Z pp11 $
+ */
+
+defined('MOODLE_INTERNAL') || die(); // moodle hack - allow execution ONLY via out UIs!!!
+
+/**#@+
+ * include simpletest files
  */
 require_once dirname(__FILE__) . '/unit_tester.php';
 require_once dirname(__FILE__) . '/mock_objects.php';
 require_once dirname(__FILE__) . '/collector.php';
 require_once dirname(__FILE__) . '/default_reporter.php';
+/**#@-*/
 
 $GLOBALS['SIMPLETEST_AUTORUNNER_INITIAL_CLASSES'] = get_declared_classes();
+$GLOBALS['SIMPLETEST_AUTORUNNER_INITIAL_PATH'] = getcwd();
 register_shutdown_function('simpletest_autorun');
 
 /**
- *    Exit handler to run all recent test cases if no test has
- *    so far been run. Uses the DefaultReporter which can have
- *    it's output controlled with SimpleTest::prefer().
+ *    Exit handler to run all recent test cases and exit system if in CLI
  */
 function simpletest_autorun() {
+       chdir($GLOBALS['SIMPLETEST_AUTORUNNER_INITIAL_PATH']);
     if (tests_have_run()) {
         return;
     }
-    $candidates = array_intersect(
-            capture_new_classes(),
-            classes_defined_in_initial_file());
-    $loader = new SimpleFileLoader();
-    $suite = $loader->createSuiteFromClasses(
-            basename(initial_file()),
-            $loader->selectRunnableTests($candidates));
-    $result = $suite->run(new DefaultReporter());
+    $result = run_local_tests();
     if (SimpleReporter::inCli()) {
         exit($result ? 0 : 1);
     }
 }
 
+/**
+ *    run all recent test cases if no test has
+ *    so far been run. Uses the DefaultReporter which can have
+ *    it's output controlled with SimpleTest::prefer().
+ *    @return boolean/null false if there were test failures, true if
+ *                         there were no failures, null if tests are
+ *                         already running
+ */
+function run_local_tests() {
+    try {
+        if (tests_have_run()) {
+            return;
+        }
+        $candidates = capture_new_classes();
+        $loader = new SimpleFileLoader();
+        $suite = $loader->createSuiteFromClasses(
+                basename(initial_file()),
+                $loader->selectRunnableTests($candidates));
+        return $suite->run(new DefaultReporter());
+    } catch (Exception $stack_frame_fix) {
+        print $stack_frame_fix->getMessage();
+        return false;
+    }
+}
+
 /**
  *    Checks the current test context to see if a test has
  *    ever been run.
@@ -54,28 +79,19 @@ function tests_have_run() {
 function initial_file() {
     static $file = false;
     if (! $file) {
-        $file = reset(get_included_files());
+        if (isset($_SERVER, $_SERVER['SCRIPT_FILENAME'])) {
+            $file = $_SERVER['SCRIPT_FILENAME'];
+        } else {
+            $included_files = get_included_files();
+            $file = reset($included_files);
+        }
     }
     return $file;
 }
 
-/**
- *    Just the classes from the first autorun script. May
- *    get a few false positives, as it just does a regex based
- *    on following the word "class".
- *    @return array        List of all possible classes in first
- *                         autorun script.
- */
-function classes_defined_in_initial_file() {
-    if (preg_match_all('/\bclass\s+(\w+)/i', file_get_contents(initial_file()), $matches)) {
-        return array_map('strtolower', $matches[1]);
-    }
-    return array();
-}
-
 /**
  *    Every class since the first autorun include. This
- *    is safe enough if require_once() is alwyas used.
+ *    is safe enough if require_once() is always used.
  *    @return array        Class names.
  */
 function capture_new_classes() {
index 423e8fb..615e738 100644 (file)
@@ -3,7 +3,7 @@
  *  Base include file for SimpleTest
  *  @package    SimpleTest
  *  @subpackage WebTester
- *  @version    $Id$
+ *  @version    $Id: browser.php 2013 2011-04-29 09:29:45Z pp11 $
  */
 
 /**#@+
@@ -13,12 +13,18 @@ require_once(dirname(__FILE__) . '/simpletest.php');
 require_once(dirname(__FILE__) . '/http.php');
 require_once(dirname(__FILE__) . '/encoding.php');
 require_once(dirname(__FILE__) . '/page.php');
+require_once(dirname(__FILE__) . '/php_parser.php');
+require_once(dirname(__FILE__) . '/tidy_parser.php');
 require_once(dirname(__FILE__) . '/selector.php');
 require_once(dirname(__FILE__) . '/frames.php');
 require_once(dirname(__FILE__) . '/user_agent.php');
+if (! SimpleTest::getParsers()) {
+    SimpleTest::setParsers(array(new SimpleTidyPageBuilder(), new SimplePHPPageBuilder()));
+    //SimpleTest::setParsers(array(new SimplePHPPageBuilder()));
+}
 /**#@-*/
 
-if (!defined('DEFAULT_MAX_NESTED_FRAMES')) {
+if (! defined('DEFAULT_MAX_NESTED_FRAMES')) {
     define('DEFAULT_MAX_NESTED_FRAMES', 3);
 }
 
@@ -28,25 +34,16 @@ if (!defined('DEFAULT_MAX_NESTED_FRAMES')) {
  *    @subpackage WebTester
  */
 class SimpleBrowserHistory {
-    var $_sequence;
-    var $_position;
-
-    /**
-     *    Starts empty.
-     *    @access public
-     */
-    function SimpleBrowserHistory() {
-        $this->_sequence = array();
-        $this->_position = -1;
-    }
+    private $sequence = array();
+    private $position = -1;
 
     /**
      *    Test for no entries yet.
      *    @return boolean        True if empty.
      *    @access private
      */
-    function _isEmpty() {
-        return ($this->_position == -1);
+    protected function isEmpty() {
+        return ($this->position == -1);
     }
 
     /**
@@ -54,8 +51,8 @@ class SimpleBrowserHistory {
      *    @return boolean        True if first.
      *    @access private
      */
-    function _atBeginning() {
-        return ($this->_position == 0) && ! $this->_isEmpty();
+    protected function atBeginning() {
+        return ($this->position == 0) && ! $this->isEmpty();
     }
 
     /**
@@ -63,8 +60,8 @@ class SimpleBrowserHistory {
      *    @return boolean        True if last.
      *    @access private
      */
-    function _atEnd() {
-        return ($this->_position + 1 >= count($this->_sequence)) && ! $this->_isEmpty();
+    protected function atEnd() {
+        return ($this->position + 1 >= count($this->sequence)) && ! $this->isEmpty();
     }
 
     /**
@@ -74,11 +71,11 @@ class SimpleBrowserHistory {
      *    @access public
      */
     function recordEntry($url, $parameters) {
-        $this->_dropFuture();
+        $this->dropFuture();
         array_push(
-                $this->_sequence,
+                $this->sequence,
                 array('url' => $url, 'parameters' => $parameters));
-        $this->_position++;
+        $this->position++;
     }
 
     /**
@@ -88,10 +85,10 @@ class SimpleBrowserHistory {
      *    @access public
      */
     function getUrl() {
-        if ($this->_isEmpty()) {
+        if ($this->isEmpty()) {
             return false;
         }
-        return $this->_sequence[$this->_position]['url'];
+        return $this->sequence[$this->position]['url'];
     }
 
     /**
@@ -101,10 +98,10 @@ class SimpleBrowserHistory {
      *    @access public
      */
     function getParameters() {
-        if ($this->_isEmpty()) {
+        if ($this->isEmpty()) {
             return false;
         }
-        return $this->_sequence[$this->_position]['parameters'];
+        return $this->sequence[$this->position]['parameters'];
     }
 
     /**
@@ -114,10 +111,10 @@ class SimpleBrowserHistory {
      *    @access public
      */
     function back() {
-        if ($this->_isEmpty() || $this->_atBeginning()) {
+        if ($this->isEmpty() || $this->atBeginning()) {
             return false;
         }
-        $this->_position--;
+        $this->position--;
         return true;
     }
 
@@ -128,10 +125,10 @@ class SimpleBrowserHistory {
      *    @access public
      */
     function forward() {
-        if ($this->_isEmpty() || $this->_atEnd()) {
+        if ($this->isEmpty() || $this->atEnd()) {
             return false;
         }
-        $this->_position++;
+        $this->position++;
         return true;
     }
 
@@ -140,12 +137,12 @@ class SimpleBrowserHistory {
      *    point.
      *    @access private
      */
-    function _dropFuture() {
-        if ($this->_isEmpty()) {
+    protected function dropFuture() {
+        if ($this->isEmpty()) {
             return;
         }
-        while (! $this->_atEnd()) {
-            array_pop($this->_sequence);
+        while (! $this->atEnd()) {
+            array_pop($this->sequence);
         }
     }
 }
@@ -158,11 +155,12 @@ class SimpleBrowserHistory {
  *    @subpackage WebTester
  */
 class SimpleBrowser {
-    var $_user_agent;
-    var $_page;
-    var $_history;
-    var $_ignore_frames;
-    var $_maximum_nested_frames;
+    private $user_agent;
+    private $page;
+    private $history;
+    private $ignore_frames;
+    private $maximum_nested_frames;
+    private $parser;
 
     /**
      *    Starts with a fresh browser with no
@@ -171,16 +169,16 @@ class SimpleBrowser {
      *    set up if specified in the options.
      *    @access public
      */
-    function SimpleBrowser() {
-        $this->_user_agent = &$this->_createUserAgent();
-        $this->_user_agent->useProxy(
+    function __construct() {
+        $this->user_agent = $this->createUserAgent();
+        $this->user_agent->useProxy(
                 SimpleTest::getDefaultProxy(),
                 SimpleTest::getDefaultProxyUsername(),
                 SimpleTest::getDefaultProxyPassword());
-        $this->_page = new SimplePage();
-        $this->_history = &$this->_createHistory();
-        $this->_ignore_frames = false;
-        $this->_maximum_nested_frames = DEFAULT_MAX_NESTED_FRAMES;
+        $this->page = new SimplePage();
+        $this->history = $this->createHistory();
+        $this->ignore_frames = false;
+        $this->maximum_nested_frames = DEFAULT_MAX_NESTED_FRAMES;
     }
 
     /**
@@ -188,9 +186,8 @@ class SimpleBrowser {
      *    @return SimpleFetcher    Content fetcher.
      *    @access protected
      */
-    function &_createUserAgent() {
-        $user_agent = new SimpleUserAgent();
-        return $user_agent;
+    protected function createUserAgent() {
+        return new SimpleUserAgent();
     }
 
     /**
@@ -198,9 +195,33 @@ class SimpleBrowser {
      *    @return SimpleBrowserHistory    New list.
      *    @access protected
      */
-    function &_createHistory() {
-        $history = new SimpleBrowserHistory();
-        return $history;
+    protected function createHistory() {
+        return new SimpleBrowserHistory();
+    }
+
+    /**
+     *    Get the HTML parser to use. Can be overridden by
+     *    setParser. Otherwise scans through the available parsers and
+     *    uses the first one which is available.
+     *    @return object SimplePHPPageBuilder or SimpleTidyPageBuilder
+     */
+    protected function getParser() {
+        if ($this->parser) {
+            return $this->parser;
+        }
+        foreach (SimpleTest::getParsers() as $parser) {
+            if ($parser->can()) {
+                return $parser;
+            }
+        }
+    }
+
+    /**
+     *    Override the default HTML parser, allowing parsers to be plugged in.
+     *    @param object           A parser object instance.
+     */
+    public function setParser($parser) {
+        $this->parser = $parser;
     }
 
     /**
@@ -209,7 +230,7 @@ class SimpleBrowser {
      *    @access public
      */
     function ignoreFrames() {
-        $this->_ignore_frames = true;
+        $this->ignore_frames = true;
     }
 
     /**
@@ -218,23 +239,23 @@ class SimpleBrowser {
      *    @access public
      */
     function useFrames() {
-        $this->_ignore_frames = false;
+        $this->ignore_frames = false;
     }
-    
+
     /**
      *    Switches off cookie sending and recieving.
      *    @access public
      */
     function ignoreCookies() {
-        $this->_user_agent->ignoreCookies();
+        $this->user_agent->ignoreCookies();
     }
-    
+
     /**
      *    Switches back on the cookie sending and recieving.
      *    @access public
      */
     function useCookies() {
-        $this->_user_agent->useCookies();
+        $this->user_agent->useCookies();
     }
 
     /**
@@ -245,37 +266,32 @@ class SimpleBrowser {
      *    @return SimplePage                     Parsed HTML.
      *    @access private
      */
-    function &_parse($response, $depth = 0) {
-        $page = &$this->_buildPage($response);
-        if ($this->_ignore_frames || ! $page->hasFrames() || ($depth > $this->_maximum_nested_frames)) {
+    protected function parse($response, $depth = 0) {
+        $page = $this->buildPage($response);
+        if ($this->ignore_frames || ! $page->hasFrames() || ($depth > $this->maximum_nested_frames)) {
             return $page;
         }
         $frameset = new SimpleFrameset($page);
         foreach ($page->getFrameset() as $key => $url) {
-            $frame = &$this->_fetch($url, new SimpleGetEncoding(), $depth + 1);
+            $frame = $this->fetch($url, new SimpleGetEncoding(), $depth + 1);
             $frameset->addFrame($frame, $key);
         }
         return $frameset;
     }
-    
+
     /**
      *    Assembles the parsing machinery and actually parses
      *    a single page. Frees all of the builder memory and so
      *    unjams the PHP memory management.
      *    @param SimpleHttpResponse $response    Response from fetch.
      *    @return SimplePage                     Parsed top level page.
-     *    @access protected
      */
-    function &_buildPage($response) {
-        $builder = new SimplePageBuilder();
-        $page = &$builder->parse($response);
-        $builder->free();
-        unset($builder);
-        return $page;
+    protected function buildPage($response) {
+        return $this->getParser()->parse($response);
     }
 
     /**
-     *    Fetches a page. Jointly recursive with the _parse()
+     *    Fetches a page. Jointly recursive with the parse()
      *    method as it descends a frameset.
      *    @param string/SimpleUrl $url          Target to fetch.
      *    @param SimpleEncoding $encoding       GET/POST parameters.
@@ -283,14 +299,12 @@ class SimpleBrowser {
      *    @return SimplePage                    Parsed page.
      *    @access private
      */
-    function &_fetch($url, $encoding, $depth = 0) {
-        $response = &$this->_user_agent->fetchResponse($url, $encoding);
+    protected function fetch($url, $encoding, $depth = 0) {
+        $response = $this->user_agent->fetchResponse($url, $encoding);
         if ($response->isError()) {
-            $page = new SimplePage($response);
-        } else {
-            $page = &$this->_parse($response, $depth);
+            return new SimplePage($response);
         }
-        return $page;
+        return $this->parse($response, $depth);
     }
 
     /**
@@ -301,12 +315,12 @@ class SimpleBrowser {
      *    @return string                          Raw content of page.
      *    @access private
      */
-    function _load($url, $parameters) {
+    protected function load($url, $parameters) {
         $frame = $url->getTarget();
-        if (! $frame || ! $this->_page->hasFrames() || (strtolower($frame) == '_top')) {
-            return $this->_loadPage($url, $parameters);
+        if (! $frame || ! $this->page->hasFrames() || (strtolower($frame) == '_top')) {
+            return $this->loadPage($url, $parameters);
         }
-        return $this->_loadFrame(array($frame), $url, $parameters);
+        return $this->loadFrame(array($frame), $url, $parameters);
     }
 
     /**
@@ -316,12 +330,12 @@ class SimpleBrowser {
      *    @return string                          Raw content of page.
      *    @access private
      */
-    function _loadPage($url, $parameters) {
-        $this->_page = &$this->_fetch($url, $parameters);
-        $this->_history->recordEntry(
-                $this->_page->getUrl(),
-                $this->_page->getRequestData());
-        return $this->_page->getRaw();
+    protected function loadPage($url, $parameters) {
+        $this->page = $this->fetch($url, $parameters);
+        $this->history->recordEntry(
+                $this->page->getUrl(),
+                $this->page->getRequestData());
+        return $this->page->getRaw();
     }
 
     /**
@@ -333,9 +347,9 @@ class SimpleBrowser {
      *    @return string                          Raw content of page.
      *    @access private
      */
-    function _loadFrame($frames, $url, $parameters) {
-        $page = &$this->_fetch($url, $parameters);
-        $this->_page->setFrame($frames, $page);
+    protected function loadFrame($frames, $url, $parameters) {
+        $page = $this->fetch($url, $parameters);
+        $this->page->setFrame($frames, $page);
         return $page->getRaw();
     }
 
@@ -348,7 +362,7 @@ class SimpleBrowser {
      *    @access public
      */
     function restart($date = false) {
-        $this->_user_agent->restart($date);
+        $this->user_agent->restart($date);
     }
 
     /**
@@ -358,7 +372,7 @@ class SimpleBrowser {
      *    @access public
      */
     function addHeader($header) {
-        $this->_user_agent->addHeader($header);
+        $this->user_agent->addHeader($header);
     }
 
     /**
@@ -367,7 +381,7 @@ class SimpleBrowser {
      *    @access public
      */
     function ageCookies($interval) {
-        $this->_user_agent->ageCookies($interval);
+        $this->user_agent->ageCookies($interval);
     }
 
     /**
@@ -381,7 +395,7 @@ class SimpleBrowser {
      *    @access public
      */
     function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
-        $this->_user_agent->setCookie($name, $value, $host, $path, $expiry);
+        $this->user_agent->setCookie($name, $value, $host, $path, $expiry);
     }
 
     /**
@@ -395,7 +409,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getCookieValue($host, $path, $name) {
-        return $this->_user_agent->getCookieValue($host, $path, $name);
+        return $this->user_agent->getCookieValue($host, $path, $name);
     }
 
     /**
@@ -406,7 +420,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getCurrentCookieValue($name) {
-        return $this->_user_agent->getBaseCookieValue($name, $this->_page->getUrl());
+        return $this->user_agent->getBaseCookieValue($name, $this->page->getUrl());
     }
 
     /**
@@ -416,7 +430,7 @@ class SimpleBrowser {
      *    @access public
      */
     function setMaximumRedirects($max) {
-        $this->_user_agent->setMaximumRedirects($max);
+        $this->user_agent->setMaximumRedirects($max);
     }
 
     /**
@@ -426,7 +440,7 @@ class SimpleBrowser {
      *    @access public
      */
     function setMaximumNestedFrames($max) {
-        $this->_maximum_nested_frames = $max;
+        $this->maximum_nested_frames = $max;
     }
 
     /**
@@ -435,7 +449,7 @@ class SimpleBrowser {
      *    @access public
      */
     function setConnectionTimeout($timeout) {
-        $this->_user_agent->setConnectionTimeout($timeout);
+        $this->user_agent->setConnectionTimeout($timeout);
     }
 
     /**
@@ -448,7 +462,7 @@ class SimpleBrowser {
      *    @access public
      */
     function useProxy($proxy, $username = false, $password = false) {
-        $this->_user_agent->useProxy($proxy, $username, $password);
+        $this->user_agent->useProxy($proxy, $username, $password);
     }
 
     /**
@@ -467,7 +481,8 @@ class SimpleBrowser {
         if ($this->getUrl()) {
             $url = $url->makeAbsolute($this->getUrl());
         }
-        $response = &$this->_user_agent->fetchResponse($url, new SimpleHeadEncoding($parameters));
+        $response = $this->user_agent->fetchResponse($url, new SimpleHeadEncoding($parameters));
+        $this->page = new SimplePage($response);
         return ! $response->isError();
     }
 
@@ -486,24 +501,55 @@ class SimpleBrowser {
         if ($this->getUrl()) {
             $url = $url->makeAbsolute($this->getUrl());
         }
-        return $this->_load($url, new SimpleGetEncoding($parameters));
+        return $this->load($url, new SimpleGetEncoding($parameters));
     }
 
     /**
      *    Fetches the page content with a POST request.
      *    @param string/SimpleUrl $url                Target to fetch as string.
-     *    @param hash/SimpleFormEncoding $parameters  POST parameters.
+     *    @param hash/SimpleFormEncoding $parameters  POST parameters or request body.
+     *    @param string $content_type                 MIME Content-Type of the request body
      *    @return string                              Content of page.
      *    @access public
      */
-    function post($url, $parameters = false) {
+    function post($url, $parameters = false, $content_type = false) {
         if (! is_object($url)) {
             $url = new SimpleUrl($url);
         }
         if ($this->getUrl()) {
             $url = $url->makeAbsolute($this->getUrl());
         }
-        return $this->_load($url, new SimplePostEncoding($parameters));
+        return $this->load($url, new SimplePostEncoding($parameters, $content_type));
+    }
+
+    /**
+     *    Fetches the page content with a PUT request.
+     *    @param string/SimpleUrl $url                Target to fetch as string.
+     *    @param hash/SimpleFormEncoding $parameters  PUT request body.
+     *    @param string $content_type                 MIME Content-Type of the request body
+     *    @return string                              Content of page.
+     *    @access public
+     */
+    function put($url, $parameters = false, $content_type = false) {
+        if (! is_object($url)) {
+            $url = new SimpleUrl($url);
+        }
+        return $this->load($url, new SimplePutEncoding($parameters, $content_type));
+    }
+
+    /**
+     *    Sends a DELETE request and fetches the response.
+     *    @param string/SimpleUrl $url                Target to fetch.
+     *    @param hash/SimpleFormEncoding $parameters  Additional parameters for
+     *                                                DELETE request.
+     *    @return string                              Content of page or false.
+     *    @access public
+     */
+    function delete($url, $parameters = false) {
+        if (! is_object($url)) {
+            $url = new SimpleUrl($url);
+        }
+        return $this->load($url, new SimpleDeleteEncoding($parameters));
     }
 
     /**
@@ -515,17 +561,17 @@ class SimpleBrowser {
      *    @access public
      */
     function retry() {
-        $frames = $this->_page->getFrameFocus();
+        $frames = $this->page->getFrameFocus();
         if (count($frames) > 0) {
-            $this->_loadFrame(
+            $this->loadFrame(
                     $frames,
-                    $this->_page->getUrl(),
-                    $this->_page->getRequestData());
-            return $this->_page->getRaw();
+                    $this->page->getUrl(),
+                    $this->page->getRequestData());
+            return $this->page->getRaw();
         }
-        if ($url = $this->_history->getUrl()) {
-            $this->_page = &$this->_fetch($url, $this->_history->getParameters());
-            return $this->_page->getRaw();
+        if ($url = $this->history->getUrl()) {
+            $this->page = $this->fetch($url, $this->history->getParameters());
+            return $this->page->getRaw();
         }
         return false;
     }
@@ -540,12 +586,12 @@ class SimpleBrowser {
      *    @access public
      */
     function back() {
-        if (! $this->_history->back()) {
+        if (! $this->history->back()) {
             return false;
         }
         $content = $this->retry();
         if (! $content) {
-            $this->_history->forward();
+            $this->history->forward();
         }
         return $content;
     }
@@ -560,12 +606,12 @@ class SimpleBrowser {
      *    @access public
      */
     function forward() {
-        if (! $this->_history->forward()) {
+        if (! $this->history->forward()) {
             return false;
         }
         $content = $this->retry();
         if (! $content) {
-            $this->_history->back();
+            $this->history->back();
         }
         return $content;
     }
@@ -581,16 +627,16 @@ class SimpleBrowser {
      *    @access public
      */
     function authenticate($username, $password) {
-        if (! $this->_page->getRealm()) {
+        if (! $this->page->getRealm()) {
             return false;
         }
-        $url = $this->_page->getUrl();
+        $url = $this->page->getUrl();
         if (! $url) {
             return false;
         }
-        $this->_user_agent->setIdentity(
+        $this->user_agent->setIdentity(
                 $url->getHost(),
-                $this->_page->getRealm(),
+                $this->page->getRealm(),
                 $username,
                 $password);
         return $this->retry();
@@ -603,7 +649,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getFrames() {
-        return $this->_page->getFrames();
+        return $this->page->getFrames();
     }
 
     /**
@@ -615,7 +661,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getFrameFocus() {
-        return $this->_page->getFrameFocus();
+        return $this->page->getFrameFocus();
     }
 
     /**
@@ -625,7 +671,7 @@ class SimpleBrowser {
      *    @access public
      */
     function setFrameFocusByIndex($choice) {
-        return $this->_page->setFrameFocusByIndex($choice);
+        return $this->page->setFrameFocusByIndex($choice);
     }
 
     /**
@@ -635,7 +681,7 @@ class SimpleBrowser {
      *    @access public
      */
     function setFrameFocus($name) {
-        return $this->_page->setFrameFocus($name);
+        return $this->page->setFrameFocus($name);
     }
 
     /**
@@ -644,7 +690,7 @@ class SimpleBrowser {
      *    @access public
      */
     function clearFrameFocus() {
-        return $this->_page->clearFrameFocus();
+        return $this->page->clearFrameFocus();
     }
 
     /**
@@ -653,7 +699,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getTransportError() {
-        return $this->_page->getTransportError();
+        return $this->page->getTransportError();
     }
 
     /**
@@ -662,7 +708,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getMimeType() {
-        return $this->_page->getMimeType();
+        return $this->page->getMimeType();
     }
 
     /**
@@ -671,7 +717,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getResponseCode() {
-        return $this->_page->getResponseCode();
+        return $this->page->getResponseCode();
     }
 
     /**
@@ -681,7 +727,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getAuthentication() {
-        return $this->_page->getAuthentication();
+        return $this->page->getAuthentication();
     }
 
     /**
@@ -691,7 +737,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getRealm() {
-        return $this->_page->getRealm();
+        return $this->page->getRealm();
     }
 
     /**
@@ -701,7 +747,7 @@ class SimpleBrowser {
      *                      a string.
      */
     function getUrl() {
-        $url = $this->_page->getUrl();
+        $url = $this->page->getUrl();
         return $url ? $url->asString() : false;
     }
 
@@ -710,7 +756,7 @@ class SimpleBrowser {
      *    @return string    base URL
      */
     function getBaseUrl() {
-        $url = $this->_page->getBaseUrl();
+        $url = $this->page->getBaseUrl();
         return $url ? $url->asString() : false;
     }
 
@@ -720,7 +766,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getRequest() {
-        return $this->_page->getRequest();
+        return $this->page->getRequest();
     }
 
     /**
@@ -729,7 +775,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getHeaders() {
-        return $this->_page->getHeaders();
+        return $this->page->getHeaders();
     }
 
     /**
@@ -738,7 +784,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getContent() {
-        return $this->_page->getRaw();
+        return $this->page->getRaw();
     }
 
     /**
@@ -747,7 +793,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getContentAsText() {
-        return $this->_page->getText();
+        return $this->page->getText();
     }
 
     /**
@@ -756,7 +802,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getTitle() {
-        return $this->_page->getTitle();
+        return $this->page->getTitle();
     }
 
     /**
@@ -766,7 +812,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getUrls() {
-        return $this->_page->getUrls();
+        return $this->page->getUrls();
     }
 
     /**
@@ -777,7 +823,7 @@ class SimpleBrowser {
      *    @access public
      */
     function setField($label, $value, $position=false) {
-        return $this->_page->setField(new SimpleByLabelOrName($label), $value, $position);
+        return $this->page->setField(new SimpleByLabelOrName($label), $value, $position);
     }
 
     /**
@@ -789,7 +835,7 @@ class SimpleBrowser {
      *    @access public
      */
     function setFieldByName($name, $value, $position=false) {
-        return $this->_page->setField(new SimpleByName($name), $value, $position);
+        return $this->page->setField(new SimpleByName($name), $value, $position);
     }
 
     /**
@@ -800,7 +846,7 @@ class SimpleBrowser {
      *    @access public
      */
     function setFieldById($id, $value) {
-        return $this->_page->setField(new SimpleById($id), $value);
+        return $this->page->setField(new SimpleById($id), $value);
     }
 
     /**
@@ -813,7 +859,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getField($label) {
-        return $this->_page->getField(new SimpleByLabelOrName($label));
+        return $this->page->getField(new SimpleByLabelOrName($label));
     }
 
     /**
@@ -826,7 +872,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getFieldByName($name) {
-        return $this->_page->getField(new SimpleByName($name));
+        return $this->page->getField(new SimpleByName($name));
     }
 
     /**
@@ -838,7 +884,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getFieldById($id) {
-        return $this->_page->getField(new SimpleById($id));
+        return $this->page->getField(new SimpleById($id));
     }
 
     /**
@@ -851,10 +897,10 @@ class SimpleBrowser {
      *    @access public
      */
     function clickSubmit($label = 'Submit', $additional = false) {
-        if (! ($form = &$this->_page->getFormBySubmit(new SimpleByLabel($label)))) {
+        if (! ($form = $this->page->getFormBySubmit(new SimpleByLabel($label)))) {
             return false;
         }
-        $success = $this->_load(
+        $success = $this->load(
                 $form->getAction(),
                 $form->submitButton(new SimpleByLabel($label), $additional));
         return ($success ? $this->getContent() : $success);
@@ -869,10 +915,10 @@ class SimpleBrowser {
      *    @access public
      */
     function clickSubmitByName($name, $additional = false) {
-        if (! ($form = &$this->_page->getFormBySubmit(new SimpleByName($name)))) {
+        if (! ($form = $this->page->getFormBySubmit(new SimpleByName($name)))) {
             return false;
         }
-        $success = $this->_load(
+        $success = $this->load(
                 $form->getAction(),
                 $form->submitButton(new SimpleByName($name), $additional));
         return ($success ? $this->getContent() : $success);
@@ -887,15 +933,15 @@ class SimpleBrowser {
      *    @access public
      */
     function clickSubmitById($id, $additional = false) {
-        if (! ($form = &$this->_page->getFormBySubmit(new SimpleById($id)))) {
+        if (! ($form = $this->page->getFormBySubmit(new SimpleById($id)))) {
             return false;
         }
-        $success = $this->_load(
+        $success = $this->load(
                 $form->getAction(),
                 $form->submitButton(new SimpleById($id), $additional));
         return ($success ? $this->getContent() : $success);
     }
-    
+
     /**
      *    Tests to see if a submit button exists with this
      *    label.
@@ -904,7 +950,7 @@ class SimpleBrowser {
      *    @access public
      */
     function isSubmit($label) {
-        return (boolean)$this->_page->getFormBySubmit(new SimpleByLabel($label));
+        return (boolean)$this->page->getFormBySubmit(new SimpleByLabel($label));
     }
 
     /**
@@ -921,10 +967,10 @@ class SimpleBrowser {
      *    @access public
      */
     function clickImage($label, $x = 1, $y = 1, $additional = false) {
-        if (! ($form = &$this->_page->getFormByImage(new SimpleByLabel($label)))) {
+        if (! ($form = $this->page->getFormByImage(new SimpleByLabel($label)))) {
             return false;
         }
-        $success = $this->_load(
+        $success = $this->load(
                 $form->getAction(),
                 $form->submitImage(new SimpleByLabel($label), $x, $y, $additional));
         return ($success ? $this->getContent() : $success);
@@ -944,10 +990,10 @@ class SimpleBrowser {
      *    @access public
      */
     function clickImageByName($name, $x = 1, $y = 1, $additional = false) {
-        if (! ($form = &$this->_page->getFormByImage(new SimpleByName($name)))) {
+        if (! ($form = $this->page->getFormByImage(new SimpleByName($name)))) {
             return false;
         }
-        $success = $this->_load(
+        $success = $this->load(
                 $form->getAction(),
                 $form->submitImage(new SimpleByName($name), $x, $y, $additional));
         return ($success ? $this->getContent() : $success);
@@ -966,15 +1012,15 @@ class SimpleBrowser {
      *    @access public
      */
     function clickImageById($id, $x = 1, $y = 1, $additional = false) {
-        if (! ($form = &$this->_page->getFormByImage(new SimpleById($id)))) {
+        if (! ($form = $this->page->getFormByImage(new SimpleById($id)))) {
             return false;
         }
-        $success = $this->_load(
+        $success = $this->load(
                 $form->getAction(),
                 $form->submitImage(new SimpleById($id), $x, $y, $additional));
         return ($success ? $this->getContent() : $success);
     }
-    
+
     /**
      *    Tests to see if an image exists with this
      *    title or alt text.
@@ -983,7 +1029,7 @@ class SimpleBrowser {
      *    @access public
      */
     function isImage($label) {
-        return (boolean)$this->_page->getFormByImage(new SimpleByLabel($label));
+        return (boolean)$this->page->getFormByImage(new SimpleByLabel($label));
     }
 
     /**
@@ -993,13 +1039,13 @@ class SimpleBrowser {
      *    @return string/boolean  Page on success.
      *    @access public
      */
-    function submitFormById($id) {
-        if (! ($form = &$this->_page->getFormById($id))) {
+    function submitFormById($id, $additional = false) {
+        if (! ($form = $this->page->getFormById($id))) {
             return false;
         }
-        $success = $this->_load(
+        $success = $this->load(
                 $form->getAction(),
-                $form->submit());
+                $form->submit($additional));
         return ($success ? $this->getContent() : $success);
     }
 
@@ -1014,7 +1060,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getLink($label, $index = 0) {
-        $urls = $this->_page->getUrlsByLabel($label);
+        $urls = $this->page->getUrlsByLabel($label);
         if (count($urls) == 0) {
             return false;
         }
@@ -1039,10 +1085,10 @@ class SimpleBrowser {
         if ($url === false) {
             return false;
         }
-        $this->_load($url, new SimpleGetEncoding());
+        $this->load($url, new SimpleGetEncoding());
         return $this->getContent();
     }
-    
+
     /**
      *    Finds a link by id attribute.
      *    @param string $id        ID attribute value.
@@ -1050,7 +1096,7 @@ class SimpleBrowser {
      *    @access public
      */
     function getLinkById($id) {
-        return $this->_page->getUrlById($id);
+        return $this->page->getUrlById($id);
     }
 
     /**
@@ -1063,7 +1109,7 @@ class SimpleBrowser {
         if (! ($url = $this->getLinkById($id))) {
             return false;
         }
-        $this->_load($url, new SimpleGetEncoding());
+        $this->load($url, new SimpleGetEncoding());
         return $this->getContent();
     }
 
index 3e91154..ac49937 100644 (file)
@@ -6,7 +6,7 @@
  * @author Travis Swicegood <development@domain51.com>
  * @package SimpleTest
  * @subpackage UnitTester
- * @version $Id$
+ * @version $Id: collector.php 2011 2011-04-29 08:22:48Z pp11 $
  */
 
 /**
@@ -23,7 +23,7 @@ class SimpleCollector {
      * @param string $path    Path to normalise.
      * @return string         Path without trailing slash.
      */
-    function _removeTrailingSlash($path) {
+    protected function removeTrailingSlash($path) {
         if (substr($path, -1) == DIRECTORY_SEPARATOR) {
             return substr($path, 0, -1);
         } elseif (substr($path, -1) == '/') {
@@ -40,13 +40,13 @@ class SimpleCollector {
      * @see _attemptToAdd()
      */
     function collect(&$test, $path) {
-        $path = $this->_removeTrailingSlash($path);
+        $path = $this->removeTrailingSlash($path);
         if ($handle = opendir($path)) {
             while (($entry = readdir($handle)) !== false) {
-                if ($this->_isHidden($entry)) {
+                if ($this->isHidden($entry)) {
                     continue;
                 }
-                $this->_handle($test, $path . DIRECTORY_SEPARATOR . $entry);
+                $this->handle($test, $path . DIRECTORY_SEPARATOR . $entry);
             }
             closedir($handle);
         }
@@ -65,13 +65,13 @@ class SimpleCollector {
      * @see collect()
      * @access protected
      */
-    function _handle(&$test, $file) {
+    protected function handle(&$test, $file) {
         if (is_dir($file)) {
             return;
         }
-        $test->addTestFile($file);
+        $test->addFile($file);
     }
-    
+
     /**
      *  Tests for hidden files so as to skip them. Currently
      *  only tests for Unix hidden files.
@@ -79,7 +79,7 @@ class SimpleCollector {
      *  @return boolean                True if hidden file.
      *  @access private
      */
-    function _isHidden($filename) {
+    protected function isHidden($filename) {
         return strncmp($filename, '.', 1) == 0;
     }
 }
@@ -93,7 +93,7 @@ class SimpleCollector {
  * @see SimpleCollector
  */
 class SimplePatternCollector extends SimpleCollector {
-    var $_pattern;
+    private $pattern;
 
     /**
      *
@@ -101,8 +101,8 @@ class SimplePatternCollector extends SimpleCollector {
      *  See {@link http://us4.php.net/manual/en/reference.pcre.pattern.syntax.php PHP's PCRE}
      *  for full documentation of valid pattern.s
      */
-    function SimplePatternCollector($pattern = '/php$/i') {
-        $this->_pattern = $pattern;
+    function __construct($pattern = '/php$/i') {
+        $this->pattern = $pattern;
     }
 
     /**
@@ -113,9 +113,9 @@ class SimplePatternCollector extends SimpleCollector {
      * @param string $path    Directory to scan.
      * @access protected
      */
-    function _handle(&$test, $filename) {
-        if (preg_match($this->_pattern, $filename)) {
-            parent::_handle($test, $filename);
+    protected function handle(&$test, $filename) {
+        if (preg_match($this->pattern, $filename)) {
+            parent::handle($test, $filename);
         }
     }
 }
old mode 100644 (file)
new mode 100755 (executable)
index bd65a96..e313db0
@@ -2,7 +2,7 @@
 /**
  *  base include file for SimpleTest
  *  @package    SimpleTest
- *  @version    $Id$
+ *  @version    $Id: compatibility.php 1900 2009-07-29 11:44:37Z lastcraft $
  */
 
 /**
  *  @package    SimpleTest
  */
 class SimpleTestCompatibility {
-    
+
     /**
      *    Creates a copy whether in PHP5 or PHP4.
      *    @param object $object     Thing to copy.
      *    @return object            A copy.
      *    @access public
-     *    @static
      */
-    function copy($object) {
+    static function copy($object) {
         if (version_compare(phpversion(), '5') >= 0) {
             eval('$copy = clone $object;');
             return $copy;
         }
         return $object;
     }
-    
+
     /**
      *    Identity test. Drops back to equality + types for PHP5
      *    objects as the === operator counts as the
@@ -35,27 +34,25 @@ class SimpleTestCompatibility {
      *    @param mixed $second   Comparison object.
      *    @return boolean        True if identical.
      *    @access public
-     *    @static
      */
-    function isIdentical($first, $second) {
+    static function isIdentical($first, $second) {
         if (version_compare(phpversion(), '5') >= 0) {
-            return SimpleTestCompatibility::_isIdenticalType($first, $second);
+            return SimpleTestCompatibility::isIdenticalType($first, $second);
         }
         if ($first != $second) {
             return false;
         }
         return ($first === $second);
     }
-    
+
     /**
      *    Recursive type test.
      *    @param mixed $first    Test subject.
      *    @param mixed $second   Comparison object.
      *    @return boolean        True if same type.
      *    @access private
-     *    @static
      */
-    function _isIdenticalType($first, $second) {
+    protected static function isIdenticalType($first, $second) {
         if (gettype($first) != gettype($second)) {
             return false;
         }
@@ -63,33 +60,32 @@ class SimpleTestCompatibility {
             if (get_class($first) != get_class($second)) {
                 return false;
             }
-            return SimpleTestCompatibility::_isArrayOfIdenticalTypes(
-                    get_object_vars($first),
-                    get_object_vars($second));
+            return SimpleTestCompatibility::isArrayOfIdenticalTypes(
+                    (array) $first,
+                    (array) $second);
         }
         if (is_array($first) && is_array($second)) {
-            return SimpleTestCompatibility::_isArrayOfIdenticalTypes($first, $second);
+            return SimpleTestCompatibility::isArrayOfIdenticalTypes($first, $second);
         }
         if ($first !== $second) {
             return false;
         }
         return true;
     }
-    
+
     /**
      *    Recursive type test for each element of an array.
      *    @param mixed $first    Test subject.
      *    @param mixed $second   Comparison object.
      *    @return boolean        True if identical.
      *    @access private
-     *    @static
      */
-    function _isArrayOfIdenticalTypes($first, $second) {
+    protected static function isArrayOfIdenticalTypes($first, $second) {
         if (array_keys($first) != array_keys($second)) {
             return false;
         }
         foreach (array_keys($first) as $key) {
-            $is_identical = SimpleTestCompatibility::_isIdenticalType(
+            $is_identical = SimpleTestCompatibility::isIdenticalType(
                     $first[$key],
                     $second[$key]);
             if (! $is_identical) {
@@ -98,16 +94,15 @@ class SimpleTestCompatibility {
         }
         return true;
     }
-    
+
     /**
      *    Test for two variables being aliases.
      *    @param mixed $first    Test subject.
      *    @param mixed $second   Comparison object.
      *    @return boolean        True if same.
      *    @access public
-     *    @static
      */
-    function isReference(&$first, &$second) {
+    static function isReference(&$first, &$second) {
         if (version_compare(phpversion(), '5', '>=') && is_object($first)) {
             return ($first === $second);
         }
@@ -124,7 +119,7 @@ class SimpleTestCompatibility {
         $first = $temp;
         return $is_ref;
     }
-    
+
     /**
      *    Test to see if an object is a member of a
      *    class hiearchy.
@@ -132,9 +127,8 @@ class SimpleTestCompatibility {
      *    @param string $class     Root name of hiearchy.
      *    @return boolean         True if class in hiearchy.
      *    @access public
-     *    @static
      */
-    function isA($object, $class) {
+    static function isA($object, $class) {
         if (version_compare(phpversion(), '5') >= 0) {
             if (! class_exists($class, false)) {
                 if (function_exists('interface_exists')) {
@@ -152,15 +146,14 @@ class SimpleTestCompatibility {
         return ((strtolower($class) == get_class($object))
                 or (is_subclass_of($object, $class)));
     }
-    
+
     /**
      *    Sets a socket timeout for each chunk.
      *    @param resource $handle    Socket handle.
      *    @param integer $timeout    Limit in seconds.
      *    @access public
-     *    @static
      */
-    function setTimeout($handle, $timeout) {
+    static function setTimeout($handle, $timeout) {
         if (function_exists('stream_set_timeout')) {
             stream_set_timeout($handle, $timeout, 0);
         } elseif (function_exists('socket_set_timeout')) {
index abbc2ff..7a28f4a 100644 (file)
@@ -3,7 +3,7 @@
  *  Base include file for SimpleTest
  *  @package    SimpleTest
  *  @subpackage WebTester
- *  @version    $Id$
+ *  @version    $Id: cookies.php 2011 2011-04-29 08:22:48Z pp11 $
  */
 
 /**#@+
@@ -21,13 +21,13 @@ require_once(dirname(__FILE__) . '/url.php');
  *    @subpackage WebTester
  */
 class SimpleCookie {
-    var $_host;
-    var $_name;
-    var $_value;
-    var $_path;
-    var $_expiry;
-    var $_is_secure;
-    
+    private $host;
+    private $name;
+    private $value;
+    private $path;
+    private $expiry;
+    private $is_secure;
+
     /**
      *    Constructor. Sets the stored values.
      *    @param string $name            Cookie key.
@@ -36,20 +36,20 @@ class SimpleCookie {
      *    @param string $expiry          Expiry date as string.
      *    @param boolean $is_secure      Currently ignored.
      */
-    function SimpleCookie($name, $value = false, $path = false, $expiry = false, $is_secure = false) {
-        $this->_host = false;
-        $this->_name = $name;
-        $this->_value = $value;
-        $this->_path = ($path ? $this->_fixPath($path) : "/");
-        $this->_expiry = false;
+    function __construct($name, $value = false, $path = false, $expiry = false, $is_secure = false) {
+        $this->host = false;
+        $this->name = $name;
+        $this->value = $value;
+        $this->path = ($path ? $this->fixPath($path) : "/");
+        $this->expiry = false;
         if (is_string($expiry)) {
-            $this->_expiry = strtotime($expiry);
+            $this->expiry = strtotime($expiry);
         } elseif (is_integer($expiry)) {
-            $this->_expiry = $expiry;
+            $this->expiry = $expiry;
         }
-        $this->_is_secure = $is_secure;
+        $this->is_secure = $is_secure;
     }
-    
+
     /**
      *    Sets the host. The cookie rules determine
      *    that the first two parts are taken for
@@ -61,13 +61,13 @@ class SimpleCookie {
      *    @access public
      */
     function setHost($host) {
-        if ($host = $this->_truncateHost($host)) {
-            $this->_host = $host;
+        if ($host = $this->truncateHost($host)) {
+            $this->host = $host;
             return true;
         }
         return false;
     }
-    
+
     /**
      *    Accessor for the truncated host to which this
      *    cookie applies.
@@ -75,9 +75,9 @@ class SimpleCookie {
      *    @access public
      */
     function getHost() {
-        return $this->_host;
+        return $this->host;
     }
-    
+
     /**
      *    Test for a cookie being valid for a host name.
      *    @param string $host    Host to test against.
@@ -85,9 +85,9 @@ class SimpleCookie {
      *                           here.
      */
     function isValidHost($host) {
-        return ($this->_truncateHost($host) === $this->getHost());
+        return ($this->truncateHost($host) === $this->getHost());
     }
-    
+
     /**
      *    Extracts just the domain part that determines a
      *    cookie's host validity.
@@ -95,7 +95,7 @@ class SimpleCookie {
      *    @return string        Domain or false on a bad host.
      *    @access private
      */
-    function _truncateHost($host) {
+    protected function truncateHost($host) {
         $tlds = SimpleUrl::getAllTopLevelDomains();
         if (preg_match('/[a-z\-]+\.(' . $tlds . ')$/i', $host, $matches)) {
             return $matches[0];
@@ -104,16 +104,16 @@ class SimpleCookie {
         }
         return false;
     }
-    
+
     /**
      *    Accessor for name.
      *    @return string       Cookie key.
      *    @access public
      */
     function getName() {
-        return $this->_name;
+        return $this->name;
     }
-    
+
     /**
      *    Accessor for value. A deleted cookie will
      *    have an empty string for this.
@@ -121,18 +121,18 @@ class SimpleCookie {
      *    @access public
      */
     function getValue() {
-        return $this->_value;
+        return $this->value;
     }
-    
+
     /**
      *    Accessor for path.
      *    @return string       Valid cookie path.
      *    @access public
      */
     function getPath() {
-        return $this->_path;
+        return $this->path;
     }
-    
+
     /**
      *    Tests a path to see if the cookie applies
      *    there. The test path must be longer or
@@ -143,23 +143,23 @@ class SimpleCookie {
      */
     function isValidPath($path) {
         return (strncmp(
-                $this->_fixPath($path),
+                $this->fixPath($path),
                 $this->getPath(),
                 strlen($this->getPath())) == 0);
     }
-    
+
     /**
      *    Accessor for expiry.
      *    @return string       Expiry string.
      *    @access public
      */
     function getExpiry() {
-        if (! $this->_expiry) {
+        if (! $this->expiry) {
             return false;
         }
-        return gmdate("D, d M Y H:i:s", $this->_expiry) . " GMT";
+        return gmdate("D, d M Y H:i:s", $this->expiry) . " GMT";
     }
-    
+
     /**
      *    Test to see if cookie is expired against
      *    the cookie format time or timestamp.
@@ -172,15 +172,15 @@ class SimpleCookie {
      *    @access public
      */
     function isExpired($now) {
-        if (! $this->_expiry) {
+        if (! $this->expiry) {
             return true;
         }
         if (is_string($now)) {
             $now = strtotime($now);
         }
-        return ($this->_expiry < $now);
+        return ($this->expiry < $now);
     }
-    
+
     /**
      *    Ages the cookie by the specified number of
      *    seconds.
@@ -188,27 +188,27 @@ class SimpleCookie {
      *    @public
      */
     function agePrematurely($interval) {
-        if ($this->_expiry) {
-            $this->_expiry -= $interval;
+        if ($this->expiry) {
+            $this->expiry -= $interval;
         }
     }
-    
+
     /**
      *    Accessor for the secure flag.
      *    @return boolean       True if cookie needs SSL.
      *    @access public
      */
     function isSecure() {
-        return $this->_is_secure;
+        return $this->is_secure;
     }
-    
+
     /**
      *    Adds a trailing and leading slash to the path
      *    if missing.
      *    @param string $path            Path to fix.
      *    @access private
      */
-    function _fixPath($path) {
+    protected function fixPath($path) {
         if (substr($path, 0, 1) != '/') {
             $path = '/' . $path;
         }
@@ -226,16 +226,16 @@ class SimpleCookie {
  *    @subpackage WebTester
  */
 class SimpleCookieJar {
-    var $_cookies;
-    
+    private $cookies;
+
     /**
      *    Constructor. Jar starts empty.
      *    @access public
      */
-    function SimpleCookieJar() {
-        $this->_cookies = array();
+    function __construct() {
+        $this->cookies = array();
     }
-    
+
     /**
      *    Removes expired and temporary cookies as if
      *    the browser was closed and re-opened.
@@ -244,21 +244,21 @@ class SimpleCookieJar {
      */
     function restartSession($date = false) {
         $surviving_cookies = array();
-        for ($i = 0; $i < count($this->_cookies); $i++) {
-            if (! $this->_cookies[$i]->getValue()) {
+        for ($i = 0; $i < count($this->cookies); $i++) {
+            if (! $this->cookies[$i]->getValue()) {
                 continue;
             }
-            if (! $this->_cookies[$i]->getExpiry()) {
+            if (! $this->cookies[$i]->getExpiry()) {
                 continue;
             }
-            if ($date && $this->_cookies[$i]->isExpired($date)) {
+            if ($date && $this->cookies[$i]->isExpired($date)) {
                 continue;
             }
-            $surviving_cookies[] = $this->_cookies[$i];
+            $surviving_cookies[] = $this->cookies[$i];
         }
-        $this->_cookies = $surviving_cookies;
+        $this->cookies = $surviving_cookies;
     }
-    
+
     /**
      *    Ages all cookies in the cookie jar.
      *    @param integer $interval     The old session is moved
@@ -268,11 +268,11 @@ class SimpleCookieJar {
      *    @access public
      */
     function agePrematurely($interval) {
-        for ($i = 0; $i < count($this->_cookies); $i++) {
-            $this->_cookies[$i]->agePrematurely($interval);
+        for ($i = 0; $i < count($this->cookies); $i++) {
+            $this->cookies[$i]->agePrematurely($interval);
         }
     }
-    
+
     /**
      *    Sets an additional cookie. If a cookie has
      *    the same name and path it is replaced.
@@ -288,9 +288,9 @@ class SimpleCookieJar {
         if ($host) {
             $cookie->setHost($host);
         }
-        $this->_cookies[$this->_findFirstMatch($cookie)] = $cookie;
+        $this->cookies[$this->findFirstMatch($cookie)] = $cookie;
     }
-    
+
     /**
      *    Finds a matching cookie to write over or the
      *    first empty slot if none.
@@ -298,20 +298,20 @@ class SimpleCookieJar {
      *    @return integer                Available slot.
      *    @access private
      */
-    function _findFirstMatch($cookie) {
-        for ($i = 0; $i < count($this->_cookies); $i++) {
-            $is_match = $this->_isMatch(
+    protected function findFirstMatch($cookie) {
+        for ($i = 0; $i < count($this->cookies); $i++) {
+            $is_match = $this->isMatch(
                     $cookie,
-                    $this->_cookies[$i]->getHost(),
-                    $this->_cookies[$i]->getPath(),
-                    $this->_cookies[$i]->getName());
+                    $this->cookies[$i]->getHost(),
+                    $this->cookies[$i]->getPath(),
+                    $this->cookies[$i]->getName());
             if ($is_match) {
                 return $i;
             }
         }
-        return count($this->_cookies);
+        return count($this->cookies);
     }
-    
+
     /**
      *    Reads the most specific cookie value from the
      *    browser cookies. Looks for the longest path that
@@ -325,8 +325,8 @@ class SimpleCookieJar {
      */
     function getCookieValue($host, $path, $name) {
         $longest_path = '';
-        foreach ($this->_cookies as $cookie) {
-            if ($this->_isMatch($cookie, $host, $path, $name)) {
+        foreach ($this->cookies as $cookie) {
+            if ($this->isMatch($cookie, $host, $path, $name)) {
                 if (strlen($cookie->getPath()) > strlen($longest_path)) {
                     $value = $cookie->getValue();
                     $longest_path = $cookie->getPath();
@@ -335,7 +335,7 @@ class SimpleCookieJar {
         }
         return (isset($value) ? $value : false);
     }
-    
+
     /**
      *    Tests cookie for matching against search
      *    criteria.
@@ -347,7 +347,7 @@ class SimpleCookieJar {
      *    @return boolean              True if matched.
      *    @access private
      */
-    function _isMatch($cookie, $host, $path, $name) {
+    protected function isMatch($cookie, $host, $path, $name) {
         if ($cookie->getName() != $name) {
             return false;
         }
@@ -359,7 +359,7 @@ class SimpleCookieJar {
         }
         return true;
     }
-    
+
     /**
      *    Uses a URL to sift relevant cookies by host and
      *    path. Results are list of strings of form "name=value".
@@ -369,8 +369,8 @@ class SimpleCookieJar {
      */
     function selectAsPairs($url) {
         $pairs = array();
-        foreach ($this->_cookies as $cookie) {
-            if ($this->_isMatch($cookie, $url->getHost(), $url->getPath(), $cookie->getName())) {
+        foreach ($this->cookies as $cookie) {
+            if ($this->isMatch($cookie, $url->getHost(), $url->getPath(), $cookie->getName())) {
                 $pairs[] = $cookie->getName() . '=' . $cookie->getValue();
             }
         }
index a25844f..6feb67a 100644 (file)
@@ -3,7 +3,7 @@
  *  Optional include file for SimpleTest
  *  @package    SimpleTest
  *  @subpackage UnitTester
- *  @version    $Id$
+ *  @version    $Id: default_reporter.php 2011 2011-04-29 08:22:48Z pp11 $
  */
 
 /**#@+
@@ -23,75 +23,101 @@ require_once(dirname(__FILE__) . '/xml.php');
  *    @subpackage UnitTester
  */
 class SimpleCommandLineParser {
-    var $_to_property = array(
-            'case' => '_case', 'c' => '_case',
-            'test' => '_test', 't' => '_test',
-            'xml' => '_xml', 'x' => '_xml');
-    var $_case = '';
-    var $_test = '';
-    var $_xml = false;
-    var $_no_skips = false;
-    
+    private $to_property = array(
+            'case' => 'case', 'c' => 'case',
+            'test' => 'test', 't' => 'test',
+    );
+    private $case = '';
+    private $test = '';
+    private $xml = false;
+    private $help = false;
+    private $no_skips = false;
+
     /**
      *    Parses raw command line arguments into object properties.
      *    @param string $arguments        Raw commend line arguments.
      */
-    function SimpleCommandLineParser($arguments) {
+    function __construct($arguments) {
         if (! is_array($arguments)) {
             return;
         }
         foreach ($arguments as $i => $argument) {
             if (preg_match('/^--?(test|case|t|c)=(.+)$/', $argument, $matches)) {
-                $property = $this->_to_property[$matches[1]];
+                $property = $this->to_property[$matches[1]];
                 $this->$property = $matches[2];
             } elseif (preg_match('/^--?(test|case|t|c)$/', $argument, $matches)) {
-                $property = $this->_to_property[$matches[1]];
+                $property = $this->to_property[$matches[1]];
                 if (isset($arguments[$i + 1])) {
                     $this->$property = $arguments[$i + 1];
                 }
             } elseif (preg_match('/^--?(xml|x)$/', $argument)) {
-                $this->_xml = true;
+                $this->xml = true;
             } elseif (preg_match('/^--?(no-skip|no-skips|s)$/', $argument)) {
-                $this->_no_skips = true;
+                $this->no_skips = true;
+            } elseif (preg_match('/^--?(help|h)$/', $argument)) {
+                $this->help = true;
             }
         }
     }
-    
+
     /**
      *    Run only this test.
      *    @return string        Test name to run.
-     *    @access public
      */
     function getTest() {
-        return $this->_test;
+        return $this->test;
     }
-    
+
     /**
      *    Run only this test suite.
      *    @return string        Test class name to run.
-     *    @access public
      */
     function getTestCase() {
-        return $this->_case;
+        return $this->case;
     }
-    
+
     /**
      *    Output should be XML or not.
      *    @return boolean        True if XML desired.
-     *    @access public
      */
     function isXml() {
-        return $this->_xml;
+        return $this->xml;
     }
-    
+
     /**
      *    Output should suppress skip messages.
      *    @return boolean        True for no skips.
-     *    @access public
      */
     function noSkips() {
-        return $this->_no_skips;
+        return $this->no_skips;
+    }
+
+    /**
+     *    Output should be a help message. Disabled during XML mode.
+     *    @return boolean        True if help message desired.
+     */
+    function help() {
+        return $this->help && ! $this->xml;
     }
+
+    /**
+     *    Returns plain-text help message for command line runner.
+     *    @return string         String help message
+     */
+    function getHelpText() {
+        return <<<HELP
+SimpleTest command line default reporter (autorun)
+Usage: php <test_file> [args...]
+
+    -c <class>      Run only the test-case <class>
+    -t <method>     Run only the test method <method>
+    -s              Suppress skip messages
+    -x              Return test results in XML
+    -h              Display this help message
+
+HELP;
+    }
+
 }
 
 /**
@@ -102,15 +128,19 @@ class SimpleCommandLineParser {
  *    @subpackage UnitTester
  */
 class DefaultReporter extends SimpleReporterDecorator {
-    
+
     /**
-     *  Assembles the appopriate reporter for the environment.
+     *  Assembles the appropriate reporter for the environment.
      */
-    function DefaultReporter() {
+    function __construct() {
         if (SimpleReporter::inCli()) {
-            global $argv;
-            $parser = new SimpleCommandLineParser($argv);
+            $parser = new SimpleCommandLineParser($_SERVER['argv']);
             $interfaces = $parser->isXml() ? array('XmlReporter') : array('TextReporter');
+            if ($parser->help()) {
+                // I'm not sure if we should do the echo'ing here -- ezyang
+                echo $parser->getHelpText();
+                exit(1);
+            }
             $reporter = new SelectiveReporter(
                     SimpleTest::preferred($interfaces),
                     $parser->getTestCase(),
@@ -127,7 +157,7 @@ class DefaultReporter extends SimpleReporterDecorator {
                 $reporter = new NoSkipsReporter($reporter);
             }
         }
-        $this->SimpleReporterDecorator($reporter);
+        parent::__construct($reporter);
     }
 }
 ?>
\ No newline at end of file
old mode 100644 (file)
new mode 100755 (executable)
index 706c4ce..a325e14
@@ -3,7 +3,7 @@
  *  base include file for SimpleTest
  *  @package    SimpleTest
  *  @subpackage UnitTester
- *  @version    $Id$
+ *  @version    $Id: detached.php 1784 2008-04-26 13:07:14Z pp11 $
  */
 
 /**#@+
@@ -19,9 +19,9 @@ require_once(dirname(__FILE__) . '/shell_tester.php');
  *    @subpackage UnitTester
  */
 class DetachedTestCase {
-    var $_command;
-    var $_dry_command;
-    var $_size;
+    private $command;
+    private $dry_command;
+    private $size;
 
     /**
      *    Sets the location of the remote test.
@@ -29,10 +29,10 @@ class DetachedTestCase {
      *    @param string $dry_command   Script for dry run.
      *    @access public
      */
-    function DetachedTestCase($command, $dry_command = false) {
-        $this->_command = $command;
-        $this->_dry_command = $dry_command ? $dry_command : $command;
-        $this->_size = false;
+    function __construct($command, $dry_command = false) {
+        $this->command = $command;
+        $this->dry_command = $dry_command ? $dry_command : $command;
+        $this->size = false;
     }
 
     /**
@@ -41,7 +41,7 @@ class DetachedTestCase {
      *    @access public
      */
     function getLabel() {
-        return $this->_command;
+        return $this->command;
     }
 
     /**
@@ -53,11 +53,11 @@ class DetachedTestCase {
      *    @access public
      */
     function run(&$reporter) {
-        $shell = new SimpleShell();
-        $shell->execute($this->_command);
-        $parser = &$this->_createParser($reporter);
+        $shell = &new SimpleShell();
+        $shell->execute($this->command);
+        $parser = &$this->createParser($reporter);
         if (! $parser->parse($shell->getOutput())) {
-            trigger_error('Cannot parse incoming XML from [' . $this->_command . ']');
+            trigger_error('Cannot parse incoming XML from [' . $this->command . ']');
             return false;
         }
         return true;
@@ -69,18 +69,18 @@ class DetachedTestCase {
      *    @access public
      */
     function getSize() {
-        if ($this->_size === false) {
-            $shell = new SimpleShell();
-            $shell->execute($this->_dry_command);
-            $reporter = new SimpleReporter();
-            $parser = &$this->_createParser($reporter);
+        if ($this->size === false) {
+            $shell = &new SimpleShell();
+            $shell->execute($this->dry_command);
+            $reporter = &new SimpleReporter();
+            $parser = &$this->createParser($reporter);
             if (! $parser->parse($shell->getOutput())) {
-                trigger_error('Cannot parse incoming XML from [' . $this->_dry_command . ']');
+                trigger_error('Cannot parse incoming XML from [' . $this->dry_command . ']');
                 return false;
             }
-            $this->_size = $reporter->getTestCaseCount();
+            $this->size = $reporter->getTestCaseCount();
         }
-        return $this->_size;
+        return $this->size;
     }
 
     /**
@@ -89,7 +89,7 @@ class DetachedTestCase {
      *    @return SimpleTestXmlListener      XML reader.
      *    @access protected
      */
-    function &_createParser(&$reporter) {
+    protected function &createParser(&$reporter) {
         return new SimpleTestXmlParser($reporter);
     }
 }
diff --git a/lib/simpletestlib/docs/en/authentication_documentation.html b/lib/simpletestlib/docs/en/authentication_documentation.html
new file mode 100644 (file)
index 0000000..e058e19
--- /dev/null
@@ -0,0 +1,378 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>SimpleTest documentation for testing log-in and authentication</title>
+<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
+</head>
+<body>
+<div class="menu_back"><div class="menu">
+<a href="index.html">SimpleTest</a>
+                |
+                <a href="overview.html">Overview</a>
+                |
+                <a href="unit_test_documentation.html">Unit tester</a>
+                |
+                <a href="group_test_documentation.html">Group tests</a>
+                |
+                <a href="mock_objects_documentation.html">Mock objects</a>
+                |
+                <a href="partial_mocks_documentation.html">Partial mocks</a>
+                |
+                <a href="reporter_documentation.html">Reporting</a>
+                |
+                <a href="expectation_documentation.html">Expectations</a>
+                |
+                <a href="web_tester_documentation.html">Web tester</a>
+                |
+                <a href="form_testing_documentation.html">Testing forms</a>
+                |
+                <span class="chosen">Authentication</span>
+                |
+                <a href="browser_documentation.html">Scriptable browser</a>
+</div></div>
+<h1>Authentication documentation</h1>
+        This page...
+        <ul>
+<li>
+            Getting through <a href="#basic">Basic HTTP authentication</a>
+        </li>
+<li>
+            Testing <a href="#cookies">cookie based authentication</a>
+        </li>
+<li>
+            Managing <a href="#session">browser sessions</a> and timeouts
+        </li>
+</ul>
+<div class="content">
+        
+            <p>
+                One of the trickiest, and yet most important, areas
+                of testing web sites is the security.
+                Testing these schemes is one of the core goals of
+                the SimpleTest web tester.
+            </p>
+        
+        <h2>
+<a class="target" name="basic"></a>Basic HTTP authentication</h2>
+            <p>
+                If you fetch a page protected by basic authentication then
+                rather than receiving content, you will instead get a 401
+                header.
+                We can illustrate this with this test...
+<pre>
+class AuthenticationTest extends WebTestCase {<strong>
+    function test401Header() {
+        $this-&gt;get('http://www.lastcraft.com/protected/');
+        $this-&gt;showHeaders();
+    }</strong>
+}
+</pre>
+                This allows us to see the challenge header...
+                <div class="demo">
+                    <h1>File test</h1>
+<pre>
+HTTP/1.1 401 Authorization Required
+Date: Sat, 18 Sep 2004 19:25:18 GMT
+Server: Apache/1.3.29 (Unix) PHP/4.3.4
+WWW-Authenticate: Basic realm="SimpleTest basic authentication"
+Connection: close
+Content-Type: text/html; charset=iso-8859-1
+</pre>
+                    <div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
+                    <strong>0</strong> passes, <strong>0</strong> fails and <strong>0</strong> exceptions.</div>
+                </div>
+                We are trying to get away from visual inspection though, and so SimpleTest
+                allows to make automated assertions against the challenge.
+                Here is a thorough test of our header...
+<pre>
+class AuthenticationTest extends WebTestCase {
+    function test401Header() {
+        $this-&gt;get('http://www.lastcraft.com/protected/');<strong>
+        $this-&gt;assertAuthentication('Basic');
+        $this-&gt;assertResponse(401);
+        $this-&gt;assertRealm('SimpleTest basic authentication');</strong>
+    }
+}
+</pre>
+                Any one of these tests would normally do on it's own depending
+                on the amount of detail you want to see.
+            </p>
+            <p>
+                One theme that runs through SimpleTest is the ability to use
+                <span class="new_code">SimpleExpectation</span> objects wherever a simple
+                match is not enough.
+                If you want only an approximate match to the realm for
+                example, you can do this...
+<pre>
+class AuthenticationTest extends WebTestCase {
+    function test401Header() {
+        $this-&gt;get('http://www.lastcraft.com/protected/');
+        $this-&gt;assertRealm(<strong>new PatternExpectation('/simpletest/i')</strong>);
+    }
+}
+</pre>
+                This type of test, testing HTTP responses, is not typical.
+            </p>
+            <p>
+                Most of the time we are not interested in testing the
+                authentication itself, but want to get past it to test
+                the pages underneath.
+                As soon as the challenge has been issued we can reply with
+                an authentication response...
+<pre>
+class AuthenticationTest extends WebTestCase {
+    function testCanAuthenticate() {
+        $this-&gt;get('http://www.lastcraft.com/protected/');<strong>
+        $this-&gt;authenticate('Me', 'Secret');</strong>
+        $this-&gt;assertTitle(...);
+    }
+}
+</pre>
+                The username and password will now be sent with every
+                subsequent request to that directory and subdirectories.
+                You will have to authenticate again if you step outside
+                the authenticated directory, but SimpleTest is smart enough
+                to merge subdirectories into a common realm.
+            </p>
+            <p>
+                If you want, you can shortcut this step further by encoding
+                the log in details straight into the URL...
+<pre>
+class AuthenticationTest extends WebTestCase {
+    function testCanReadAuthenticatedPages() {
+        $this-&gt;get('http://<strong>Me:Secret@</strong>www.lastcraft.com/protected/');
+        $this-&gt;assertTitle(...);
+    }
+}
+</pre>
+                If your username or password has special characters, then you
+                will have to URL encode them or the request will not be parsed
+                correctly.
+                I'm afraid we leave this up to you.
+            </p>
+            <p>
+                A problem with encoding the login details directly in the URL is
+                the authentication header will not be sent on subsequent requests.
+                If you navigate with relative URLs though, the authentication
+                information will be preserved along with the domain name.
+            </p>
+            <p>
+                Normally though, you use the <span class="new_code">authenticate()</span> call.
+                SimpleTest will then remember your login information on each request.
+            </p>
+            <p>
+                Only testing with basic authentication is currently supported, and
+                this is only really secure in tandem with HTTPS connections.
+                This is usually good enough to protect test server from prying eyes,
+                however.
+                Digest authentication and NTLM authentication may be added
+                in the future if enough people request this feature.
+            </p>
+        
+        <h2>
+<a class="target" name="cookies"></a>Cookies</h2>
+            <p>
+                Basic authentication doesn't give enough control over the
+                user interface for web developers.
+                More likely this functionality will be coded directly into
+                the web architecture using cookies with complicated timeouts.
+                We need to be able to test this too.
+            </p>
+            <p>
+                Starting with a simple log-in form...
+<pre>
+&lt;form&gt;
+    Username:
+    &lt;input type="text" name="u" value="" /&gt;&lt;br /&gt;
+    Password:
+    &lt;input type="password" name="p" value="" /&gt;&lt;br /&gt;
+    &lt;input type="submit" value="Log in" /&gt;
+&lt;/form&gt;
+</pre>
+                Which looks like...
+            </p>
+            <p>
+                <form class="demo">
+                    Username:
+                    <input type="text" name="u" value=""><br>
+                    Password:
+                    <input type="password" name="p" value=""><br>
+                    <input type="submit" value="Log in">
+                </form>
+            </p>
+            <p>
+                Let's suppose that in fetching this page a cookie has been
+                set with a session ID.
+                We are not going to fill the form in yet, just test that
+                we are tracking the user.
+                Here is the test...
+<pre>
+class LogInTest extends WebTestCase {
+    function testSessionCookieSetBeforeForm() {
+        $this-&gt;get('http://www.my-site.com/login.php');<strong>
+        $this-&gt;assertCookie('SID');</strong>
+    }
+}
+</pre>
+                All we are doing is confirming that the cookie is set.
+                As the value is likely to be rather cryptic it's not
+                really worth testing this with...
+<pre>
+class LogInTest extends WebTestCase {
+    function testSessionCookieIsCorrectPattern() {
+        $this-&gt;get('http://www.my-site.com/login.php');
+        $this-&gt;assertCookie('SID', <strong>new PatternExpectation('/[a-f0-9]{32}/i')</strong>);
+    }
+}
+</pre>
+                If you are using PHP to handle sessions for you then
+                this test is even more useless, as we are just testing PHP itself.
+            </p>
+            <p>
+                The simplest test of logging in is to visually inspect the
+                next page to see if you are really logged in.
+                Just test the next page with <span class="new_code">WebTestCase::assertText()</span>.
+            </p>
+            <p>
+                The test is similar to any other form test,
+                but we might want to confirm that we still have the same
+                cookie after log-in as before we entered.
+                We wouldn't want to lose track of this after all.
+                Here is a possible test for this...
+<pre>
+class LogInTest extends WebTestCase {
+    ...
+    function testSessionCookieSameAfterLogIn() {
+        $this-&gt;get('http://www.my-site.com/login.php');<strong>
+        $session = $this-&gt;getCookie('SID');
+        $this-&gt;setField('u', 'Me');
+        $this-&gt;setField('p', 'Secret');
+        $this-&gt;click('Log in');
+        $this-&gt;assertText('Welcome Me');
+        $this-&gt;assertCookie('SID', $session);</strong>
+    }
+}
+</pre>
+                This confirms that the session identifier is maintained
+                afer log-in and we haven't accidently reset it.
+            </p>
+            <p>
+                We could even attempt to hack our own system by setting
+                arbitrary cookies to gain access...
+<pre>
+class LogInTest extends WebTestCase {
+    ...
+    function testSessionCookieSameAfterLogIn() {
+        $this-&gt;get('http://www.my-site.com/login.php');<strong>
+        $this-&gt;setCookie('SID', 'Some other session');
+        $this-&gt;get('http://www.my-site.com/restricted.php');</strong>
+        $this-&gt;assertText('Access denied');
+    }
+}
+</pre>
+                Is your site protected from this attack?
+            </p>
+        
+        <h2>
+<a class="target" name="session"></a>Browser sessions</h2>
+            <p>
+                If you are testing an authentication system a critical piece
+                of behaviour is what happens when a user logs back in.
+                We would like to simulate closing and reopening a browser...
+<pre>
+class LogInTest extends WebTestCase {
+    ...
+    function testLoseAuthenticationAfterBrowserClose() {
+        $this-&gt;get('http://www.my-site.com/login.php');
+        $this-&gt;setField('u', 'Me');
+        $this-&gt;setField('p', 'Secret');
+        $this-&gt;click('Log in');
+        $this-&gt;assertText('Welcome Me');<strong>
+        
+        $this-&gt;restart();
+        $this-&gt;get('http://www.my-site.com/restricted.php');
+        $this-&gt;assertText('Access denied');</strong>
+    }
+}
+</pre>
+                The <span class="new_code">WebTestCase::restart()</span> method will
+                preserve cookies that have unexpired timeouts, but throw away
+                those that are temporary or expired.
+                You can optionally specify the time and date that the restart
+                happened.
+            </p>
+            <p>
+                Expiring cookies can be a problem.
+                After all, if you have a cookie that expires after an hour,
+                you don't want to stall the test for an hour while waiting
+                for the cookie to pass it's timeout.
+            </p>
+            <p>
+                To push the cookies over the hour limit you can age them
+                before you restart the session...
+<pre>
+class LogInTest extends WebTestCase {
+    ...
+    function testLoseAuthenticationAfterOneHour() {
+        $this-&gt;get('http://www.my-site.com/login.php');
+        $this-&gt;setField('u', 'Me');
+        $this-&gt;setField('p', 'Secret');
+        $this-&gt;click('Log in');
+        $this-&gt;assertText('Welcome Me');
+        <strong>
+        $this-&gt;ageCookies(3600);</strong>
+        $this-&gt;restart();
+        $this-&gt;get('http://www.my-site.com/restricted.php');
+        $this-&gt;assertText('Access denied');
+    }
+}
+</pre>
+                After the restart it will appear that cookies are an
+                hour older, and any that pass their expiry will have
+                disappeared.
+            </p>
+        
+    </div>
+        References and related information...
+        <ul>
+<li>
+            SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
+        </li>
+<li>
+            SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
+        </li>
+<li>
+            The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
+            gives full detail on the classes and assertions available.
+        </li>
+</ul>
+<div class="menu_back"><div class="menu">
+<a href="index.html">SimpleTest</a>
+                |
+                <a href="overview.html">Overview</a>
+                |
+                <a href="unit_test_documentation.html">Unit tester</a>
+                |
+                <a href="group_test_documentation.html">Group tests</a>
+                |
+                <a href="mock_objects_documentation.html">Mock objects</a>
+                |
+                <a href="partial_mocks_documentation.html">Partial mocks</a>
+                |
+                <a href="reporter_documentation.html">Reporting</a>
+                |
+                <a href="expectation_documentation.html">Expectations</a>
+                |
+                <a href="web_tester_documentation.html">Web tester</a>
+                |
+                <a href="form_testing_documentation.html">Testing forms</a>
+                |
+                <span class="chosen">Authentication</span>
+                |
+                <a href="browser_documentation.html">Scriptable browser</a>
+</div></div>
+<div class="copyright">
+            Copyright<br>Marcus Baker 2006
+        </div>
+</body>
+</html>
diff --git a/lib/simpletestlib/docs/en/browser_documentation.html b/lib/simpletestlib/docs/en/browser_documentation.html
new file mode 100644 (file)
index 0000000..809c143
--- /dev/null
@@ -0,0 +1,501 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>SimpleTest documentation for the scriptable web browser component</title>
+<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
+</head>
+<body>
+<div class="menu_back"><div class="menu">
+<a href="index.html">SimpleTest</a>
+                |
+                <a href="overview.html">Overview</a>
+                |
+                <a href="unit_test_documentation.html">Unit tester</a>
+                |
+                <a href="group_test_documentation.html">Group tests</a>
+                |
+                <a href="mock_objects_documentation.html">Mock objects</a>
+                |
+                <a href="partial_mocks_documentation.html">Partial mocks</a>
+                |
+                <a href="reporter_documentation.html">Reporting</a>
+                |
+                <a href="expectation_documentation.html">Expectations</a>
+                |
+                <a href="web_tester_documentation.html">Web tester</a>
+                |
+                <a href="form_testing_documentation.html">Testing forms</a>
+                |
+                <a href="authentication_documentation.html">Authentication</a>
+                |
+                <span class="chosen">Scriptable browser</span>
+</div></div>
+<h1>PHP Scriptable Web Browser</h1>
+        This page...
+        <ul>
+<li>
+            Using the bundled <a href="#scripting">web browser in scripts</a>
+        </li>
+<li>
+            <a href="#debug">Debugging</a> failed pages
+        </li>
+<li>
+            Complex <a href="#unit">tests with multiple web browsers</a>
+        </li>
+</ul>
+<div class="content">
+        
+            <p>
+                SimpleTest's web browser component can be used not just
+                outside of the <span class="new_code">WebTestCase</span> class, but also
+                independently of the SimpleTest framework itself.
+            </p>
+        
+        <h2>
+<a class="target" name="scripting"></a>The Scriptable Browser</h2>
+            <p>
+                You can use the web browser in PHP scripts to confirm
+                services are up and running, or to extract information
+                from them at a regular basis.
+                For example, here is a small script to extract the current number of
+                open PHP 5 bugs from the <a href="http://www.php.net/">PHP web site</a>...
+<pre>
+<strong>&lt;?php
+require_once('simpletest/browser.php');
+    
+$browser = &amp;new SimpleBrowser();
+$browser-&gt;get('http://php.net/');
+$browser-&gt;click('reporting bugs');
+$browser-&gt;click('statistics');
+$page = $browser-&gt;click('PHP 5 bugs only');
+preg_match('/status=Open.*?by=Any.*?(\d+)&lt;\/a&gt;/', $page, $matches);
+print $matches[1];
+?&gt;</strong>
+</pre>
+                There are simpler methods to do this particular example in PHP
+                of course.
+                For example you can just use the PHP <span class="new_code">file()</span>
+                command against what here is a pretty fixed page.
+                However, using the web browser for scripts allows authentication,
+                correct handling of cookies, automatic loading of frames, redirects,
+                form submission and the ability to examine the page headers.
+            <p>
+            </p>
+                Methods such as periodic scraping are fragile against a site that is constantly
+                evolving and you would want a more direct way of accessing
+                data in a permanent set up, but for simple tasks this can provide
+                a very rapid solution.
+            </p>
+            <p>
+                All of the navigation methods used in the
+                <a href="web_tester_documentation.html">WebTestCase</a>
+                are present in the <span class="new_code">SimpleBrowser</span> class, but
+                the assertions are replaced with simpler accessors.
+                Here is a full list of the page navigation methods...
+                <table><tbody>
+                    <tr>
+<td><span class="new_code">addHeader($header)</span></td>
+<td>Adds a header to every fetch</td>
+</tr>
+                    <tr>
+<td><span class="new_code">useProxy($proxy, $username, $password)</span></td>
+<td>Use this proxy from now on</td>
+</tr>
+                    <tr>
+<td><span class="new_code">head($url, $parameters)</span></td>
+<td>Perform a HEAD request</td>
+</tr>
+                    <tr>
+<td><span class="new_code">get($url, $parameters)</span></td>
+<td>Fetch a page with GET</td>
+</tr>
+                    <tr>
+<td><span class="new_code">post($url, $parameters)</span></td>
+<td>Fetch a page with POST</td>
+</tr>
+                    <tr>
+<td><span class="new_code">click($label)</span></td>
+<td>Clicks visible link or button text</td>
+</tr>
+                    <tr>
+<td><span class="new_code">clickLink($label)</span></td>
+<td>Follows a link by label</td>
+</tr>
+                    <tr>
+<td><span class="new_code">clickLinkById($id)</span></td>
+<td>Follows a link by attribute</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getUrl()</span></td>
+<td>Current URL of page or frame</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getTitle()</span></td>
+<td>Page title</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getContent()</span></td>
+<td>Raw page or frame</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getContentAsText()</span></td>
+<td>HTML removed except for alt text</td>
+</tr>
+                    <tr>
+<td><span class="new_code">retry()</span></td>
+<td>Repeat the last request</td>
+</tr>
+                    <tr>
+<td><span class="new_code">back()</span></td>
+<td>Use the browser back button</td>
+</tr>
+                    <tr>
+<td><span class="new_code">forward()</span></td>
+<td>Use the browser forward button</td>
+</tr>
+                    <tr>
+<td><span class="new_code">authenticate($username, $password)</span></td>
+<td>Retry page or frame after a 401 response</td>
+</tr>
+                    <tr>
+<td><span class="new_code">restart($date)</span></td>
+<td>Restarts the browser for a new session</td>
+</tr>
+                    <tr>
+<td><span class="new_code">ageCookies($interval)</span></td>
+<td>Ages the cookies by the specified time</td>
+</tr>
+                    <tr>
+<td><span class="new_code">setCookie($name, $value)</span></td>
+<td>Sets an additional cookie</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getCookieValue($host, $path, $name)</span></td>
+<td>Reads the most specific cookie</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getCurrentCookieValue($name)</span></td>
+<td>Reads cookie for the current context</td>
+</tr>
+                </tbody></table>
+                The methods <span class="new_code">SimpleBrowser::useProxy()</span> and
+                <span class="new_code">SimpleBrowser::addHeader()</span> are special.
+                Once called they continue to apply to all subsequent fetches.
+            </p>
+            <p>
+                Navigating forms is similar to the
+                <a href="form_testing_documentation.html">WebTestCase form navigation</a>...
+                <table><tbody>
+                    <tr>
+<td><span class="new_code">setField($label, $value)</span></td>
+<td>Sets all form fields with that label or name</td>
+</tr>
+                    <tr>
+<td><span class="new_code">setFieldByName($name, $value)</span></td>
+<td>Sets all form fields with that name</td>
+</tr>
+                    <tr>
+<td><span class="new_code">setFieldById($id, $value)</span></td>
+<td>Sets all form fields with that id</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getField($label)</span></td>
+<td>Accessor for a form element value by label tag and then name</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getFieldByName($name)</span></td>
+<td>Accessor for a form element value using name attribute</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getFieldById($id)</span></td>
+<td>Accessor for a form element value</td>
+</tr>
+                    <tr>
+<td><span class="new_code">clickSubmit($label)</span></td>
+<td>Submits form by button label</td>
+</tr>
+                    <tr>
+<td><span class="new_code">clickSubmitByName($name)</span></td>
+<td>Submits form by button attribute</td>
+</tr>
+                    <tr>
+<td><span class="new_code">clickSubmitById($id)</span></td>
+<td>Submits form by button attribute</td>
+</tr>
+                    <tr>
+<td><span class="new_code">clickImage($label, $x, $y)</span></td>
+<td>Clicks an input tag of type image by title or alt text</td>
+</tr>
+                    <tr>
+<td><span class="new_code">clickImageByName($name, $x, $y)</span></td>
+<td>Clicks an input tag of type image by name</td>
+</tr>
+                    <tr>
+<td><span class="new_code">clickImageById($id, $x, $y)</span></td>
+<td>Clicks an input tag of type image by ID attribute</td>
+</tr>
+                    <tr>
+<td><span class="new_code">submitFormById($id)</span></td>
+<td>Submits by the form tag attribute</td>
+</tr>
+                </tbody></table>
+                At the moment there aren't many methods to list available links and fields.
+                <table><tbody>
+                    <tr>
+<td><span class="new_code">isClickable($label)</span></td>
+<td>Test to see if a click target exists by label or name</td>
+</tr>
+                    <tr>
+<td><span class="new_code">isSubmit($label)</span></td>
+<td>Test for the existence of a button with that label or name</td>
+</tr>
+                    <tr>
+<td><span class="new_code">isImage($label)</span></td>
+<td>Test for the existence of an image button with that label or name</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getLink($label)</span></td>
+<td>Finds a URL from its label</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getLinkById($label)</span></td>
+<td>Finds a URL from its ID attribute</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getUrls()</span></td>
+<td>Lists available links in the current page</td>
+</tr>
+                </tbody></table>
+                This will be expanded in later versions of SimpleTest.
+            </p>
+            <p>
+                Frames are a rather esoteric feature these days, but SimpleTest has
+                retained support for them.
+            </p>
+            <p>
+                Within a page, individual frames can be selected.
+                If no selection is made then all the frames are merged together
+                in one large conceptual page.
+                The content of the current page will be a concatenation of all of the
+                frames in the order that they were specified in the "frameset"
+                tags.
+                <table><tbody>
+                    <tr>
+<td><span class="new_code">getFrames()</span></td>
+<td>A dump of the current frame structure</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getFrameFocus()</span></td>
+<td>Current frame label or index</td>
+</tr>
+                    <tr>
+<td><span class="new_code">setFrameFocusByIndex($choice)</span></td>
+<td>Select a frame numbered from 1</td>
+</tr>
+                    <tr>
+<td><span class="new_code">setFrameFocus($name)</span></td>
+<td>Select frame by label</td>
+</tr>
+                    <tr>
+<td><span class="new_code">clearFrameFocus()</span></td>
+<td>Treat all the frames as a single page</td>
+</tr>
+                </tbody></table>
+                When focused on a single frame, the content will come from
+                that frame only.
+                This includes links to click and forms to submit.
+            </p>
+        
+        <h2>
+<a class="target" name="debug"></a>What went wrong?</h2>
+            <p>
+                All of this functionality is great when we actually manage to fetch pages,
+                but that doesn't always happen.
+                To help figure out what went wrong, the browser has some methods to
+                aid in debugging...
+                <table><tbody>
+                    <tr>
+<td><span class="new_code">setConnectionTimeout($timeout)</span></td>
+<td>Close the socket on overrun</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getUrl()</span></td>
+<td>Url of most recent page fetched</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getRequest()</span></td>
+<td>Raw request header of page or frame</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getHeaders()</span></td>
+<td>Raw response header of page or frame</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getTransportError()</span></td>
+<td>Any socket level errors in the last fetch</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getResponseCode()</span></td>
+<td>HTTP response of page or frame</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getMimeType()</span></td>
+<td>Mime type of page or frame</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getAuthentication()</span></td>
+<td>Authentication type in 401 challenge header</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getRealm()</span></td>
+<td>Authentication realm in 401 challenge header</td>
+</tr>
+                    <tr>
+<td><span class="new_code">getBaseUrl()</span></td>
+<td>Base url only of most recent page fetched</td>
+</tr>
+                    <tr>
+<td><span class="new_code">setMaximumRedirects($max)</span></td>
+<td>Number of redirects before page is loaded anyway</td>
+</tr>
+                    <tr>
+<td><span class="new_code">setMaximumNestedFrames($max)</span></td>
+<td>Protection against recursive framesets</td>
+</tr>
+                    <tr>
+<td><span class="new_code">ignoreFrames()</span></td>
+<td>Disables frames support</td>
+</tr>
+                    <tr>
+<td><span class="new_code">useFrames()</span></td>
+<td>Enables frames support</td>
+</tr>
+                    <tr>
+<td><span class="new_code">ignoreCookies()</span></td>
+<td>Disables sending and receiving of cookies</td>
+</tr>
+                    <tr>
+<td><span class="new_code">useCookies()</span></td>
+<td>Enables cookie support</td>
+</tr>
+                </tbody></table>
+                The methods <span class="new_code">SimpleBrowser::setConnectionTimeout()</span>
+                <span class="new_code">SimpleBrowser::setMaximumRedirects()</span>,
+                <span class="new_code">SimpleBrowser::setMaximumNestedFrames()</span>,
+                <span class="new_code">SimpleBrowser::ignoreFrames()</span>,
+                <span class="new_code">SimpleBrowser::useFrames()</span>,
+                <span class="new_code">SimpleBrowser::ignoreCookies()</span> and
+                <span class="new_code">SimpleBrowser::useCokies()</span> continue to apply
+                to every subsequent request.
+                The other methods are frames aware.
+                This means that if you have an individual frame that is not
+                loading, navigate to it using <span class="new_code">SimpleBrowser::setFrameFocus()</span>
+                and you can then use <span class="new_code">SimpleBrowser::getRequest()</span>, etc to
+                see what happened.
+            </p>
+        
+        <h2>
+<a class="target" name="unit"></a>Complex unit tests with multiple browsers</h2>
+            <p>
+                Anything that could be done in a
+                <a href="web_tester_documentation.html">WebTestCase</a> can
+                now be done in a <a href="unit_tester_documentation.html">UnitTestCase</a>.
+                This means that we could freely mix domain object testing with the
+                web interface...
+<pre>
+<strong>class TestOfRegistration extends UnitTestCase {
+    function testNewUserAddedToAuthenticator() {</strong>
+        $browser = new SimpleBrowser();
+        $browser-&gt;get('http://my-site.com/register.php');
+        $browser-&gt;setField('email', 'me@here');
+        $browser-&gt;setField('password', 'Secret');
+        $browser-&gt;click('Register');
+        <strong>
+        $authenticator = new Authenticator();
+        $member = $authenticator-&gt;findByEmail('me@here');
+        $this-&gt;assertEqual($member-&gt;getPassword(), 'Secret');
+    }
+}</strong>
+</pre>
+                While this may be a useful temporary expediency, I am not a fan
+                of this type of testing.
+                The testing has cut across application layers, make it twice as
+                likely it will need refactoring when the code changes.
+            </p>
+            <p>
+                A more useful case of where using the browser directly can be helpful
+                is where the <span class="new_code">WebTestCase</span> cannot cope.
+                An example is where two browsers are needed at the same time.
+            </p>
+            <p>
+                For example, say we want to disallow multiple simultaneous
+                usage of a site with the same username.
+                This test case will do the job...
+<pre>
+class TestOfSecurity extends UnitTestCase {
+    function testNoMultipleLoginsFromSameUser() {<strong>
+        $first_attempt = new SimpleBrowser();
+        $first_attempt-&gt;get('http://my-site.com/login.php');
+        $first_attempt-&gt;setField('name', 'Me');
+        $first_attempt-&gt;setField('password', 'Secret');
+        $first_attempt-&gt;click('Enter');
+        $this-&gt;assertEqual($first_attempt-&gt;getTitle(), 'Welcome');
+        
+        $second_attempt = new SimpleBrowser();
+        $second_attempt-&gt;get('http://my-site.com/login.php');
+        $second_attempt-&gt;setField('name', 'Me');
+        $second_attempt-&gt;setField('password', 'Secret');
+        $second_attempt-&gt;click('Enter');
+        $this-&gt;assertEqual($second_attempt-&gt;getTitle(), 'Access Denied');</strong>
+    }
+}
+</pre>
+                You can also use the <span class="new_code">SimpleBrowser</span> class
+                directly when you want to write test cases using a different
+                test tool than SimpleTest, such as PHPUnit.
+            </p>
+        
+    </div>
+        References and related information...
+        <ul>
+<li>
+            SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
+        </li>
+<li>
+            SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
+        </li>
+<li>
+            The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
+            gives full detail on the classes and assertions available.
+        </li>
+</ul>
+<div class="menu_back"><div class="menu">
+<a href="index.html">SimpleTest</a>
+                |
+                <a href="overview.html">Overview</a>
+                |
+                <a href="unit_test_documentation.html">Unit tester</a>
+                |
+                <a href="group_test_documentation.html">Group tests</a>
+                |
+                <a href="mock_objects_documentation.html">Mock objects</a>
+                |
+                <a href="partial_mocks_documentation.html">Partial mocks</a>
+                |
+                <a href="reporter_documentation.html">Reporting</a>
+                |
+                <a href="expectation_documentation.html">Expectations</a>
+                |
+                <a href="web_tester_documentation.html">Web tester</a>
+                |
+                <a href="form_testing_documentation.html">Testing forms</a>
+                |
+                <a href="authentication_documentation.html">Authentication</a>
+                |
+                <span class="chosen">Scriptable browser</span>
+</div></div>
+<div class="copyright">
+            Copyright<br>Marcus Baker 2006
+        </div>
+</body>
+</html>
diff --git a/lib/simpletestlib/docs/en/docs.css b/lib/simpletestlib/docs/en/docs.css
new file mode 100755 (executable)
index 0000000..18368a0
--- /dev/null
@@ -0,0 +1,121 @@
+body {
+    padding-left: 3%;
+    padding-right: 3%;
+}
+h1, h2, h3 {
+       font-family: sans-serif;
+}
+h1 {
+    text-align: center;
+}
+pre {
+    font-family: "courier new", courier, typewriter, monospace;
+    font-size: 90%;
+    border: 1px solid;
+       border-color: #999966;
+    background-color: #ffffcc;
+    padding: 5px;
+    margin-left: 20px;
+    margin-right: 40px;
+}
+.code, .new_code, pre.new_code {
+    font-family: "courier new", courier, typewriter, monospace;
+    font-weight: bold;
+}
+div.copyright {
+    font-size: 80%;
+    color: gray;
+}
+div.copyright a {
+    margin-top: 1em;
+    color: gray;
+}
+ul.api {
+    border: 2px outset;
+    border-color: gray;
+    background-color: white;
+    margin: 5px;
+    margin-left: 5%;
+    margin-right: 5%;
+}
+ul.api li {
+    margin-top: 0.2em;
+    margin-bottom: 0.2em;
+    list-style: none;
+    text-indent: -3em;
+    padding-left: 1em;
+}
+div.demo {
+    border: 4px ridge;
+    border-color: gray;
+    padding: 10px;
+    margin: 5px;
+    margin-left: 20px;
+    margin-right: 40px;
+    background-color: white;
+}
+div.demo span.fail {
+    color: red;
+}
+div.demo span.pass {
+    color: green;
+}
+div.demo h1 {
+    font-size: 12pt;
+    text-align: left;
+    font-weight: bold;
+}
+div.menu {
+    text-align: center;
+}
+table {
+    border: 2px outset;
+    border-color: gray;
+    background-color: white;
+    margin: 5px;
+    margin-left: 5%;
+    margin-right: 5%;
+}
+td {
+    font-size: 90%;
+}
+.shell {
+    color: white;
+}
+pre.shell {
+    border: 4px ridge;
+    border-color: gray;
+    padding: 10px;
+    margin: 5px;
+    margin-left: 20px;
+    margin-right: 40px;
+    background-color: #000100;
+       color: #99ff99;
+    font-size: 90%;
+}
+pre.file {
+    color: black;
+    border: 1px solid;
+    border-color: black;
+    padding: 10px;
+    margin: 5px;
+    margin-left: 20px;
+    margin-right: 40px;
+    background-color: white;
+    font-size: 90%;
+}
+form.demo {
+    background-color: lightgray;
+    border: 4px outset;
+    border-color: lightgray;
+    padding: 10px;
+    margin-right: 40%;
+}
+dl, dd {
+       margin:  10px;
+       margin-left:  30px;
+}
+em {
+    font-weight: bold;
+    font-family: "courier new", courier, typewriter, monospace;
+}
diff --git a/lib/simpletestlib/docs/en/expectation_documentation.html b/lib/simpletestlib/docs/en/expectation_documentation.html
new file mode 100644 (file)
index 0000000..26704d5
--- /dev/null
@@ -0,0 +1,476 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>
+        Extending the SimpleTest unit tester with additional expectation classes
+    </title>
+<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
+</head>
+<body>
+<div class="menu_back"><div class="menu">
+<a href="index.html">SimpleTest</a>
+                |
+                <a href="overview.html">Overview</a>
+                |
+                <a href="unit_test_documentation.html">Unit tester</a>
+                |
+                <a href="group_test_documentation.html">Group tests</a>
+                |
+                <a href="mock_objects_documentation.html">Mock objects</a>
+                |
+                <a href="partial_mocks_documentation.html">Partial mocks</a>
+                |
+                <a href="reporter_documentation.html">Reporting</a>
+                |
+                <span class="chosen">Expectations</span>
+                |
+                <a href="web_tester_documentation.html">Web tester</a>
+                |
+                <a href="form_testing_documentation.html">Testing forms</a>
+                |
+                <a href="authentication_documentation.html">Authentication</a>
+                |
+                <a href="browser_documentation.html">Scriptable browser</a>
+</div></div>
+<h1>Expectation documentation</h1>
+        This page...
+        <ul>
+<li>
+            Using expectations for
+            <a href="#mock">more precise testing with mock objects</a>
+        </li>
+<li>
+            <a href="#behaviour">Changing mock object behaviour</a> with expectations
+        </li>
+<li>
+            <a href="#extending">Extending the expectations</a>
+        </li>
+<li>
+            Underneath SimpleTest <a href="#unit">uses expectation classes</a>
+        </li>
+</ul>
+<div class="content">
+        <h2>
+<a class="target" name="mock"></a>More control over mock objects</h2>
+            <p>
+                The default behaviour of the
+                <a href="mock_objects_documentation.html">mock objects</a>
+                in
+                <a href="http://sourceforge.net/projects/simpletest/">SimpleTest</a>
+                is either an identical match on the argument or to allow any argument at all.
+                For almost all tests this is sufficient.
+                Sometimes, though, you want to weaken a test case.
+            </p>
+            <p>
+                One place where a test can be too tightly coupled is with
+                text matching.
+                Suppose we have a component that outputs a helpful error
+                message when something goes wrong.
+                You want to test that the correct error was sent, but the actual
+                text may be rather long.
+                If you test for the text exactly, then every time the exact wording
+                of the message changes, you will have to go back and edit the test suite.
+            </p>
+            <p>
+                For example, suppose we have a news service that has failed
+                to connect to its remote source.
+<pre>
+<strong>class NewsService {
+    ...
+    function publish($writer) {
+        if (! $this-&gt;isConnected()) {
+            $writer-&gt;write('Cannot connect to news service "' .
+                    $this-&gt;_name . '" at this time. ' .
+                    'Please try again later.');
+        }
+        ...
+    }
+}</strong>
+</pre>
+                Here it is sending its content to a
+                <span class="new_code">Writer</span> class.
+                We could test this behaviour with a
+                <span class="new_code">MockWriter</span> like so...
+<pre>
+class TestOfNewsService extends UnitTestCase {
+    ...
+    function testConnectionFailure() {<strong>
+        $writer = new MockWriter();
+        $writer-&gt;expectOnce('write', array(
+                'Cannot connect to news service ' .
+                '"BBC News" at this time. ' .
+                'Please try again later.'));
+        
+        $service = new NewsService('BBC News');
+        $service-&gt;publish($writer);</strong>
+    }
+}
+</pre>
+                This is a good example of a brittle test.
+                If we decide to add additional instructions, such as
+                suggesting an alternative news source, we will break
+                our tests even though no underlying functionality
+                has been altered.
+            </p>
+            <p>
+                To get around this, we would like to do a regular expression
+                test rather than an exact match.
+                We can actually do this with...
+<pre>
+class TestOfNewsService extends UnitTestCase {
+    ...
+    function testConnectionFailure() {
+        $writer = new MockWriter();<strong>
+        $writer-&gt;expectOnce(
+                'write',
+                array(new PatternExpectation('/cannot connect/i')));</strong>
+        
+        $service = new NewsService('BBC News');
+        $service-&gt;publish($writer);
+    }
+}
+</pre>
+                Instead of passing in the expected parameter to the
+                <span class="new_code">MockWriter</span> we pass an
+                expectation class called
+                <span class="new_code">PatternExpectation</span>.
+                The mock object is smart enough to recognise this as special
+                and to treat it differently.
+                Rather than simply comparing the incoming argument to this
+                object, it uses the expectation object itself to
+                perform the test.
+            </p>
+            <p>
+                The <span class="new_code">PatternExpectation</span> takes
+                the regular expression to match in its constructor.
+                Whenever a comparison is made by the <span class="new_code">MockWriter</span>
+                against this expectation class, it will do a
+                <span class="new_code">preg_match()</span> with this pattern.
+                With our test case above, as long as "cannot connect"
+                appears in the text of the string, the mock will issue a pass
+                to the unit tester.
+                The rest of the text does not matter.
+            </p>
+            <p>
+                The possible expectation classes are...
+                <table><tbody>
+                    <tr>
+<td><span class="new_code">AnythingExpectation</span></td>
+<td>Will always match</td>
+</tr>
+                    <tr>
+<td><span class="new_code">EqualExpectation</span></td>
+<td>An equality, rather than the stronger identity comparison</td>
+</tr>
+                    <tr>
+<td><span class="new_code">NotEqualExpectation</span></td>
+<td>An inequality comparison</td>
+</tr>
+                    <tr>
+<td><span class="new_code">IndenticalExpectation</span></td>
+<td>The default mock object check which must match exactly</td>
+</tr>
+                    <tr>
+<td><span class="new_code">NotIndenticalExpectation</span></td>
+<td>Inverts the mock object logic</td>
+</tr>
+                    <tr>
+<td><span class="new_code">WithinMarginExpectation</span></td>
+<td>Compares a value to within a margin</td>
+</tr>
+                    <tr>
+<td><span class="new_code">OutsideMarginExpectation</span></td>
+<td>Checks that a value is out side the margin</td>
+</tr>
+                    <tr>
+<td><span class="new_code">PatternExpectation</span></td>
+<td>Uses a Perl Regex to match a string</td>
+</tr>
+                    <tr>
+<td><span class="new_code">NoPatternExpectation</span></td>
+<td>Passes only if failing a Perl Regex</td>
+</tr>
+                    <tr>
+<td><span class="new_code">IsAExpectation</span></td>
+<td>Checks the type or class name only</td>
+</tr>
+                    <tr>
+<td><span class="new_code">NotAExpectation</span></td>
+<td>Opposite of the <span class="new_code">IsAExpectation</span>
+</td>
+</tr>
+                    <tr>
+<td><span class="new_code">MethodExistsExpectation</span></td>
+<td>Checks a method is available on an object</td>
+</tr>
+                    <tr>
+<td><span class="new_code">TrueExpectation</span></td>
+<td>Accepts any PHP variable that evaluates to true</td>
+</tr>
+                    <tr>
+<td><span class="new_code">FalseExpectation</span></td>
+<td>Accepts any PHP variable that evaluates to false</td>
+</tr>
+                </tbody></table>
+                Most take the expected value in the constructor.
+                The exceptions are the pattern matchers, which take a regular expression,
+                and the <span class="new_code">IsAExpectation</span> and <span class="new_code">NotAExpectation</span> which takes a type
+                or class name as a string.
+            </p>
+            <p>
+                Some examples...
+            </p>
+            <p>
+<pre>
+$mock-&gt;expectOnce('method', array(new IdenticalExpectation(14)));
+</pre>
+                This is the same as <span class="new_code">$mock-&gt;expectOnce('method', array(14))</span>.
+<pre>
+$mock-&gt;expectOnce('method', array(new EqualExpectation(14)));
+</pre>
+                This is different from the previous version in that the string
+                <span class="new_code">"14"</span> as a parameter will also pass.
+                Sometimes the additional type checks of SimpleTest are too restrictive.
+<pre>
+$mock-&gt;expectOnce('method', array(new AnythingExpectation(14)));
+</pre>
+                This is the same as <span class="new_code">$mock-&gt;expectOnce('method', array('*'))</span>.
+<pre>
+$mock-&gt;expectOnce('method', array(new IdenticalExpectation('*')));
+</pre>
+                This is handy if you want to assert a literal <span class="new_code">"*"</span>.
+<pre>
+new NotIdenticalExpectation(14)
+</pre>
+                This matches on anything other than integer 14.
+                Even the string <span class="new_code">"14"</span> would pass.
+<pre>
+new WithinMarginExpectation(14.0, 0.001)
+</pre>
+                This will accept any value from 13.999 to 14.001 inclusive.
+            </p>
+        
+        <h2>
+<a class="target" name="behaviour"></a>Using expectations to control stubs</h2>
+            <p>
+                The expectation classes can be used not just for sending assertions
+                from mock objects, but also for selecting behaviour for the
+                <a href="mock_objects_documentation.html">mock objects</a>.
+                Anywhere a list of arguments is given, a list of expectation objects
+                can be inserted instead.
+            </p>
+            <p>
+                Suppose we want a mock authorisation server to simulate a successful login,
+                but only if it receives a valid session object.
+                We can do this as follows...
+<pre>
+Mock::generate('Authorisation');
+<strong>
+$authorisation = new MockAuthorisation();
+$authorisation-&gt;returns(
+        'isAllowed',
+        true,
+        array(new IsAExpectation('Session', 'Must be a session')));
+$authorisation-&gt;returns('isAllowed', false);</strong>
+</pre>
+                We have set the default mock behaviour to return false when
+                <span class="new_code">isAllowed</span> is called.
+                When we call the method with a single parameter that
+                is a <span class="new_code">Session</span> object, it will return true.
+                We have also added a second parameter as a message.
+                This will be displayed as part of the mock object
+                failure message if this expectation is the cause of
+                a failure.
+            </p>
+            <p>
+                This kind of sophistication is rarely useful, but is included for
+                completeness.
+            </p>
+        
+        <h2>
+<a class="target" name="extending"></a>Creating your own expectations</h2>
+            <p>
+                The expectation classes have a very simple structure.
+                So simple that it is easy to create your own versions for
+                commonly used test logic.
+            </p>
+            <p>
+                As an example here is the creation of a class to test for
+                valid IP addresses.
+                In order to work correctly with the stubs and mocks the new
+                expectation class should extend
+                <span class="new_code">SimpleExpectation</span> or further extend a subclass...
+<pre>
+<strong>class ValidIp extends SimpleExpectation {
+    
+    function test($ip) {
+        return (ip2long($ip) != -1);
+    }
+    
+    function testMessage($ip) {
+        return "Address [$ip] should be a valid IP address";
+    }
+}</strong>
+</pre>
+                There are only two methods to implement.
+                The <span class="new_code">test()</span> method should
+                evaluate to true if the expectation is to pass, and
+                false otherwise.
+                The <span class="new_code">testMessage()</span> method
+                should simply return some helpful text explaining the test
+                that was carried out.
+            </p>
+            <p>
+                This class can now be used in place of the earlier expectation
+                classes.
+            </p>
+            <p>
+                Here is a more typical example, matching part of a hash...
+<pre>
+<strong>class JustField extends EqualExpectation {
+    private $key;
+    
+    function __construct($key, $expected) {
+        parent::__construct($expected);
+        $this-&gt;key = $key;
+    }
+    
+    function test($compare) {
+        if (! isset($compare[$this-&gt;key])) {
+            return false;
+        }
+        return parent::test($compare[$this-&gt;key]);
+    }
+    
+    function testMessage($compare) {
+        if (! isset($compare[$this-&gt;key])) {
+            return 'Key [' . $this-&gt;key . '] does not exist';
+        }
+        return 'Key [' . $this-&gt;key . '] -&gt; ' .
+                parent::testMessage($compare[$this-&gt;key]);
+    }
+}</strong>
+</pre>
+                We tend to seperate message clauses with
+                "&amp;nbsp;-&gt;&amp;nbsp;".
+                This allows derivative tools to reformat the output.
+            </p>
+            <p>
+                Suppose some authenticator is expecting to be given
+                a database row corresponding to the user, and we
+                only need to confirm the username is correct.
+                We can assert just their username with...
+<pre>
+$mock-&gt;expectOnce('authenticate',
+                  array(new JustKey('username', 'marcus')));
+</pre>
+            </p>
+        
+        <h2>
+<a class="target" name="unit"></a>Under the bonnet of the unit tester</h2>
+            <p>
+                The <a href="http://sourceforge.net/projects/simpletest/">SimpleTest unit testing framework</a>
+                also uses the expectation classes internally for the
+                <a href="unit_test_documentation.html">UnitTestCase class</a>.
+                We can also take advantage of these mechanisms to reuse our
+                homebrew expectation classes within the test suites directly.
+            </p>
+            <p>
+                The most crude way of doing this is to use the generic
+                <span class="new_code">SimpleTest::assert()</span> method to
+                test against it directly...
+<pre>
+<strong>class TestOfNetworking extends UnitTestCase {
+    ...
+    function testGetValidIp() {
+        $server = &amp;new Server();
+        $this-&gt;assert(
+                new ValidIp(),
+                $server-&gt;getIp(),
+                'Server IP address-&gt;%s');
+    }
+}</strong>
+</pre>
+                <span class="new_code">assert()</span> will test any expectation class directly.
+            </p>
+            <p>
+                This is a little untidy compared with our usual
+                <span class="new_code">assert...()</span> syntax.
+            </p>
+            <p>
+                For such a simple case we would normally create a
+                separate assertion method on our test case rather
+                than bother using the expectation class.
+                If we pretend that our expectation is a little more
+                complicated for a moment, so that we want to reuse it,
+                we get...
+<pre>
+class TestOfNetworking extends UnitTestCase {
+    ...<strong>
+    function assertValidIp($ip, $message = '%s') {
+        $this-&gt;assert(new ValidIp(), $ip, $message);
+    }</strong>
+    
+    function testGetValidIp() {
+        $server = &amp;new Server();<strong>
+        $this-&gt;assertValidIp(
+                $server-&gt;getIp(),
+                'Server IP address-&gt;%s');</strong>
+    }
+}
+</pre>
+                It is rare to need the expectations for more than pattern
+                matching, but these facilities do allow testers to build
+                some sort of domain language for testing their application.
+                Also, complex expectation classes could make the tests
+                harder to read and debug.
+                In effect extending the test framework to create their own tool set.
+            </p>
+        
+    </div>
+        References and related information...
+        <ul>
+<li>
+            SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
+        </li>
+<li>
+            SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
+        </li>
+<li>
+            The expectations mimic the constraints in <a href="http://www.jmock.org/">JMock</a>.
+        </li>
+<li>
+            <a href="http://simpletest.org/api/">Full API for SimpleTest</a>
+            from the PHPDoc.
+        </li>
+</ul>
+<div class="menu_back"><div class="menu">
+<a href="index.html">SimpleTest</a>
+                |
+                <a href="overview.html">Overview</a>
+                |
+                <a href="unit_test_documentation.html">Unit tester</a>
+                |
+                <a href="group_test_documentation.html">Group tests</a>
+                |
+                <a href="mock_objects_documentation.html">Mock objects</a>
+                |
+                <a href="partial_mocks_documentation.html">Partial mocks</a>
+                |
+                <a href="reporter_documentation.html">Reporting</a>
+                |
+                <span class="chosen">Expectations</span>
+                |
+                <a href="web_tester_documentation.html">Web tester</a>
+                |
+                <a href="form_testing_documentation.html">Testing forms</a>
+                |
+                <a href="authentication_documentation.html">Authentication</a>
+                |
+                <a href="browser_documentation.html">Scriptable browser</a>
+</div></div>
+<div class="copyright">
+            Copyright<br>Marcus Baker 2006
+        </div>
+</body>
+</html>
diff --git a/lib/simpletestlib/docs/en/form_testing_documentation.html b/lib/simpletestlib/docs/en/form_testing_documentation.html
new file mode 100644 (file)
index 0000000..328735c
--- /dev/null
@@ -0,0 +1,351 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>SimpleTest documentation for testing HTML forms</title>
+<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
+</head>
+<body>
+<div class="menu_back"><div class="menu">
+<a href="index.html">SimpleTest</a>
+                |
+                <a href="overview.html">Overview</a>
+                |
+                <a href="unit_test_documentation.html">Unit tester</a>
+                |
+                <a href="group_test_documentation.html">Group tests</a>
+                |
+                <a href="mock_objects_documentation.html">Mock objects</a>
+                |
+                <a href="partial_mocks_documentation.html">Partial mocks</a>
+                |
+                <a href="reporter_documentation.html">Reporting</a>
+                |
+                <a href="expectation_documentation.html">Expectations</a>
+                |
+                <a href="web_tester_documentation.html">Web tester</a>
+                |
+                <span class="chosen">Testing forms</span>
+                |
+                <a href="authentication_documentation.html">Authentication</a>
+                |
+                <a href="browser_documentation.html">Scriptable browser</a>
+</div></div>
+<h1>Form testing documentation</h1>
+        This page...
+        <ul>
+<li>
+            Changing form values and successfully
+            <a href="#submit">Submitting a simple form</a>
+        </li>
+<li>
+            Handling <a href="#multiple">widgets with multiple values</a>
+            by setting lists.
+        </li>
+<li>
+            Bypassing javascript to <a href="#hidden-field">set a hidden field</a>.
+        </li>
+<li>
+            <a href="#raw">Raw posting</a> when you don't have a button
+            to click.
+        </li>
+</ul>
+<div class="content">
+        <h2>
+<a class="target" name="submit"></a>Submitting a simple form</h2>
+            <p>
+                When a page is fetched by the <span class="new_code">WebTestCase</span>
+                using <span class="new_code">get()</span> or
+                <span class="new_code">post()</span> the page content is
+                automatically parsed.
+                This results in any form controls that are inside &lt;form&gt; tags
+                being available from within the test case.
+                For example, if we have this snippet of HTML...
+<pre>
+&lt;form&gt;
+    &lt;input type="text" name="a" value="A default" /&gt;
+    &lt;input type="submit" value="Go" /&gt;
+&lt;/form&gt;
+</pre>
+                Which looks like this...
+            </p>
+            <p>
+                <form class="demo">
+                    <input type="text" name="a" value="A default">
+                    <input type="submit" value="Go">
+                </form>
+            </p>
+            <p>
+                We can navigate to this code, via the
+                <a href="http://www.lastcraft.com/form_testing_documentation.php">LastCraft</a>
+                site, with the following test...
+<pre>
+class SimpleFormTests extends WebTestCase {<strong>
+    function testDefaultValue() {
+        $this-&gt;get('http://www.lastcraft.com/form_testing_documentation.php');
+        $this-&gt;assertField('a', 'A default');
+    }</strong>
+}
+</pre>
+                Immediately after loading the page all of the HTML controls are set at
+                their default values just as they would appear in the web browser.
+                The assertion tests that a HTML widget exists in the page with the
+                name "a" and that it is currently set to the value
+                "A default".
+                As usual, we could use a pattern expectation instead of a fixed
+                string.
+<pre>
+class SimpleFormTests extends WebTestCase {
+    function testDefaultValue() {
+        $this-&gt;get('http://www.lastcraft.com/form_testing_documentation.php');
+        $this-&gt;assertField('a', <strong>new PatternExpectation('/default/')</strong>);
+    }
+}
+</pre>
+                We could submit the form straight away, but first we'll change
+                the value of the text field and only then submit it...
+<pre>
+class SimpleFormTests extends WebTestCase {
+    function testDefaultValue() {
+        $this-&gt;get('http://www.my-site.com/');
+        $this-&gt;assertField('a', 'A default');<strong>
+        $this-&gt;setField('a', 'New value');
+        $this-&gt;click('Go');</strong>
+    }
+}
+</pre>
+                Because we didn't specify a method attribute on the form tag, and
+                didn't specify an action either, the test case will follow
+                the usual browser behaviour of submitting the form data as a <em>GET</em>
+                request back to the same location.
+                In general SimpleTest tries to emulate typical browser behaviour as much as possible,
+                rather than attempting to catch any form of HTML omission.
+                This is because the target of the testing framework is the PHP application
+                logic, not syntax or other errors in the HTML code.
+                For HTML errors, other tools such as
+                <a href="http://www.w3.org/People/Raggett/tidy/">HTMLTidy</a> should be used,
+                or any of the HTML and CSS validators already out there.
+            </p>
+            <p>
+                If a field is not present in any form, or if an option is unavailable,
+                then <span class="new_code">WebTestCase::setField()</span> will return
+                <span class="new_code">false</span>.
+                For example, suppose we wish to verify that a "Superuser"
+                option is not present in this form...
+<pre>
+&lt;strong&gt;Select type of user to add:&lt;/strong&gt;
+&lt;select name="type"&gt;
+    &lt;option&gt;Subscriber&lt;/option&gt;
+    &lt;option&gt;Author&lt;/option&gt;
+    &lt;option&gt;Administrator&lt;/option&gt;
+&lt;/select&gt;
+</pre>
+                Which looks like...
+            </p>
+            <p>
+                <form class="demo">
+                    <strong>Select type of user to add:</strong>
+                    <select name="type">
+                        <option>Subscriber</option>
+                        <option>Author</option>
+                        <option>Administrator</option>
+                    </select>
+                </form>
+            </p>
+            <p>
+                The following test will confirm it...
+<pre>
+class SimpleFormTests extends WebTestCase {
+    ...
+    function testNoSuperuserChoiceAvailable() {<strong>
+        $this-&gt;get('http://www.lastcraft.com/form_testing_documentation.php');
+        $this-&gt;assertFalse($this-&gt;setField('type', 'Superuser'));</strong>
+    }
+}
+</pre>
+                The current selection will not be changed if the new value is not an option.
+            </p>
+            <p>
+                Here is the full list of widgets currently supported...
+                <ul>
+                    <li>Text fields, including hidden and password fields.</li>
+                    <li>Submit buttons including the button tag, although not yet reset buttons</li>
+                    <li>Text area. This includes text wrapping behaviour.</li>
+                    <li>Checkboxes, including multiple checkboxes in the same form.</li>
+                    <li>Drop down selections, including multiple selects.</li>
+                    <li>Radio buttons.</li>
+                    <li>Images.</li>
+                </ul>
+            </p>
+            <p>
+                The browser emulation offered by SimpleTest mimics
+                the actions which can be perform by a user on a
+                standard HTML page. Javascript is not supported, and
+                it's unlikely that support will be added any time
+                soon.
+            </p>
+            <p>
+                Of particular note is that the Javascript idiom of
+                passing form results by setting a hidden field cannot
+                be performed using the normal SimpleTest
+                commands. See below for a way to test such forms.
+            </p>
+        
+        <h2>
+<a class="target" name="multiple"></a>Fields with multiple values</h2>
+            <p>
+                SimpleTest can cope with two types of multivalue controls: Multiple
+                selection drop downs, and multiple checkboxes with the same name
+                within a form.
+                The multivalue nature of these means that setting and testing
+                are slightly different.
+                Using checkboxes as an example...
+<pre>
+&lt;form class="demo"&gt;
+    &lt;strong&gt;Create privileges allowed:&lt;/strong&gt;
+    &lt;input type="checkbox" name="crud" value="c" checked&gt;&lt;br&gt;
+    &lt;strong&gt;Retrieve privileges allowed:&lt;/strong&gt;
+    &lt;input type="checkbox" name="crud" value="r" checked&gt;&lt;br&gt;
+    &lt;strong&gt;Update privileges allowed:&lt;/strong&gt;
+    &lt;input type="checkbox" name="crud" value="u" checked&gt;&lt;br&gt;
+    &lt;strong&gt;Destroy privileges allowed:&lt;/strong&gt;
+    &lt;input type="checkbox" name="crud" value="d" checked&gt;&lt;br&gt;
+    &lt;input type="submit" value="Enable Privileges"&gt;
+&lt;/form&gt;
+</pre>
+                Which renders as...
+            </p>
+            <p>
+                <form class="demo">
+                    <strong>Create privileges allowed:</strong>
+                    <input type="checkbox" name="crud" value="c" checked><br>
+                    <strong>Retrieve privileges allowed:</strong>
+                    <input type="checkbox" name="crud" value="r" checked><br>
+                    <strong>Update privileges allowed:</strong>
+                    <input type="checkbox" name="crud" value="u" checked><br>
+                    <strong>Destroy privileges allowed:</strong>
+                    <input type="checkbox" name="crud" value="d" checked><br>
+                    <input type="submit" value="Enable Privileges">
+                </form>
+            </p>
+            <p>
+                If we wish to disable all but the retrieval privileges and
+                submit this information we can do it like this...
+<pre>
+class SimpleFormTests extends WebTestCase {
+    ...<strong>
+    function testDisableNastyPrivileges() {
+        $this-&gt;get('http://www.lastcraft.com/form_testing_documentation.php');
+        $this-&gt;assertField('crud', array('c', 'r', 'u', 'd'));
+        $this-&gt;setField('crud', array('r'));
+        $this-&gt;click('Enable Privileges');
+    }</strong>
+}
+</pre>
+                Instead of setting the field to a single value, we give it a list
+                of values.
+                We do the same when testing expected values.
+                We can then write other test code to confirm the effect of this, perhaps
+                by logging in as that user and attempting an update.
+            </p>
+        
+        <h2>
+<a class="target" name="hidden-field"></a>Forms which use javascript to set a hidden field</h2>
+            <p>
+                If you want to test a form which relies on javascript to set a hidden
+                field, you can't just call setField().
+                The following code will <em>not</em> work:
+<pre>
+class SimpleFormTests extends WebTestCase {
+    function testEmulateMyJavascriptForm() {
+        <strong>// This does *not* work</strong>
+        $this-&gt;setField('a_hidden_field', '123');
+        $this-&gt;clickSubmit('OK');
+    }
+}
+</pre>
+                Instead, you need to pass the additional form parameters to the
+                clickSubmit() method:
+<pre>
+class SimpleFormTests extends WebTestCase {
+    function testMyJavascriptForm() {
+        <strong>$this-&gt;clickSubmit('OK', array('a_hidden_field'=&gt;'123'));</strong>
+    }
+
+}
+</pre>
+                Bear in mind that in doing this you're effectively stubbing out a
+                part of your software (the javascript code in the form), and
+                perhaps you might be better off using something like 
+                <a href="http://selenium.openqa.org/">Selenium</a> to ensure a complete
+                test.
+            </p>
+        
+        <h2>
+<a class="target" name="raw"></a>Raw posting</h2>
+            <p>
+                If you want to test a form handler, but have not yet written
+                or do not have access to the form itself, you can create a
+                form submission by hand.
+<pre>
+class SimpleFormTests extends WebTestCase {
+    ...<strong>    
+    function testAttemptedHack() {
+        $this-&gt;post(
+                'http://www.my-site.com/add_user.php',
+                array('type' =&gt; 'superuser'));
+        $this-&gt;assertNoText('user created');
+    }</strong>
+}
+</pre>
+                By adding data to the <span class="new_code">WebTestCase::post()</span>
+                method, we are emulating a form submission.
+                You would normally only do this as a temporary expedient, or where
+                you are expecting a 3rd party to submit to a form.
+                The exception is when you want tests to protect you from
+                attempts to spoof your pages.
+            </p>
+        
+    </div>
+        References and related information...
+        <ul>
+<li>
+            SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
+        </li>
+<li>
+            SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
+        </li>
+<li>
+            The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
+            gives full detail on the classes and assertions available.
+        </li>
+</ul>
+<div class="menu_back"><div class="menu">
+<a href="index.html">SimpleTest</a>
+                |
+                <a href="overview.html">Overview</a>
+                |
+                <a href="unit_test_documentation.html">Unit tester</a>
+                |
+                <a href="group_test_documentation.html">Group tests</a>
+                |
+                <a href="mock_objects_documentation.html">Mock objects</a>
+                |
+                <a href="partial_mocks_documentation.html">Partial mocks</a>
+                |
+                <a href="reporter_documentation.html">Reporting</a>
+                |
+                <a href="expectation_documentation.html">Expectations</a>
+                |
+                <a href="web_tester_documentation.html">Web tester</a>
+                |
+                <span class="chosen">Testing forms</span>
+                |
+                <a href="authentication_documentation.html">Authentication</a>
+                |
+                <a href="browser_documentation.html">Scriptable browser</a>
+</div></div>
+<div class="copyright">
+            Copyright<br>Marcus Baker 2006
+        </div>
+</body>
+</html>
diff --git a/lib/simpletestlib/docs/en/group_test_documentation.html b/lib/simpletestlib/docs/en/group_test_documentation.html
new file mode 100644 (file)
index 0000000..10f22a2
--- /dev/null
@@ -0,0 +1,252 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>SimpleTest for PHP test suites</title>
+<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
+</head>
+<body>
+<div class="menu_back"><div class="menu">
+<a href="index.html">SimpleTest</a>
+                |
+                <a href="overview.html">Overview</a>
+                |
+                <a href="unit_test_documentation.html">Unit tester</a>
+                |
+                <span class="chosen">Group tests</span>
+                |
+                <a href="mock_objects_documentation.html">Mock objects</a>
+                |
+                <a href="partial_mocks_documentation.html">Partial mocks</a>
+                |
+                <a href="reporter_documentation.html">Reporting</a>
+                |
+                <a href="expectation_documentation.html">Expectations</a>
+                |
+                <a href="web_tester_documentation.html">Web tester</a>
+                |
+                <a href="form_testing_documentation.html">Testing forms</a>
+                |
+                <a href="authentication_documentation.html">Authentication</a>
+                |
+                <a href="browser_documentation.html">Scriptable browser</a>
+</div></div>
+<h1>Test suite documentation</h1>
+        This page...
+        <ul>
+<li>
+            Different ways to <a href="#group">group tests</a> together.
+        </li>
+<li>
+            Combining group tests into <a href="#higher">larger groups</a>.
+        </li>
+</ul>
+<div class="content">
+        <h2>
+<a class="target" name="group"></a>Grouping tests into suites</h2>
+            <p>
+                There are many ways to group tests together into test suites.
+                One way is to simply place multiple test cases into a single file...
+<pre>
+<strong>&lt;?php
+require_once(dirname(__FILE__) . '/simpletest/autorun.php');
+require_once(dirname(__FILE__) . '/../classes/io.php');
+
+class FileTester extends UnitTestCase {
+    ...
+}
+
+class SocketTester extends UnitTestCase {
+    ...
+}
+?&gt;</strong>
+</pre>
+                As many cases as needed can appear in a single file.
+                They should include any code they need, such as the library
+                being tested, but need none of the SimpleTest libraries.
+            </p>
+            <p>
+                Occasionally special subclasses are created that methods useful
+                for testing part of the application.
+                These new base classes are then used in place of <span class="new_code">UnitTestCase</span>
+                or <span class="new_code">WebTestCase</span>.
+                You don't normally want to run these as test cases.
+                Simply mark any base test cases that should not be run as abstract...
+<pre>
+<strong>abstract</strong> class MyFileTestCase extends UnitTestCase {
+    ...
+}
+
+class FileTester extends MyFileTestCase { ... }
+
+class SocketTester extends UnitTestCase { ... }
+</pre>
+                Here the <span class="new_code">FileTester</span> class does
+                not contain any actual tests, but is the base class for other
+                test cases.
+            </p>
+            <p>
+                We will call this sample <em>file_test.php</em>.
+                Currently the test cases are grouped simply by being in the same file.
+                We can build larger constructs just by including other test files in.
+<pre>
+&lt;?php
+require_once('simpletest/autorun.php');
+require_once('file_test.php');
+?&gt;
+</pre>
+                This will work, but create a purely flat hierarchy.
+                INstead we create a test suite file.
+                Our top level test suite can look like this...
+<pre>
+&lt;?php
+require_once('simpletest/autorun.php');
+
+class AllFileTests extends TestSuite {
+    function __construct() {
+        parent::__construct();
+        <strong>$this-&gt;addFile('file_test.php');</strong>
+    }
+}
+?&gt;
+</pre>
+                What happens here is that the <span class="new_code">TestSuite</span>
+                class will do the <span class="new_code">require_once()</span>
+                for us.
+                It then checks to see if any new test case classes
+                have been created by the new file and automatically composes
+                them to the test suite.
+                This method gives us the most control as we just manually add
+                more test files as our test suite grows.
+            </p>
+            <p>
+                If this is too much typing, and you are willing to group
+                test suites together in their own directories or otherwise
+                tag the file names, then there is a more automatic way...
+<pre>
+&lt;?php
+require_once('simpletest/autorun.php');
+
+class AllFileTests extends TestSuite {
+    function __construct() {
+        parent::__construct();
+        $this-&gt;collect(dirname(__FILE__) . '/unit',
+                       new SimplePatternCollector('/_test.php/'));
+    }
+}
+?&gt;
+</pre>
+                This will scan a directory called "unit" for any files
+                ending with "_test.php" and load them.
+                You don't have to use <span class="new_code">SimplePatternCollector</span> to
+                filter by a pattern in the filename, but this is the most common
+                usage.
+            </p>
+            <p>
+                That snippet above is very common in practice.
+                Now all you have to do is drop a file of test cases into the
+                directory and it will run just by running the test suite script.
+            </p>
+            <p>
+                The catch is that you cannot control the order in which the test
+                cases are run.
+                If you want to see lower level components fail first in the test suite,
+                and this will make diagnosis a lot easier, then you should manually
+                call <span class="new_code">addFile()</span> for these.
+                Tests cases are only loaded once, so it's fine to have these included
+                again by a directory scan.
+            </p>
+            <p>
+                Test cases loaded with the <span class="new_code">addFile</span> method have some
+                useful properties.
+                You can guarantee that the constructor is run
+                just before the first test method and the destructor
+                is run just after the last test method.
+                This allows you to place test case wide set up and tear down
+                code in the constructor and destructor, just like a normal
+                class.
+            </p>
+        
+        <h2>
+<a class="target" name="higher"></a>Composite suites</h2>
+            <p>
+                The above method places all of the test cases into one large suite.
+                For larger projects though this may not be flexible enough; you
+                may want to group the tests together in all sorts of ways.
+            </p>
+            <p>
+                Everything we have described so far with test scripts applies to
+                <span class="new_code">TestSuite</span>s as well...
+<pre>
+&lt;?php
+require_once('simpletest/autorun.php');
+<strong>
+class BigTestSuite extends TestSuite {
+    function __construct() {
+        parent::__construct();
+        $this-&gt;addFile('file_tests.php');
+    }
+}</strong>
+?&gt;
+</pre>
+                This effectively adds our test cases and a single suite below
+                the first.
+                When a test fails, we see the breadcrumb trail of the nesting.
+                We can even mix groups and test cases freely as long as
+                we are careful about loops in our includes.
+<pre>
+&lt;?php
+require_once('simpletest/autorun.php');
+
+class BigTestSuite extends TestSuite {
+    function __construct() {
+        parent::__construct();
+        $this-&gt;addFile('file_tests.php');
+        <strong>$this-&gt;addFile('some_other_test.php');</strong>
+    }
+}
+?&gt;
+</pre>
+                Note that in the event of a double include, ony the first instance
+                of the test case will be run.
+            </p>
+        
+    </div>
+        References and related information...
+        <ul>
+<li>
+            SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
+        </li>
+<li>
+            SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
+        </li>
+</ul>
+<div class="menu_back"><div class="menu">
+<a href="index.html">SimpleTest</a>
+                |
+                <a href="overview.html">Overview</a>
+                |
+                <a href="unit_test_documentation.html">Unit tester</a>
+                |
+                <span class="chosen">Group tests</span>
+                |
+                <a href="mock_objects_documentation.html">Mock objects</a>
+                |
+                <a href="partial_mocks_documentation.html">Partial mocks</a>
+                |
+                <a href="reporter_documentation.html">Reporting</a>
+                |
+                <a href="expectation_documentation.html">Expectations</a>
+                |
+                <a href="web_tester_documentation.html">Web tester</a>
+                |
+                <a href="form_testing_documentation.html">Testing forms</a>
+                |
+                <a href="authentication_documentation.html">Authentication</a>
+                |
+                <a href="browser_documentation.html">Scriptable browser</a>
+</div></div>
+<div class="copyright">
+            Copyright<br>Marcus Baker 2006
+        </div>
+</body>
+</html>
diff --git a/lib/simpletestlib/docs/en/index.html b/lib/simpletestlib/docs/en/index.html
new file mode 100644 (file)
index 0000000..9f022d6
--- /dev/null
@@ -0,0 +1,542 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>
+        Download the SimpleTest testing framework -
+        Unit tests and mock objects for PHP
+    </title>
+<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
+</head>
+<body>
+<div class="menu_back"><div class="menu">
+<span class="chosen">SimpleTest</span>
+                |
+                <a href="overview.html">Overview</a>
+                |
+                <a href="unit_test_documentation.html">Unit tester</a>
+                |
+                <a href="group_test_documentation.html">Group tests</a>
+                |
+                <a href="mock_objects_documentation.html">Mock objects</a>
+                |
+                <a href="partial_mocks_documentation.html">Partial mocks</a>
+                |
+                <a href="reporter_documentation.html">Reporting</a>
+                |
+                <a href="expectation_documentation.html">Expectations</a>
+                |
+                <a href="web_tester_documentation.html">Web tester</a>
+                |
+                <a href="form_testing_documentation.html">Testing forms</a>
+                |
+                <a href="authentication_documentation.html">Authentication</a>
+                |
+                <a href="browser_documentation.html">Scriptable browser</a>
+</div></div>
+<h1>SimpleTest for PHP</h1>
+        This page...
+        <ul>
+<li>
+            <a href="#unit">Using unit tester</a>
+            with an example.
+        </li>
+<li>
+            <a href="#group">Grouping tests</a>
+            for testing with one click.
+        </li>
+<li>
+            <a href="#mock">Using mock objects</a>
+            to ease testing and gain tighter control.
+        </li>
+<li>
+            <a href="#web">Testing web pages</a>
+            at the browser level.
+        </li>
+</ul>
+<div class="content">
+        
+        
+            <p>
+                The following assumes that you are familiar with the concept
+                of unit testing as well as the PHP web development language.
+                It is a guide for the impatient new user of
+                <a href="https://sourceforge.net/project/showfiles.php?group_id=76550">SimpleTest</a>.
+                For fuller documentation, especially if you are new
+                to unit testing see the ongoing
+                <a href="unit_test_documentation.html">documentation</a>, and for
+                example test cases see the
+                <a href="http://www.lastcraft.com/first_test_tutorial.php">unit testing tutorial</a>.
+            </p>
+        
+        <h2>
+<a class="target" name="unit"></a>Using the tester quickly</h2>
+            <p>
+                Amongst software testing tools, a unit tester is the one
+                closest to the developer.
+                In the context of agile development the test code sits right
+                next to the source code as both are written simultaneously.
+                In this context SimpleTest aims to be a complete PHP developer
+                test solution and is called "Simple" because it
+                should be easy to use and extend.
+                It wasn't a good choice of name really.
+                It includes all of the typical functions you would expect from
+                <a href="http://www.junit.org/">JUnit</a> and the
+                <a href="http://sourceforge.net/projects/phpunit/">PHPUnit</a>
+                ports, and includes
+                <a href="http://www.mockobjects.com">mock objects</a>.
+            </p>
+            <p>
+                What makes this tool immediately useful to the PHP developer is the internal
+                web browser.
+                This allows tests that navigate web sites, fill in forms and test pages.
+                Being able to write these test in PHP means that it is easy to write
+                integrated tests.
+                An example might be confirming that a user was written to a database
+                after a signing up through the web site.
+            </p>
+            <p>
+                The quickest way to demonstrate SimpleTest is with an example.
+            </p>
+            <p>
+                Let us suppose we are testing a simple file logging class called
+                <span class="new_code">Log</span> in <em>classes/log.php</em>.
+                We start by creating a test script which we will call
+                <em>tests/log_test.php</em> and populate it as follows...
+<pre>
+&lt;?php
+<strong>require_once('simpletest/autorun.php');</strong>
+require_once('../classes/log.php');
+
+class TestOfLogging extends <strong>UnitTestCase</strong> {
+}
+?&gt;
+</pre>
+                Here the <em>simpletest</em> folder is either local or in the path.
+                You would have to edit these locations depending on where you
+                unpacked the toolset.
+                The "autorun.php" file does more than just include the
+                SimpleTest files, it also runs our test for us.
+            </p>
+            <p>
+                The <span class="new_code">TestOfLogging</span> is our first test case and it's
+                currently empty.
+                Each test case is a class that extends one of the SimpleTet base classes
+                and we can have as many of these in the file as we want.
+            </p>
+            <p>
+                With three lines of scaffolding, and our <span class="new_code">Log</span> class
+                include, we have a test suite.
+                No tests though.
+            </p>
+            <p>
+                For our first test, we'll assume that the <span class="new_code">Log</span> class
+                takes the file name to write to in the constructor, and we have
+                a temporary folder in which to place this file...
+<pre>
+&lt;?php
+require_once('simpletest/autorun.php');
+require_once('../classes/log.php');
+
+class TestOfLogging extends UnitTestCase {
+    function <strong>testLogCreatesNewFileOnFirstMessage()</strong> {
+        @unlink('/temp/test.log');
+        $log = new Log('/temp/test.log');
+        <strong>$this-&gt;assertFalse(file_exists('/temp/test.log'));</strong>
+        $log-&gt;message('Should write this to a file');
+        <strong>$this-&gt;assertTrue(file_exists('/temp/test.log'));</strong>
+    }
+}
+?&gt;
+</pre>
+                When a test case runs, it will search for any method that
+                starts with the string "test"
+                and execute that method.
+                If the method starts "test", it's a test.
+                Note the very long name <span class="new_code">testLogCreatesNewFileOnFirstMessage()</span>.
+                This is considered good style and makes the test output more readable.
+            </p>
+            <p>
+                We would normally have more than one test method in a test case,
+                but that's for later.
+            </p>
+            <p>
+                Assertions within the test methods trigger messages to the
+                test framework which displays the result immediately.
+                This immediate response is important, not just in the event
+                of the code causing a crash, but also so that
+                <span class="new_code">print</span> statements can display
+                their debugging content right next to the assertion concerned.
+            </p>
+            <p>
+                To see these results we have to actually run the tests.
+                No other code is necessary - we can just open the page
+                with our browser.
+            </p>
+            <p>
+                On failure the display looks like this...
+                <div class="demo">
+                    <h1>TestOfLogging</h1>
+                    <span class="fail">Fail</span>: testLogCreatesNewFileOnFirstMessage-&gt;True assertion failed.<br>
+                    <div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">1/1 test cases complete.
+                    <strong>1</strong> passes and <strong>1</strong> fails.</div>
+                </div>
+                ...and if it passes like this...
+                <div class="demo">
+                    <h1>TestOfLogging</h1>
+                    <div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
+                    <strong>2</strong> passes and <strong>0</strong> fails.</div>
+                </div>
+                And if you get this...
+                <div class="demo">
+                    <b>Fatal error</b>:  Failed opening required '../classes/log.php' (include_path='') in <b>/home/marcus/projects/lastcraft/tutorial_tests/Log/tests/log_test.php</b> on line <b>7</b>
+                </div>
+                it means you're missing the <em>classes/Log.php</em> file that could look like...
+<pre>
+&lt;?php<strong>
+class Log {
+    function Log($file_path) {
+    }
+
+    function message() {
+    }
+}</strong>
+?&gt;
+</pre>
+                It's fun to write the code after the test.
+                More than fun even -
+                this system is called "Test Driven Development".
+            </p>
+            <p>
+                For more information about <span class="new_code">UnitTestCase</span>, see
+                the <a href="unit_test_documentation.html">unit test documentation</a>.
+            </p>
+        
+        <h2>
+<a class="target" name="group"></a>Building test suites</h2>
+            <p>
+                It is unlikely in a real application that we will only ever run
+                one test case.
+                This means that we need a way of grouping cases into a test
+                script that can, if need be, run every test for the application.
+            </p>
+            <p>
+                Our first step is to create a new file called <em>tests/all_tests.php</em>
+                and insert the following code...
+<pre>
+&lt;?php
+<strong>require_once('simpletest/autorun.php');</strong>
+
+class AllTests extends <strong>TestSuite</strong> {
+    function AllTests() {
+        $this-&gt;TestSuite(<strong>'All tests'</strong>);
+        <strong>$this-&gt;addFile('log_test.php');</strong>
+    }
+}
+?&gt;
+</pre>
+                The "autorun" include allows our upcoming test suite
+                to be run just by invoking this script.
+            </p>
+            <p>
+                The <span class="new_code">TestSuite</span> subclass must chain it's constructor.
+                This limitation will be removed in future versions.
+            </p>
+            <p>
+                The method <span class="new_code">TestSuite::addFile()</span>
+                will include the test case file and read any new classes
+                that are descended from <span class="new_code">SimpleTestCase</span>.
+                <span class="new_code">UnitTestCase</span> is just one example of a class derived from
+                <span class="new_code">SimpleTestCase</span>, and you can create your own.
+                <span class="new_code">TestSuite::addFile()</span> can include other test suites.
+            </p>
+            <p>
+                The class will not be instantiated yet.
+                When the test suite runs it will construct each instance once
+                it reaches that test, then destroy it straight after.
+                This means that the constructor is run just before each run
+                of that test case, and the destructor is run before the next test case starts.
+            </p>
+            <p>
+                It is common to group test case code into superclasses which are not
+                supposed to run, but become the base classes of other tests.
+                For "autorun" to work properly the test case file should not blindly run
+                any other test case extensions that do not actually run tests.
+                This could result in extra test cases being counted during the test
+                run.
+                Hardly a major problem, but to avoid this inconvenience simply mark your
+                base class as <span class="new_code">abstract</span>.
+                SimpleTest won't run abstract classes.
+                If you are still using PHP4, then
+                a <span class="new_code">SimpleTestOptions::ignore()</span> directive
+                somewhere in the test case file will have the same effect.
+            </p>
+            <p>
+                Also, the test case file should not have been included
+                elsewhere or no cases will be added to this group test.
+                This would be a more serious error as if the test case classes are
+                already loaded by PHP the <span class="new_code">TestSuite::addFile()</span>
+                method will not detect them.
+            </p>
+            <p>
+                To display the results it is necessary only to invoke
+                <em>tests/all_tests.php</em> from the web server or the command line.
+            </p>
+            <p>
+                For more information about building test suites,
+                see the <a href="group_test_documentation.html">test suite documentation</a>.
+            </p>
+        
+        <h2>
+<a class="target" name="mock"></a>Using mock objects</h2>
+            <p>
+                Let's move further into the future and do something really complicated.
+            </p>
+            <p>
+                Assume that our logging class is tested and completed.
+                Assume also that we are testing another class that is
+                required to write log messages, say a
+                <span class="new_code">SessionPool</span>.
+                We want to test a method that will probably end up looking
+                like this...
+<pre><strong>
+class SessionPool {
+    ...
+    function logIn($username) {
+        ...
+        $this-&gt;_log-&gt;message("User $username logged in.");
+        ...
+    }
+    ...
+}
+</strong>
+</pre>
+                In the spirit of reuse, we are using our
+                <span class="new_code">Log</span> class.
+                A conventional test case might look like this...
+<pre>
+&lt;?php
+require_once('simpletest/autorun.php');
+require_once('../classes/log.php');
+<strong>require_once('../classes/session_pool.php');</strong>
+
+class <strong>TestOfSessionLogging</strong> extends UnitTestCase {
+    
+    function setUp() {
+        <strong>@unlink('/temp/test.log');</strong>
+    }
+    
+    function tearDown() {
+        <strong>@unlink('/temp/test.log');</strong>
+    }
+    
+    function testLoggingInIsLogged() {
+        <strong>$log = new Log('/temp/test.log');
+        $session_pool = &amp;new SessionPool($log);
+        $session_pool-&gt;logIn('fred');</strong>
+        $messages = file('/temp/test.log');
+        $this-&gt;assertEqual($messages[0], "User fred logged in.<strong>\n</strong>");
+    }
+}
+?&gt;
+</pre>
+                We'll explain the <span class="new_code">setUp()</span> and <span class="new_code">tearDown()</span>
+                methods later.
+            </p>
+            <p>
+                This test case design is not all bad, but it could be improved.
+                We are spending time fiddling with log files which are
+                not part of our test.
+                We have created close ties with the <span class="new_code">Log</span> class and
+                this test.
+                What if we don't use files any more, but use ths
+                <em>syslog</em> library instead?
+                It means that our <span class="new_code">TestOfSessionLogging</span> test will
+                fail, even thouh it's not testing Logging.
+            </p>
+            <p>
+                It's fragile in smaller ways too.
+                Did you notice the extra carriage return in the message?
+                Was that added by the logger?
+                What if it also added a time stamp or other data?
+            </p>
+            <p>
+                The only part that we really want to test is that a particular
+                message was sent to the logger.
+                We can reduce coupling if we pass in a fake logging class
+                that simply records the message calls for testing, but
+                takes no action.
+                It would have to look exactly like our original though.
+            </p>
+            <p>
+                If the fake object doesn't write to a file then we save on deleting
+                the file before and after each test. We could save even more
+                test code if the fake object would kindly run the assertion for us.
+            <p>
+            </p>
+                Too good to be true?
+                We can create such an object easily...
+<pre>
+&lt;?php
+require_once('simpletest/autorun.php');
+require_once('../classes/log.php');
+require_once('../classes/session_pool.php');
+
+<strong>Mock::generate('Log');</strong>
+
+class TestOfSessionLogging extends UnitTestCase {
+    
+    function testLoggingInIsLogged() {<strong>
+        $log = &amp;new MockLog();
+        $log-&gt;expectOnce('message', array('User fred logged in.'));</strong>
+        $session_pool = &amp;new SessionPool(<strong>$log</strong>);
+        $session_pool-&gt;logIn('fred');
+    }
+}
+?&gt;
+</pre>
+                The <span class="new_code">Mock::generate()</span> call code generated a new class
+                called <span class="new_code">MockLog</span>.
+                This looks like an identical clone, except that we can wire test code
+                to it.
+                That's what <span class="new_code">expectOnce()</span> does.
+                It says that if <span class="new_code">message()</span> is ever called on me, it had
+                better be with the parameter "User fred logged in.".
+            </p>
+            <p>
+                The test will be triggered when the call to
+                <span class="new_code">message()</span> is invoked on the
+                <span class="new_code">MockLog</span> object by <span class="new_code">SessionPool::logIn()</span> code.
+                The mock call will trigger a parameter comparison and then send the
+                resulting pass or fail event to the test display.
+                Wildcards can be included here too, so you don't have to test every parameter of
+                a call when you only want to test one.
+            </p>
+            <p>
+                If the mock reaches the end of the test case without the
+                method being called, the <span class="new_code">expectOnce()</span>
+                expectation will trigger a test failure.
+                In other words the mocks can detect the absence of
+                behaviour as well as the presence.
+            </p>
+            <p>
+                The mock objects in the SimpleTest suite can have arbitrary
+                return values set, sequences of returns, return values
+                selected according to the incoming arguments, sequences of
+                parameter expectations and limits on the number of times
+                a method is to be invoked.
+            </p>
+            <p>
+                For more information about mocking and stubbing, see the
+                <a href="mock_objects_documentation.html">mock objects documentation</a>.
+            </p>
+        
+        <h2>
+<a class="target" name="web"></a>Web page testing</h2>
+            <p>
+                One of the requirements of web sites is that they produce web
+                pages.
+                If you are building a project top-down and you want to fully
+                integrate testing along the way then you will want a way of
+                automatically navigating a site and examining output for
+                correctness.
+                This is the job of a web tester.
+            </p>
+            <p>
+                The web testing in SimpleTest is fairly primitive, as there is
+                no JavaScript.
+                Most other browser operations are simulated.
+            </p>
+            <p>
+                To give an idea here is a trivial example where a home
+                page is fetched, from which we navigate to an "about"
+                page and then test some client determined content.
+<pre>
+&lt;?php
+require_once('simpletest/autorun.php');
+<strong>require_once('simpletest/web_tester.php');</strong>
+
+class TestOfAbout extends <strong>WebTestCase</strong> {
+    function testOurAboutPageGivesFreeReignToOurEgo() {
+        <strong>$this-&gt;get('http://test-server/index.php');
+        $this-&gt;click('About');
+        $this-&gt;assertTitle('About why we are so great');
+        $this-&gt;assertText('We are really great');</strong>
+    }
+}
+?&gt;
+</pre>
+                With this code as an acceptance test, you can ensure that
+                the content always meets the specifications of both the
+                developers, and the other project stakeholders.
+            </p>
+            <p>
+                You can navigate forms too...
+<pre>
+&lt;?php
+require_once('simpletest/autorun.php');
+require_once('simpletest/web_tester.php');
+
+class TestOfRankings extends WebTestCase {
+    function testWeAreTopOfGoogle() {
+        $this-&gt;get('http://google.com/');
+        $this-&gt;setField('q', 'simpletest');
+        $this-&gt;click("I'm Feeling Lucky");
+        $this-&gt;assertTitle('SimpleTest - Unit Testing for PHP');
+    }
+}
+?&gt;
+</pre>
+                ...although this could violate Google's(tm) terms and conditions.
+            </p>
+            <p>
+                For more information about web testing, see the
+                <a href="browser_documentation.html">scriptable
+                browser documentation</a> and the
+                <a href="web_tester_documentation.html">WebTestCase</a>.
+            </p>
+            <p>
+                <a href="http://sourceforge.net/projects/simpletest/"><img src="http://sourceforge.net/sflogo.php?group_id=76550&amp;type=5" width="210" height="62" border="0" alt="SourceForge.net Logo"></a>
+            </p>
+        
+    </div>
+        References and related information...
+        <ul>
+<li>
+            <a href="https://sourceforge.net/project/showfiles.php?group_id=76550&amp;release_id=153280">Download PHP SimpleTest</a>
+            from <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
+        </li>
+<li>
+            The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
+            gives full detail on the classes and assertions available.
+        </li>
+</ul>
+<div class="menu_back"><div class="menu">
+<span class="chosen">SimpleTest</span>
+                |
+                <a href="overview.html">Overview</a>
+                |
+                <a href="unit_test_documentation.html">Unit tester</a>
+                |
+                <a href="group_test_documentation.html">Group tests</a>
+                |
+                <a href="mock_objects_documentation.html">Mock objects</a>
+                |
+                <a href="partial_mocks_documentation.html">Partial mocks</a>
+                |
+                <a href="reporter_documentation.html">Reporting</a>
+                |
+                <a href="expectation_documentation.html">Expectations</a>
+                |
+                <a href="web_tester_documentation.html">Web tester</a>
+                |
+                <a href="form_testing_documentation.html">Testing forms</a>
+                |
+                <a href="authentication_documentation.html">Authentication</a>
+                |
+                <a href="browser_documentation.html">Scriptable browser</a>
+</div></div>
+<div class="copyright">
+            Copyright<br>Marcus Baker 2006
+        </div>
+</body>
+</html>
diff --git a/lib/simpletestlib/docs/en/mock_objects_documentation.html b/lib/simpletestlib/docs/en/mock_objects_documentation.html
new file mode 100644 (file)
index 0000000..b4697f9
--- /dev/null
@@ -0,0 +1,870 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>SimpleTest for PHP mock objects documentation</title>
+<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
+</head>
+<body>
+<div class="menu_back"><div class="menu">
+<a href="index.html">SimpleTest</a>
+                |
+                <a href="overview.html">Overview</a>
+                |
+                <a href="unit_test_documentation.html">Unit tester</a>
+                |
+                <a href="group_test_documentation.html">Group tests</a>
+                |
+                <span class="chosen">Mock objects</span>
+                |
+                <a href="partial_mocks_documentation.html">Partial mocks</a>
+                |
+                <a href="reporter_documentation.html">Reporting</a>
+                |
+                <a href="expectation_documentation.html">Expectations</a>
+                |
+                <a href="web_tester_documentation.html">Web tester</a>
+                |
+                <a href="form_testing_documentation.html">Testing forms</a>
+                |
+                <a href="authentication_documentation.html">Authentication</a>
+                |
+                <a href="browser_documentation.html">Scriptable browser</a>
+</div></div>
+<h1>Mock objects documentation</h1>
+        This page...
+        <ul>
+<li>
+            <a href="#what">What are mock objects?</a>
+        </li>
+<li>
+            <a href="#creation">Creating mock objects</a>.
+        </li>
+<li>
+            <a href="#expectations">Mocks as critics</a> with expectations.
+        </li>
+</ul>
+<div class="content">
+        <h2>
+<a class="target" name="what"></a>What are mock objects?</h2>
+            <p>
+                Mock objects have two roles during a test case: actor and critic.
+            </p>
+            <p>
+                The actor behaviour is to simulate objects that are difficult to
+                set up or time consuming to set up for a test.
+                The classic example is a database connection.
+                Setting up a test database at the start of each test would slow
+                testing to a crawl and would require the installation of the
+                database engine and test data on the test machine.
+                If we can simulate the connection and return data of our
+                choosing we not only win on the pragmatics of testing, but can
+                also feed our code spurious data to see how it responds.
+                We can simulate databases being down or other extremes
+                without having to create a broken database for real.
+                In other words, we get greater control of the test environment.
+            </p>
+            <p>
+                If mock objects only behaved as actors they would simply be
+                known as "server stubs".
+                This was originally a pattern named by Robert Binder (<a href="">Testing
+                object-oriented systems</a>: models, patterns, and tools,
+                Addison-Wesley) in 1999.
+            </p>
+            <p>
+                A server stub is a simulation of an object or component.
+                It should exactly replace a component in a system for test
+                or prototyping purposes, but remain lightweight.
+                This allows tests to run more quickly, or if the simulated
+                class has not been written, to run at all.
+            </p>
+            <p>
+                However, the mock objects not only play a part (by supplying chosen
+                return values on demand) they are also sensitive to the
+                messages sent to them (via expectations).
+                By setting expected parameters for a method call they act
+                as a guard that the calls upon them are made correctly.
+                If expectations are not met they save us the effort of
+                writing a failed test assertion by performing that duty on our
+                behalf.
+            </p>
+            <p>
+                In the case of an imaginary database connection they can
+                test that the query, say SQL, was correctly formed by
+                the object that is using the connection.
+                Set them up with fairly tight expectations and you will
+                hardly need manual assertions at all.
+            </p>
+        
+        <h2>
+<a class="target" name="creation"></a>Creating mock objects</h2>
+            <p>
+                All we need is an existing class or interface, say a database connection
+                that looks like this...
+<pre>
+<strong>class DatabaseConnection {
+    function DatabaseConnection() { }
+    function query($sql) { }
+    function selectQuery($sql) { }
+}</strong>
+</pre>
+                To create a mock version of the class we need to run a
+                code generator...
+<pre>
+require_once('simpletest/autorun.php');
+require_once('database_connection.php');
+
+<strong>Mock::generate('DatabaseConnection');</strong>
+</pre>
+                This code generates a clone class called
+                <span class="new_code">MockDatabaseConnection</span>.
+                This new class appears to be the same, but actually has no behaviour at all.
+            </p>
+            <p>
+                The new class is usually a subclass of <span class="new_code">DatabaseConnection</span>.
+                Unfortunately, there is no way to create a mock version of a
+                class with a <span class="new_code">final</span> method without having a living version of
+                that method.
+                We consider that unsafe.
+                If the target is an interface, or if <span class="new_code">final</span> methods are
+                present in a target class, then a whole new class
+                is created, but one implemeting the same interfaces.
+                If you try to pass this separate class through a type hint that specifies
+                the old concrete class name, it will fail.
+                Code like that insists on type hinting to a class with <span class="new_code">final</span>
+                methods probably cannot be safely tested with mocks.
+            </p>
+            <p>
+                If you want to see the generated code, then simply <span class="new_code">print</span>
+                the output of <span class="new_code">Mock::generate()</span>.
+                Here is the generated code for the <span class="new_code">DatabaseConnection</span>
+                class rather than the interface version...
+<pre>
+class MockDatabaseConnection extends DatabaseConnection {
+    public $mock;
+    protected $mocked_methods = array('databaseconnection', 'query', 'selectquery');
+
+    function MockDatabaseConnection() {
+        $this-&gt;mock = new SimpleMock();
+        $this-&gt;mock-&gt;disableExpectationNameChecks();
+    }
+    ...
+    function DatabaseConnection() {
+        $args = func_get_args();
+        $result = &amp;$this-&gt;mock-&gt;invoke("DatabaseConnection", $args);
+        return $result;
+    }
+    function query($sql) {
+        $args = func_get_args();
+        $result = &amp;$this-&gt;mock-&gt;invoke("query", $args);
+        return $result;
+    }
+    function selectQuery($sql) {
+        $args = func_get_args();
+        $result = &amp;$this-&gt;mock-&gt;invoke("selectQuery", $args);
+        return $result;
+    }
+}
+</pre>
+                Your output may vary depending on the exact version
+                of SimpleTest you are using.
+            </p>
+            <p>
+                Besides the original methods of the class, you will see some extra
+                methods that help testing.
+                More on these later.
+            </p>
+            <p>
+                We can now create instances of the new class within
+                our test case...
+<pre>
+require_once('simpletest/autorun.php');
+require_once('database_connection.php');
+
+Mock::generate('DatabaseConnection');
+
+class MyTestCase extends UnitTestCase {
+
+    function testSomething() {
+        <strong>$connection = new MockDatabaseConnection();</strong>
+    }
+}
+</pre>
+                The mock version now has all the methods of the original.
+                Also, any type hints will be faithfully preserved.
+                Say our query methods expect a <span class="new_code">Query</span> object...
+<pre>
+<strong>class DatabaseConnection {
+    function DatabaseConnection() { }
+    function query(Query $query) { }
+    function selectQuery(Query $query) { }
+}</strong>
+</pre>
+                If we now pass the wrong type of object, or worse a non-object...
+<pre>
+class MyTestCase extends UnitTestCase {
+
+    function testSomething() {
+        $connection = new MockDatabaseConnection();
+        $connection-&gt;query('insert into accounts () values ()');
+    }
+}
+</pre>
+                ...the code will throw a type violation at you just as the
+                original class would.
+            </p>
+            <p>
+                The mock version now has all the methods of the original.
+                Unfortunately, they all return <span class="new_code">null</span>.
+                As methods that always return <span class="new_code">null</span> are not that useful,
+                we need to be able to set them to something else...
+            </p>
+            <p>
+                <a class="target" name="stub"><h2>Mocks as actors</h2></a>
+            </p>
+            <p>
+                Changing the return value of a method from <span class="new_code">null</span>
+                to something else is pretty easy...
+<pre>
+<strong>$connection-&gt;returns('query', 37)</strong>
+</pre>
+                Now every time we call
+                <span class="new_code">$connection-&gt;query()</span> we get
+                the result of 37.
+                There is nothing special about 37.
+                The return value can be arbitrarily complicated.
+            </p>
+            <p>
+                Parameters are irrelevant here, we always get the same
+                values back each time once they have been set up this way.
+                That may not sound like a convincing replica of a
+                database connection, but for the half a dozen lines of
+                a test method it is usually all you need.
+            </p>
+            <p>
+                Things aren't always that simple though.
+                One common problem is iterators, where constantly returning
+                the same value could cause an endless loop in the object
+                being tested.
+                For these we need to set up sequences of values.
+                Let's say we have a simple iterator that looks like this...
+<pre>
+class Iterator {
+    function Iterator() { }
+    function next() { }
+}
+</pre>
+                This is about the simplest iterator you could have.
+                Assuming that this iterator only returns text until it
+                reaches the end, when it returns false, we can simulate it
+                with...
+<pre>
+Mock::generate('Iterator');
+
+class IteratorTest extends UnitTestCase() {
+
+    function testASequence() {<strong>
+        $iterator = new MockIterator();
+        $iterator-&gt;returns('next', false);
+        $iterator-&gt;returnsAt(0, 'next', 'First string');
+        $iterator-&gt;returnsAt(1, 'next', 'Second string');</strong>
+        ...
+    }
+}
+</pre>
+                When <span class="new_code">next()</span> is called on the
+                <span class="new_code">MockIterator</span> it will first return "First string",
+                on the second call "Second string" will be returned
+                and on any other call <span class="new_code">false</span> will
+                be returned.
+                The sequenced return values take precedence over the constant
+                return value.
+                The constant one is a kind of default if you like.
+