]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-15204 Update complete
authornicolasconnault <nicolasconnault>
Tue, 10 Jun 2008 20:10:50 +0000 (20:10 +0000)
committernicolasconnault <nicolasconnault>
Tue, 10 Jun 2008 20:10:50 +0000 (20:10 +0000)
45 files changed:
lib/simpletestlib/BACKLOG [deleted file]
lib/simpletestlib/HELP_MY_TESTS_DONT_WORK_ANYMORE
lib/simpletestlib/README
lib/simpletestlib/TODO [deleted file]
lib/simpletestlib/VERSION
lib/simpletestlib/authentication.php
lib/simpletestlib/autorun.php [new file with mode: 0644]
lib/simpletestlib/browser.php
lib/simpletestlib/collector.php
lib/simpletestlib/compatibility.php
lib/simpletestlib/cookies.php
lib/simpletestlib/default_reporter.php [new file with mode: 0644]
lib/simpletestlib/detached.php
lib/simpletestlib/dumper.php
lib/simpletestlib/eclipse.php
lib/simpletestlib/encoding.php
lib/simpletestlib/errors.php
lib/simpletestlib/exceptions.php
lib/simpletestlib/expectation.php
lib/simpletestlib/extensions/phpunit_test_case.php
lib/simpletestlib/extensions/testdox.php [new file with mode: 0644]
lib/simpletestlib/extensions/testdox/test.php [new file with mode: 0644]
lib/simpletestlib/form.php
lib/simpletestlib/frames.php
lib/simpletestlib/http.php
lib/simpletestlib/invoker.php
lib/simpletestlib/mock_objects.php
lib/simpletestlib/page.php
lib/simpletestlib/parser.php
lib/simpletestlib/reflection_php4.php
lib/simpletestlib/reflection_php5.php
lib/simpletestlib/remote.php
lib/simpletestlib/reporter.php
lib/simpletestlib/scorer.php
lib/simpletestlib/selector.php
lib/simpletestlib/shell_tester.php
lib/simpletestlib/simpletest.php
lib/simpletestlib/socket.php
lib/simpletestlib/tag.php
lib/simpletestlib/test_case.php
lib/simpletestlib/unit_tester.php
lib/simpletestlib/url.php
lib/simpletestlib/user_agent.php
lib/simpletestlib/web_tester.php
lib/simpletestlib/xml.php

diff --git a/lib/simpletestlib/BACKLOG b/lib/simpletestlib/BACKLOG
deleted file mode 100644 (file)
index 03c4730..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-BACKLOG
-This is backed up stuff that defines the 1.0.1 release.
-
-$Id$
-
-Unit tester
------------
-Add skip facility (4).
-
-Reporter
---------
-Add a repeating reporter (2).
-Split and refactor dumpers by type (2).
-
-Mock objects
-------------
-
-Parser
-------
-Add U flag to regexes to allow unicode (3).
-Parse fieldsets correctly with labels (5).
-
-Browser
--------
-Change getUrls() to return more information (2).
-Add a file() loading command (2).
-Investigate bug, clicking keeps encoding again and again (3).
-Fix bug URL losing spaces (3).
-Setting an option by id attribute should only toggle that option (3).
-Add additional parameters to click() method (1).
-Allow index selection of controls (3).
-
-Web tester
-----------
-Add assertClickable() method (1).
-Add assertSubmit() method (1).
-
-Documentation
--------------
-Extension docs (1).
-Replace all deprecated methods (3).
-Split mock objects off fromtutorial (4).
-Add page for projects that use or extend SimpleTest (2).
-
-Build
------
index 806a7a7056b03c03ac982affcbf49e62cb37822f..8ac9cf2aa51b4cd34cb8467d8dd9e7f2a33af267 100644 (file)
@@ -5,17 +5,31 @@ 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...
 
+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().
+
+Method setWildcard() removed in mocks
+-------------------------------------
+Even setWildcard() has been removed in 1.0.1beta now.
+If you want to test explicitely for a '*' string, then
+simply pass in new IdenticalExpectation('*') instead.
+
 No method _getTest() on mocks
 -----------------------------
 This has finally been removed. It was a pretty esoteric
 flex point anyway. It was there to allow the mocks to
-work with other test tools, but no one does this anyway.
+work with other test tools, but no one does this.
 
 No method assertError(), assertNoErrors(), swallowErrors()
 ----------------------------------------------------------
 These have been deprecated in 1.0.1beta in favour of
 expectError() and expectException(). assertNoErrors() is
-redundant if you use expectError() as failures are now reporterted
+redundant if you use expectError() as failures are now reported
 immediately.
 
 No method TestCase::signal()
index b8d1979205d9e907fd16870e8f2e5e4bb8015401..c52e3f7edcc5896ccd16a463e6c8c738a7d058ee 100644 (file)
@@ -8,9 +8,9 @@ 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+.
+mock objects for PHP 4.2.0+ (and PHP 5.0 to 5.3 without E_STRICT).
 
-If you have used JUnit you will find this PHP unit testing version very
+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.
 The stubs can have return values set for different arguments, can have
 sequences set also by arguments and can return items by reference.
@@ -31,9 +31,9 @@ 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.
+one with the toolkit. YOucan download an Eclipse plug-in separately.
 
-You are looking at a first full release. The unit tests for SimpleTest
+You are looking at a second full release. The unit tests for SimpleTest
 itself can be run here...
 
 simpletest/test/unit_tests.php
@@ -42,7 +42,7 @@ And tests involving live network connections as well are here...
 
 simpletest/test/all_tests.php
 
-The full tests will typically overrun the 8Mb limit usually allowed
+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.
@@ -55,7 +55,7 @@ The full tests read some test data from the LastCraft site. 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.
+unit test failures, so please correct 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...
@@ -63,7 +63,7 @@ also function as expected. If they don't see the file...
 HELP_MY_TESTS_DONT_WORK_ANYMORE
 
 This contains information on interface changes. It also points out
-deprecated interfaces so you should read this even if all of
+deprecated interfaces, so you should read this even if all of
 your current tests appear to run.
 
 There is a documentation folder which contains the core reference information
@@ -77,7 +77,7 @@ 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.
 
-If you download and use and possibly even extend this tool, please let us
+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...
@@ -87,10 +87,10 @@ simpletest-support@lists.sourceforge.net
 ...so that others can read them too. We usually try to respond within 48
 hours.
 
-There is no change log as yet except at Sourceforge. You can visit the
+There is no change log except at Sourceforge. You can visit the
 release notes to see the completed TODO list after each cycle and also the
-status of any bugs, but if the bug is recent then it will be fixed in CVS only.
-The CVS check-ins always have all the tests passing and so CVS snapshots should
+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
@@ -103,6 +103,6 @@ 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.
 
-yours Marcus Baker, Jason Sweat, Travis Swicegood and Perrick Penet.
+Marcus Baker, Jason Sweat, Travis Swicegood, Perrick Penet and Edward Z. Yang.
 -- 
 marcus@lastcraft.com
diff --git a/lib/simpletestlib/TODO b/lib/simpletestlib/TODO
deleted file mode 100644 (file)
index 5d9dc54..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-TODO
-This is immediate stuff only for this iteration (1.0.1alpha3) of 44 hours (60 actual).
-
-$Id$
-
-Unit tester
------------
-Change assertCopy() to assertClone() and add identical check (1/1) - done.
-The ignore() directive should ignore parent classes as well (1/3) - done.
-The fail() method should issue a line number (2/1) - done.
-Abstract test cases should be ignored automatically (2/2) - done.
-More meaningful message when trying a test in the constructor (1/1) - done.
-Add exception trap (2/2) - done.
-
-Reporter
---------
-Allow multiple reporters (2/1) - done.
-Add a test case selecting ReporterDecorator (2/4) - done.
-Can we get rid of that useless runner class? yes! (1/2) - done.
-Test case should say what tests are available (1/1) - done.
-
-Mock objects
-------------
-Confirm mocking of SPL classes and derivatives (3/4) - done.
-Optional parameters generate spurious warnings in PHP 5.0.2 (1/2) - done.
-
-Parser
-------
-
-Browser
--------
-Allow ignoring of cookies as per feature request (2/8) - done.
-Setting a radio button with value 0 just doesn't happen (1/10) - done.
-Fix quoting issue with fields as in bug report (2/2) - done.
-Fix bug with encoded GET parameters not being transmitted in form action (3/2) - done.
-Confirm that button with no name has no value transmitted (2/4) - done.
-Allow setting of checkboxes by setting them to true (1/1) - done.
-Add memory fixes to builder (1/1) - done.
-
-Web tester
-----------
-Failure to connect should be reported unless suppressed (2/2) - done.
-Message bug in web header pattern assertions (2/1) - done.
-
-Documentation
--------------
-
-Build
------
-Fix fatal error in PHP 5.1 (3/0) - done.
-Fix fatal error in PHP 5.0.5 (3/2) - done.
-Test with PHP 4.4.2 (1/1) - done.
-Fix error in PHP 5.0.2 (2/2) - done.
index fff574851f5b5c0d80af6fd0044222f70cc44e7a..7f207341d5d9bda5ae8acfa7a6e02cdf7cd9d983 100644 (file)
@@ -1 +1 @@
-1.0.1beta
\ No newline at end of file
+1.0.1
\ No newline at end of file
index 98fe4118d3b15010c6ccff2038116b1360754bd2..382f1289aaa365624389e6c853d45df9bfae68fe 100644 (file)
 <?php
+/**
+ *  Base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage WebTester
+ *  @version    $Id$
+ */
+/**
+ *  include http class
+ */
+require_once(dirname(__FILE__) . '/http.php');
+
+/**
+ *    Represents a single security realm's identity.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleRealm {
+    var $_type;
+    var $_root;
+    var $_username;
+    var $_password;
+    
     /**
-     * Base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     WebTester
-     * @version        $Id$
+     *    Starts with the initial entry directory.
+     *    @param string $type      Authentication type for this
+     *                             realm. Only Basic authentication
+     *                             is currently supported.
+     *    @param SimpleUrl $url    Somewhere in realm.
+     *    @access public
      */
+    function SimpleRealm($type, $url) {
+        $this->_type = $type;
+        $this->_root = $url->getBasePath();
+        $this->_username = false;
+        $this->_password = false;
+    }
+    
     /**
-     * include http class
+     *    Adds another location to the realm.
+     *    @param SimpleUrl $url    Somewhere in realm.
+     *    @access public
      */
-    require_once(dirname(__FILE__) . '/http.php');
+    function stretch($url) {
+        $this->_root = $this->_getCommonPath($this->_root, $url->getPath());
+    }
     
     /**
-     *    Represents a single security realm's identity.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Finds the common starting path.
+     *    @param string $first        Path to compare.
+     *    @param string $second       Path to compare.
+     *    @return string              Common directories.
+     *    @access private
      */
-    class SimpleRealm {
-        var $_type;
-        var $_root;
-        var $_username;
-        var $_password;
-        
-        /**
-         *    Starts with the initial entry directory.
-         *    @param string $type      Authentication type for this
-         *                             realm. Only Basic authentication
-         *                             is currently supported.
-         *    @param SimpleUrl $url    Somewhere in realm.
-         *    @access public
-         */
-        function SimpleRealm($type, $url) {
-            $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());
-        }
-        
-        /**
-         *    Finds the common starting path.
-         *    @param string $first        Path to compare.
-         *    @param string $second       Path to compare.
-         *    @return string              Common directories.
-         *    @access private
-         */
-        function _getCommonPath($first, $second) {
-            $first = explode('/', $first);
-            $second = explode('/', $second);
-            for ($i = 0; $i < min(count($first), count($second)); $i++) {
-                if ($first[$i] != $second[$i]) {
-                    return implode('/', array_slice($first, 0, $i)) . '/';
-                }
-            }
-            return implode('/', $first) . '/';
-        }
-        
-        /**
-         *    Sets the identity to try within this realm.
-         *    @param string $username    Username in authentication dialog.
-         *    @param string $username    Password in authentication dialog.
-         *    @access public
-         */
-        function setIdentity($username, $password) {
-            $this->_username = $username;
-            $this->_password = $password;
-        }
-        
-        /**
-         *    Accessor for current identity.
-         *    @return string        Last succesful username.
-         *    @access public
-         */
-        function getUsername() {
-            return $this->_username;
-        }
-        
-        /**
-         *    Accessor for current identity.
-         *    @return string        Last succesful password.
-         *    @access public
-         */
-        function getPassword() {
-            return $this->_password;
-        }
-        
-        /**
-         *    Test to see if the URL is within the directory
-         *    tree of the realm.
-         *    @param SimpleUrl $url    URL to test.
-         *    @return boolean          True if subpath.
-         *    @access public
-         */
-        function isWithin($url) {
-            if ($this->_isIn($this->_root, $url->getBasePath())) {
-                return true;
-            }
-            if ($this->_isIn($this->_root, $url->getBasePath() . $url->getPage() . '/')) {
-                return true;
+    function _getCommonPath($first, $second) {
+        $first = explode('/', $first);
+        $second = explode('/', $second);
+        for ($i = 0; $i < min(count($first), count($second)); $i++) {
+            if ($first[$i] != $second[$i]) {
+                return implode('/', array_slice($first, 0, $i)) . '/';
             }
-            return false;
-        }
-        
-        /**
-         *    Tests to see if one string is a substring of
-         *    another.
-         *    @param string $part        Small bit.
-         *    @param string $whole       Big bit.
-         *    @return boolean            True if the small bit is
-         *                               in the big bit.
-         *    @access private
-         */
-        function _isIn($part, $whole) {
-            return strpos($whole, $part) === 0;
         }
+        return implode('/', $first) . '/';
     }
     
     /**
-     *    Manages security realms.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Sets the identity to try within this realm.
+     *    @param string $username    Username in authentication dialog.
+     *    @param string $username    Password in authentication dialog.
+     *    @access public
      */
-    class SimpleAuthenticator {
-        var $_realms;
-        
-        /**
-         *    Clears the realms.
-         *    @access public
-         */
-        function SimpleAuthenticator() {
-            $this->restartSession();
-        }
-        
-        /**
-         *    Starts with no realms set up.
-         *    @access public
-         */
-        function restartSession() {
-            $this->_realms = array();
+    function setIdentity($username, $password) {
+        $this->_username = $username;
+        $this->_password = $password;
+    }
+    
+    /**
+     *    Accessor for current identity.
+     *    @return string        Last succesful username.
+     *    @access public
+     */
+    function getUsername() {
+        return $this->_username;
+    }
+    
+    /**
+     *    Accessor for current identity.
+     *    @return string        Last succesful password.
+     *    @access public
+     */
+    function getPassword() {
+        return $this->_password;
+    }
+    
+    /**
+     *    Test to see if the URL is within the directory
+     *    tree of the realm.
+     *    @param SimpleUrl $url    URL to test.
+     *    @return boolean          True if subpath.
+     *    @access public
+     */
+    function isWithin($url) {
+        if ($this->_isIn($this->_root, $url->getBasePath())) {
+            return true;
         }
-        
-        /**
-         *    Adds a new realm centered the current URL.
-         *    Browsers vary 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
-         *    occours. SimpleTest tries to follow the spirit of
-         *    the original standards committee and treats the
-         *    base URL as the root of a file tree shaped realm.
-         *    @param SimpleUrl $url    Base of realm.
-         *    @param string $type      Authentication type for this
-         *                             realm. Only Basic authentication
-         *                             is currently supported.
-         *    @param string $realm     Name of realm.
-         *    @access public
-         */
-        function addRealm($url, $type, $realm) {
-            $this->_realms[$url->getHost()][$realm] = new SimpleRealm($type, $url);
+        if ($this->_isIn($this->_root, $url->getBasePath() . $url->getPage() . '/')) {
+            return true;
         }
-        
-        /**
-         *    Sets the current identity to be presented
-         *    against that realm.
-         *    @param string $host        Server hosting realm.
-         *    @param string $realm       Name of realm.
-         *    @param string $username    Username for realm.
-         *    @param string $password    Password for realm.
-         *    @access public
-         */
-        function setIdentityForRealm($host, $realm, $username, $password) {
-            if (isset($this->_realms[$host][$realm])) {
-                $this->_realms[$host][$realm]->setIdentity($username, $password);
-            }
+        return false;
+    }
+    
+    /**
+     *    Tests to see if one string is a substring of
+     *    another.
+     *    @param string $part        Small bit.
+     *    @param string $whole       Big bit.
+     *    @return boolean            True if the small bit is
+     *                               in the big bit.
+     *    @access private
+     */
+    function _isIn($part, $whole) {
+        return strpos($whole, $part) === 0;
+    }
+}
+
+/**
+ *    Manages security realms.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleAuthenticator {
+    var $_realms;
+    
+    /**
+     *    Clears the realms.
+     *    @access public
+     */
+    function SimpleAuthenticator() {
+        $this->restartSession();
+    }
+    
+    /**
+     *    Starts with no realms set up.
+     *    @access public
+     */
+    function restartSession() {
+        $this->_realms = array();
+    }
+    
+    /**
+     *    Adds a new realm centered the current URL.
+     *    Browsers vary 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
+     *    occours. SimpleTest tries to follow the spirit of
+     *    the original standards committee and treats the
+     *    base URL as the root of a file tree shaped realm.
+     *    @param SimpleUrl $url    Base of realm.
+     *    @param string $type      Authentication type for this
+     *                             realm. Only Basic authentication
+     *                             is currently supported.
+     *    @param string $realm     Name of realm.
+     *    @access public
+     */
+    function addRealm($url, $type, $realm) {
+        $this->_realms[$url->getHost()][$realm] = new SimpleRealm($type, $url);
+    }
+    
+    /**
+     *    Sets the current identity to be presented
+     *    against that realm.
+     *    @param string $host        Server hosting realm.
+     *    @param string $realm       Name of realm.
+     *    @param string $username    Username for realm.
+     *    @param string $password    Password for realm.
+     *    @access public
+     */
+    function setIdentityForRealm($host, $realm, $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()])) {
-                return false;
-            }
-            foreach ($this->_realms[$url->getHost()] as $name => $realm) {
-                if ($realm->isWithin($url)) {
-                    return $realm;
-                }
-            }
+    }
+    
+    /**
+     *    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()])) {
             return false;
         }
-        
-        /**
-         *    Presents the appropriate headers for this location.
-         *    @param SimpleHttpRequest $request  Request to modify.
-         *    @param SimpleUrl $url              Base of realm.
-         *    @access public
-         */
-        function addHeaders(&$request, $url) {
-            if ($url->getUsername() && $url->getPassword()) {
-                $username = $url->getUsername();
-                $password = $url->getPassword();
-            } elseif ($realm = $this->_findRealmFromUrl($url)) {
-                $username = $realm->getUsername();
-                $password = $realm->getPassword();
-            } else {
-                return;
+        foreach ($this->_realms[$url->getHost()] as $name => $realm) {
+            if ($realm->isWithin($url)) {
+                return $realm;
             }
-            $this->addBasicHeaders($request, $username, $password);
         }
-        
-        /**
-         *    Presents the appropriate headers for this
-         *    location for basic authentication.
-         *    @param SimpleHttpRequest $request  Request to modify.
-         *    @param string $username            Username for realm.
-         *    @param string $password            Password for realm.
-         *    @access public
-         *    @static
-         */
-        function addBasicHeaders(&$request, $username, $password) {
-            if ($username && $password) {
-                $request->addHeaderLine(
-                        'Authorization: Basic ' . base64_encode("$username:$password"));
-            }
+        return false;
+    }
+    
+    /**
+     *    Presents the appropriate headers for this location.
+     *    @param SimpleHttpRequest $request  Request to modify.
+     *    @param SimpleUrl $url              Base of realm.
+     *    @access public
+     */
+    function addHeaders(&$request, $url) {
+        if ($url->getUsername() && $url->getPassword()) {
+            $username = $url->getUsername();
+            $password = $url->getPassword();
+        } elseif ($realm = $this->_findRealmFromUrl($url)) {
+            $username = $realm->getUsername();
+            $password = $realm->getPassword();
+        } else {
+            return;
+        }
+        $this->addBasicHeaders($request, $username, $password);
+    }
+    
+    /**
+     *    Presents the appropriate headers for this
+     *    location for basic authentication.
+     *    @param SimpleHttpRequest $request  Request to modify.
+     *    @param string $username            Username for realm.
+     *    @param string $password            Password for realm.
+     *    @access public
+     *    @static
+     */
+    function addBasicHeaders(&$request, $username, $password) {
+        if ($username && $password) {
+            $request->addHeaderLine(
+                    'Authorization: Basic ' . base64_encode("$username:$password"));
         }
     }
+}
 ?>
\ No newline at end of file
diff --git a/lib/simpletestlib/autorun.php b/lib/simpletestlib/autorun.php
new file mode 100644 (file)
index 0000000..a57408d
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+/**
+ *  Autorunner which runs all tests cases found in a file
+ *  that includes this module.
+ *  @package    SimpleTest
+ *  @version    $Id$
+ */
+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();
+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().
+ */
+function simpletest_autorun() {
+    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());
+    if (SimpleReporter::inCli()) {
+        exit($result ? 0 : 1);
+    }
+}
+
+/**
+ *    Checks the current test context to see if a test has
+ *    ever been run.
+ *    @return boolean        True if tests have run.
+ */
+function tests_have_run() {
+    if ($context = SimpleTest::getContext()) {
+        return (boolean)$context->getTest();
+    }
+    return false;
+}
+
+/**
+ *    The first autorun file.
+ *    @return string        Filename of first autorun script.
+ */
+function initial_file() {
+    static $file = false;
+    if (! $file) {
+        $file = reset(get_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.
+ *    @return array        Class names.
+ */
+function capture_new_classes() {
+    global $SIMPLETEST_AUTORUNNER_INITIAL_CLASSES;
+    return array_map('strtolower', array_diff(get_declared_classes(),
+                            $SIMPLETEST_AUTORUNNER_INITIAL_CLASSES ?
+                            $SIMPLETEST_AUTORUNNER_INITIAL_CLASSES : array()));
+}
+?>
\ No newline at end of file
index 6b41d0b4b4c7fbbe893ab097e319208203d1155e..dc544708b402068977b27e2de7e6014d52cb3659 100644 (file)
 <?php
+/**
+ *  Base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage WebTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ *  include other SimpleTest class files
+ */
+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__) . '/selector.php');
+require_once(dirname(__FILE__) . '/frames.php');
+require_once(dirname(__FILE__) . '/user_agent.php');
+/**#@-*/
+
+if (!defined('DEFAULT_MAX_NESTED_FRAMES')) {
+    define('DEFAULT_MAX_NESTED_FRAMES', 3);
+}
+
+/**
+ *    Browser history list.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleBrowserHistory {
+    var $_sequence;
+    var $_position;
+
     /**
-     * Base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     WebTester
-     * @version        $Id$
+     *    Starts empty.
+     *    @access public
      */
+    function SimpleBrowserHistory() {
+        $this->_sequence = array();
+        $this->_position = -1;
+    }
 
-    /**#@+
-     * include other SimpleTest class files
+    /**
+     *    Test for no entries yet.
+     *    @return boolean        True if empty.
+     *    @access private
      */
-    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__) . '/selector.php');
-    require_once(dirname(__FILE__) . '/frames.php');
-    require_once(dirname(__FILE__) . '/user_agent.php');
-    /**#@-*/
+    function _isEmpty() {
+        return ($this->_position == -1);
+    }
 
-    if (!defined('DEFAULT_MAX_NESTED_FRAMES')) {
-        define('DEFAULT_MAX_NESTED_FRAMES', 3);
+    /**
+     *    Test for being at the beginning.
+     *    @return boolean        True if first.
+     *    @access private
+     */
+    function _atBeginning() {
+        return ($this->_position == 0) && ! $this->_isEmpty();
     }
 
     /**
-     *    Browser history list.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Test for being at the last entry.
+     *    @return boolean        True if last.
+     *    @access private
      */
-    class SimpleBrowserHistory {
-        var $_sequence;
-        var $_position;
+    function _atEnd() {
+        return ($this->_position + 1 >= count($this->_sequence)) && ! $this->_isEmpty();
+    }
 
-        /**
-         *    Starts empty.
-         *    @access public
-         */
-        function SimpleBrowserHistory() {
-            $this->_sequence = array();
-            $this->_position = -1;
-        }
+    /**
+     *    Adds a successfully fetched page to the history.
+     *    @param SimpleUrl $url                 URL of fetch.
+     *    @param SimpleEncoding $parameters     Any post data with the fetch.
+     *    @access public
+     */
+    function recordEntry($url, $parameters) {
+        $this->_dropFuture();
+        array_push(
+                $this->_sequence,
+                array('url' => $url, 'parameters' => $parameters));
+        $this->_position++;
+    }
 
-        /**
-         *    Test for no entries yet.
-         *    @return boolean        True if empty.
-         *    @access private
-         */
-        function _isEmpty() {
-            return ($this->_position == -1);
+    /**
+     *    Last fully qualified URL for current history
+     *    position.
+     *    @return SimpleUrl        URL for this position.
+     *    @access public
+     */
+    function getUrl() {
+        if ($this->_isEmpty()) {
+            return false;
         }
+        return $this->_sequence[$this->_position]['url'];
+    }
 
-        /**
-         *    Test for being at the beginning.
-         *    @return boolean        True if first.
-         *    @access private
-         */
-        function _atBeginning() {
-            return ($this->_position == 0) && ! $this->_isEmpty();
+    /**
+     *    Parameters of last fetch from current history
+     *    position.
+     *    @return SimpleFormEncoding    Post parameters.
+     *    @access public
+     */
+    function getParameters() {
+        if ($this->_isEmpty()) {
+            return false;
         }
+        return $this->_sequence[$this->_position]['parameters'];
+    }
 
-        /**
-         *    Test for being at the last entry.
-         *    @return boolean        True if last.
-         *    @access private
-         */
-        function _atEnd() {
-            return ($this->_position + 1 >= count($this->_sequence)) && ! $this->_isEmpty();
+    /**
+     *    Step back one place in the history. Stops at
+     *    the first page.
+     *    @return boolean     True if any previous entries.
+     *    @access public
+     */
+    function back() {
+        if ($this->_isEmpty() || $this->_atBeginning()) {
+            return false;
         }
+        $this->_position--;
+        return true;
+    }
 
-        /**
-         *    Adds a successfully fetched page to the history.
-         *    @param SimpleUrl $url                 URL of fetch.
-         *    @param SimpleEncoding $parameters     Any post data with the fetch.
-         *    @access public
-         */
-        function recordEntry($url, $parameters) {
-            $this->_dropFuture();
-            array_push(
-                    $this->_sequence,
-                    array('url' => $url, 'parameters' => $parameters));
-            $this->_position++;
+    /**
+     *    Step forward one place. If already at the
+     *    latest entry then nothing will happen.
+     *    @return boolean     True if any future entries.
+     *    @access public
+     */
+    function forward() {
+        if ($this->_isEmpty() || $this->_atEnd()) {
+            return false;
         }
+        $this->_position++;
+        return true;
+    }
 
-        /**
-         *    Last fully qualified URL for current history
-         *    position.
-         *    @return SimpleUrl        URL for this position.
-         *    @access public
-         */
-        function getUrl() {
-            if ($this->_isEmpty()) {
-                return false;
-            }
-            return $this->_sequence[$this->_position]['url'];
+    /**
+     *    Ditches all future entries beyond the current
+     *    point.
+     *    @access private
+     */
+    function _dropFuture() {
+        if ($this->_isEmpty()) {
+            return;
         }
-
-        /**
-         *    Parameters of last fetch from current history
-         *    position.
-         *    @return SimpleFormEncoding    Post parameters.
-         *    @access public
-         */
-        function getParameters() {
-            if ($this->_isEmpty()) {
-                return false;
-            }
-            return $this->_sequence[$this->_position]['parameters'];
+        while (! $this->_atEnd()) {
+            array_pop($this->_sequence);
         }
+    }
+}
+
+/**
+ *    Simulated web browser. This is an aggregate of
+ *    the user agent, the HTML parsing, request history
+ *    and the last header set.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleBrowser {
+    var $_user_agent;
+    var $_page;
+    var $_history;
+    var $_ignore_frames;
+    var $_maximum_nested_frames;
 
-        /**
-         *    Step back one place in the history. Stops at
-         *    the first page.
-         *    @return boolean     True if any previous entries.
-         *    @access public
-         */
-        function back() {
-            if ($this->_isEmpty() || $this->_atBeginning()) {
-                return false;
-            }
-            $this->_position--;
-            return true;
-        }
+    /**
+     *    Starts with a fresh browser with no
+     *    cookie or any other state information. The
+     *    exception is that a default proxy will be
+     *    set up if specified in the options.
+     *    @access public
+     */
+    function SimpleBrowser() {
+        $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;
+    }
 
-        /**
-         *    Step forward one place. If already at the
-         *    latest entry then nothing will happen.
-         *    @return boolean     True if any future entries.
-         *    @access public
-         */
-        function forward() {
-            if ($this->_isEmpty() || $this->_atEnd()) {
-                return false;
-            }
-            $this->_position++;
-            return true;
-        }
+    /**
+     *    Creates the underlying user agent.
+     *    @return SimpleFetcher    Content fetcher.
+     *    @access protected
+     */
+    function &_createUserAgent() {
+        $user_agent = &new SimpleUserAgent();
+        return $user_agent;
+    }
 
-        /**
-         *    Ditches all future entries beyond the current
-         *    point.
-         *    @access private
-         */
-        function _dropFuture() {
-            if ($this->_isEmpty()) {
-                return;
-            }
-            while (! $this->_atEnd()) {
-                array_pop($this->_sequence);
-            }
-        }
+    /**
+     *    Creates a new empty history list.
+     *    @return SimpleBrowserHistory    New list.
+     *    @access protected
+     */
+    function &_createHistory() {
+        $history = &new SimpleBrowserHistory();
+        return $history;
     }
 
     /**
-     *    Simulated web browser. This is an aggregate of
-     *    the user agent, the HTML parsing, request history
-     *    and the last header set.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleBrowser {
-        var $_user_agent;
-        var $_page;
-        var $_history;
-        var $_ignore_frames;
-        var $_maximum_nested_frames;
-
-        /**
-         *    Starts with a fresh browser with no
-         *    cookie or any other state information. The
-         *    exception is that a default proxy will be
-         *    set up if specified in the options.
-         *    @access public
-         */
-        function SimpleBrowser() {
-            $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;
-        }
+     *    Disables frames support. Frames will not be fetched
+     *    and the frameset page will be used instead.
+     *    @access public
+     */
+    function ignoreFrames() {
+        $this->_ignore_frames = true;
+    }
 
-        /**
-         *    Creates the underlying user agent.
-         *    @return SimpleFetcher    Content fetcher.
-         *    @access protected
-         */
-        function &_createUserAgent() {
-            $user_agent = &new SimpleUserAgent();
-            return $user_agent;
-        }
+    /**
+     *    Enables frames support. Frames will be fetched from
+     *    now on.
+     *    @access public
+     */
+    function useFrames() {
+        $this->_ignore_frames = false;
+    }
+    
+    /**
+     *    Switches off cookie sending and recieving.
+     *    @access public
+     */
+    function ignoreCookies() {
+        $this->_user_agent->ignoreCookies();
+    }
+    
+    /**
+     *    Switches back on the cookie sending and recieving.
+     *    @access public
+     */
+    function useCookies() {
+        $this->_user_agent->useCookies();
+    }
 
-        /**
-         *    Creates a new empty history list.
-         *    @return SimpleBrowserHistory    New list.
-         *    @access protected
-         */
-        function &_createHistory() {
-            $history = &new SimpleBrowserHistory();
-            return $history;
+    /**
+     *    Parses the raw content into a page. Will load further
+     *    frame pages unless frames are disabled.
+     *    @param SimpleHttpResponse $response    Response from fetch.
+     *    @param integer $depth                  Nested frameset depth.
+     *    @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)) {
+            return $page;
         }
-
-        /**
-         *    Disables frames support. Frames will not be fetched
-         *    and the frameset page will be used instead.
-         *    @access public
-         */
-        function ignoreFrames() {
-            $this->_ignore_frames = true;
+        $frameset = &new SimpleFrameset($page);
+        foreach ($page->getFrameset() as $key => $url) {
+            $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;
+    }
 
-        /**
-         *    Enables frames support. Frames will be fetched from
-         *    now on.
-         *    @access public
-         */
-        function useFrames() {
-            $this->_ignore_frames = false;
-        }
-        
-        /**
-         *    Switches off cookie sending and recieving.
-         *    @access public
-         */
-        function ignoreCookies() {
-            $this->_user_agent->ignoreCookies();
-        }
-        
-        /**
-         *    Switches back on the cookie sending and recieving.
-         *    @access public
-         */
-        function useCookies() {
-            $this->_user_agent->useCookies();
-        }
+    /**
+     *    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.
+     *    @param integer $depth                 Nested frameset depth protection.
+     *    @return SimplePage                    Parsed page.
+     *    @access private
+     */
+    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 $page;
+    }
 
-        /**
-         *    Parses the raw content into a page. Will load further
-         *    frame pages unless frames are disabled.
-         *    @param SimpleHttpResponse $response    Response from fetch.
-         *    @param integer $depth                  Nested frameset depth.
-         *    @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)) {
-                return $page;
-            }
-            $frameset = &new SimpleFrameset($page);
-            foreach ($page->getFrameset() as $key => $url) {
-                $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;
+    /**
+     *    Fetches a page or a single frame if that is the current
+     *    focus.
+     *    @param SimpleUrl $url                   Target to fetch.
+     *    @param SimpleEncoding $parameters       GET/POST parameters.
+     *    @return string                          Raw content of page.
+     *    @access private
+     */
+    function _load($url, $parameters) {
+        $frame = $url->getTarget();
+        if (! $frame || ! $this->_page->hasFrames() || (strtolower($frame) == '_top')) {
+            return $this->_loadPage($url, $parameters);
         }
+        return $this->_loadFrame(array($frame), $url, $parameters);
+    }
 
-        /**
-         *    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.
-         *    @param integer $depth                 Nested frameset depth protection.
-         *    @return SimplePage                    Parsed page.
-         *    @access private
-         */
-        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 $page;
-        }
+    /**
+     *    Fetches a page and makes it the current page/frame.
+     *    @param string/SimpleUrl $url            Target to fetch as string.
+     *    @param SimplePostEncoding $parameters   POST parameters.
+     *    @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();
+    }
 
-        /**
-         *    Fetches a page or a single frame if that is the current
-         *    focus.
-         *    @param SimpleUrl $url                   Target to fetch.
-         *    @param SimpleEncoding $parameters       GET/POST parameters.
-         *    @return string                          Raw content of page.
-         *    @access private
-         */
-        function _load($url, $parameters) {
-            $frame = $url->getTarget();
-            if (! $frame || ! $this->_page->hasFrames() || (strtolower($frame) == '_top')) {
-                return $this->_loadPage($url, $parameters);
-            }
-            return $this->_loadFrame(array($frame), $url, $parameters);
-        }
+    /**
+     *    Fetches a frame into the existing frameset replacing the
+     *    original.
+     *    @param array $frames                    List of names to drill down.
+     *    @param string/SimpleUrl $url            Target to fetch as string.
+     *    @param SimpleFormEncoding $parameters   POST parameters.
+     *    @return string                          Raw content of page.
+     *    @access private
+     */
+    function _loadFrame($frames, $url, $parameters) {
+        $page = &$this->_fetch($url, $parameters);
+        $this->_page->setFrame($frames, $page);
+        return $page->getRaw();
+    }
 
-        /**
-         *    Fetches a page and makes it the current page/frame.
-         *    @param string/SimpleUrl $url            Target to fetch as string.
-         *    @param SimplePostEncoding $parameters   POST parameters.
-         *    @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();
-        }
+    /**
+     *    Removes expired and temporary cookies as if
+     *    the browser was closed and re-opened.
+     *    @param string/integer $date   Time when session restarted.
+     *                                  If omitted then all persistent
+     *                                  cookies are kept.
+     *    @access public
+     */
+    function restart($date = false) {
+        $this->_user_agent->restart($date);
+    }
 
-        /**
-         *    Fetches a frame into the existing frameset replacing the
-         *    original.
-         *    @param array $frames                    List of names to drill down.
-         *    @param string/SimpleUrl $url            Target to fetch as string.
-         *    @param SimpleFormEncoding $parameters   POST parameters.
-         *    @return string                          Raw content of page.
-         *    @access private
-         */
-        function _loadFrame($frames, $url, $parameters) {
-            $page = &$this->_fetch($url, $parameters);
-            $this->_page->setFrame($frames, $page);
-        }
+    /**
+     *    Adds a header to every fetch.
+     *    @param string $header       Header line to add to every
+     *                                request until cleared.
+     *    @access public
+     */
+    function addHeader($header) {
+        $this->_user_agent->addHeader($header);
+    }
 
-        /**
-         *    Removes expired and temporary cookies as if
-         *    the browser was closed and re-opened.
-         *    @param string/integer $date   Time when session restarted.
-         *                                  If omitted then all persistent
-         *                                  cookies are kept.
-         *    @access public
-         */
-        function restart($date = false) {
-            $this->_user_agent->restart($date);
-        }
+    /**
+     *    Ages the cookies by the specified time.
+     *    @param integer $interval    Amount in seconds.
+     *    @access public
+     */
+    function ageCookies($interval) {
+        $this->_user_agent->ageCookies($interval);
+    }
 
-        /**
-         *    Adds a header to every fetch.
-         *    @param string $header       Header line to add to every
-         *                                request until cleared.
-         *    @access public
-         */
-        function addHeader($header) {
-            $this->_user_agent->addHeader($header);
-        }
+    /**
+     *    Sets an additional cookie. If a cookie has
+     *    the same name and path it is replaced.
+     *    @param string $name       Cookie key.
+     *    @param string $value      Value of cookie.
+     *    @param string $host       Host upon which the cookie is valid.
+     *    @param string $path       Cookie path if not host wide.
+     *    @param string $expiry     Expiry date.
+     *    @access public
+     */
+    function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
+        $this->_user_agent->setCookie($name, $value, $host, $path, $expiry);
+    }
 
-        /**
-         *    Ages the cookies by the specified time.
-         *    @param integer $interval    Amount in seconds.
-         *    @access public
-         */
-        function ageCookies($interval) {
-            $this->_user_agent->ageCookies($interval);
-        }
+    /**
+     *    Reads the most specific cookie value from the
+     *    browser cookies.
+     *    @param string $host        Host to search.
+     *    @param string $path        Applicable path.
+     *    @param string $name        Name of cookie to read.
+     *    @return string             False if not present, else the
+     *                               value as a string.
+     *    @access public
+     */
+    function getCookieValue($host, $path, $name) {
+        return $this->_user_agent->getCookieValue($host, $path, $name);
+    }
 
-        /**
-         *    Sets an additional cookie. If a cookie has
-         *    the same name and path it is replaced.
-         *    @param string $name       Cookie key.
-         *    @param string $value      Value of cookie.
-         *    @param string $host       Host upon which the cookie is valid.
-         *    @param string $path       Cookie path if not host wide.
-         *    @param string $expiry     Expiry date.
-         *    @access public
-         */
-        function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
-            $this->_user_agent->setCookie($name, $value, $host, $path, $expiry);
-        }
+    /**
+     *    Reads the current cookies for the current URL.
+     *    @param string $name   Key of cookie to find.
+     *    @return string        Null if there is no current URL, false
+     *                          if the cookie is not set.
+     *    @access public
+     */
+    function getCurrentCookieValue($name) {
+        return $this->_user_agent->getBaseCookieValue($name, $this->_page->getUrl());
+    }
 
-        /**
-         *    Reads the most specific cookie value from the
-         *    browser cookies.
-         *    @param string $host        Host to search.
-         *    @param string $path        Applicable path.
-         *    @param string $name        Name of cookie to read.
-         *    @return string             False if not present, else the
-         *                               value as a string.
-         *    @access public
-         */
-        function getCookieValue($host, $path, $name) {
-            return $this->_user_agent->getCookieValue($host, $path, $name);
-        }
+    /**
+     *    Sets the maximum number of redirects before
+     *    a page will be loaded anyway.
+     *    @param integer $max        Most hops allowed.
+     *    @access public
+     */
+    function setMaximumRedirects($max) {
+        $this->_user_agent->setMaximumRedirects($max);
+    }
 
-        /**
-         *    Reads the current cookies for the current URL.
-         *    @param string $name   Key of cookie to find.
-         *    @return string        Null if there is no current URL, false
-         *                          if the cookie is not set.
-         *    @access public
-         */
-        function getCurrentCookieValue($name) {
-            return $this->_user_agent->getBaseCookieValue($name, $this->_page->getUrl());
-        }
+    /**
+     *    Sets the maximum number of nesting of framed pages
+     *    within a framed page to prevent loops.
+     *    @param integer $max        Highest depth allowed.
+     *    @access public
+     */
+    function setMaximumNestedFrames($max) {
+        $this->_maximum_nested_frames = $max;
+    }
 
-        /**
-         *    Sets the maximum number of redirects before
-         *    a page will be loaded anyway.
-         *    @param integer $max        Most hops allowed.
-         *    @access public
-         */
-        function setMaximumRedirects($max) {
-            $this->_user_agent->setMaximumRedirects($max);
-        }
+    /**
+     *    Sets the socket timeout for opening a connection.
+     *    @param integer $timeout      Maximum time in seconds.
+     *    @access public
+     */
+    function setConnectionTimeout($timeout) {
+        $this->_user_agent->setConnectionTimeout($timeout);
+    }
 
-        /**
-         *    Sets the maximum number of nesting of framed pages
-         *    within a framed page to prevent loops.
-         *    @param integer $max        Highest depth allowed.
-         *    @access public
-         */
-        function setMaximumNestedFrames($max) {
-            $this->_maximum_nested_frames = $max;
-        }
+    /**
+     *    Sets proxy to use on all requests for when
+     *    testing from behind a firewall. Set URL
+     *    to false to disable.
+     *    @param string $proxy        Proxy URL.
+     *    @param string $username     Proxy username for authentication.
+     *    @param string $password     Proxy password for authentication.
+     *    @access public
+     */
+    function useProxy($proxy, $username = false, $password = false) {
+        $this->_user_agent->useProxy($proxy, $username, $password);
+    }
 
-        /**
-         *    Sets the socket timeout for opening a connection.
-         *    @param integer $timeout      Maximum time in seconds.
-         *    @access public
-         */
-        function setConnectionTimeout($timeout) {
-            $this->_user_agent->setConnectionTimeout($timeout);
+    /**
+     *    Fetches the page content with a HEAD request.
+     *    Will affect cookies, but will not change the base URL.
+     *    @param string/SimpleUrl $url                Target to fetch as string.
+     *    @param hash/SimpleHeadEncoding $parameters  Additional parameters for
+     *                                                HEAD request.
+     *    @return boolean                             True if successful.
+     *    @access public
+     */
+    function head($url, $parameters = false) {
+        if (! is_object($url)) {
+            $url = new SimpleUrl($url);
         }
-
-        /**
-         *    Sets proxy to use on all requests for when
-         *    testing from behind a firewall. Set URL
-         *    to false to disable.
-         *    @param string $proxy        Proxy URL.
-         *    @param string $username     Proxy username for authentication.
-         *    @param string $password     Proxy password for authentication.
-         *    @access public
-         */
-        function useProxy($proxy, $username = false, $password = false) {
-            $this->_user_agent->useProxy($proxy, $username, $password);
+        if ($this->getUrl()) {
+            $url = $url->makeAbsolute($this->getUrl());
         }
+        $response = &$this->_user_agent->fetchResponse($url, new SimpleHeadEncoding($parameters));
+        return ! $response->isError();
+    }
 
-        /**
-         *    Fetches the page content with a HEAD request.
-         *    Will affect cookies, but will not change the base URL.
-         *    @param string/SimpleUrl $url                Target to fetch as string.
-         *    @param hash/SimpleHeadEncoding $parameters  Additional parameters for
-         *                                                HEAD request.
-         *    @return boolean                             True if successful.
-         *    @access public
-         */
-        function head($url, $parameters = false) {
-            if (! is_object($url)) {
-                $url = new SimpleUrl($url);
-            }
-            if ($this->getUrl()) {
-                $url = $url->makeAbsolute($this->getUrl());
-            }
-            $response = &$this->_user_agent->fetchResponse($url, new SimpleHeadEncoding($parameters));
-            return ! $response->isError();
+    /**
+     *    Fetches the page content with a simple GET request.
+     *    @param string/SimpleUrl $url                Target to fetch.
+     *    @param hash/SimpleFormEncoding $parameters  Additional parameters for
+     *                                                GET request.
+     *    @return string                              Content of page or false.
+     *    @access public
+     */
+    function get($url, $parameters = false) {
+        if (! is_object($url)) {
+            $url = new SimpleUrl($url);
+        }
+        if ($this->getUrl()) {
+            $url = $url->makeAbsolute($this->getUrl());
         }
+        return $this->_load($url, new SimpleGetEncoding($parameters));
+    }
 
-        /**
-         *    Fetches the page content with a simple GET request.
-         *    @param string/SimpleUrl $url                Target to fetch.
-         *    @param hash/SimpleFormEncoding $parameters  Additional parameters for
-         *                                                GET request.
-         *    @return string                              Content of page or false.
-         *    @access public
-         */
-        function get($url, $parameters = false) {
-            if (! is_object($url)) {
-                $url = new SimpleUrl($url);
-            }
-            if ($this->getUrl()) {
-                $url = $url->makeAbsolute($this->getUrl());
-            }
-            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.
+     *    @return string                              Content of page.
+     *    @access public
+     */
+    function post($url, $parameters = false) {
+        if (! is_object($url)) {
+            $url = new SimpleUrl($url);
+        }
+        if ($this->getUrl()) {
+            $url = $url->makeAbsolute($this->getUrl());
         }
+        return $this->_load($url, new SimplePostEncoding($parameters));
+    }
 
-        /**
-         *    Fetches the page content with a POST request.
-         *    @param string/SimpleUrl $url                Target to fetch as string.
-         *    @param hash/SimpleFormEncoding $parameters  POST parameters.
-         *    @return string                              Content of page.
-         *    @access public
-         */
-        function post($url, $parameters = false) {
-            if (! is_object($url)) {
-                $url = new SimpleUrl($url);
-            }
-            if ($this->getUrl()) {
-                $url = $url->makeAbsolute($this->getUrl());
-            }
-            return $this->_load($url, new SimplePostEncoding($parameters));
+    /**
+     *    Equivalent to hitting the retry button on the
+     *    browser. Will attempt to repeat the page fetch. If
+     *    there is no history to repeat it will give false.
+     *    @return string/boolean   Content if fetch succeeded
+     *                             else false.
+     *    @access public
+     */
+    function retry() {
+        $frames = $this->_page->getFrameFocus();
+        if (count($frames) > 0) {
+            $this->_loadFrame(
+                    $frames,
+                    $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();
         }
+        return false;
+    }
 
-        /**
-         *    Equivalent to hitting the retry button on the
-         *    browser. Will attempt to repeat the page fetch. If
-         *    there is no history to repeat it will give false.
-         *    @return string/boolean   Content if fetch succeeded
-         *                             else false.
-         *    @access public
-         */
-        function retry() {
-            $frames = $this->_page->getFrameFocus();
-            if (count($frames) > 0) {
-                $this->_loadFrame(
-                        $frames,
-                        $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();
-            }
+    /**
+     *    Equivalent to hitting the back button on the
+     *    browser. The browser history is unchanged on
+     *    failure. The page content is refetched as there
+     *    is no concept of content caching in SimpleTest.
+     *    @return boolean     True if history entry and
+     *                        fetch succeeded
+     *    @access public
+     */
+    function back() {
+        if (! $this->_history->back()) {
             return false;
         }
-
-        /**
-         *    Equivalent to hitting the back button on the
-         *    browser. The browser history is unchanged on
-         *    failure. The page content is refetched as there
-         *    is no concept of content caching in SimpleTest.
-         *    @return boolean     True if history entry and
-         *                        fetch succeeded
-         *    @access public
-         */
-        function back() {
-            if (! $this->_history->back()) {
-                return false;
-            }
-            $content = $this->retry();
-            if (! $content) {
-                $this->_history->forward();
-            }
-            return $content;
+        $content = $this->retry();
+        if (! $content) {
+            $this->_history->forward();
         }
+        return $content;
+    }
 
-        /**
-         *    Equivalent to hitting the forward button on the
-         *    browser. The browser history is unchanged on
-         *    failure. The page content is refetched as there
-         *    is no concept of content caching in SimpleTest.
-         *    @return boolean     True if history entry and
-         *                        fetch succeeded
-         *    @access public
-         */
-        function forward() {
-            if (! $this->_history->forward()) {
-                return false;
-            }
-            $content = $this->retry();
-            if (! $content) {
-                $this->_history->back();
-            }
-            return $content;
+    /**
+     *    Equivalent to hitting the forward button on the
+     *    browser. The browser history is unchanged on
+     *    failure. The page content is refetched as there
+     *    is no concept of content caching in SimpleTest.
+     *    @return boolean     True if history entry and
+     *                        fetch succeeded
+     *    @access public
+     */
+    function forward() {
+        if (! $this->_history->forward()) {
+            return false;
         }
-
-        /**
-         *    Retries a request after setting the authentication
-         *    for the current realm.
-         *    @param string $username    Username for realm.
-         *    @param string $password    Password for realm.
-         *    @return boolean            True if successful fetch. Note
-         *                               that authentication may still have
-         *                               failed.
-         *    @access public
-         */
-        function authenticate($username, $password) {
-            if (! $this->_page->getRealm()) {
-                return false;
-            }
-            $url = $this->_page->getUrl();
-            if (! $url) {
-                return false;
-            }
-            $this->_user_agent->setIdentity(
-                    $url->getHost(),
-                    $this->_page->getRealm(),
-                    $username,
-                    $password);
-            return $this->retry();
+        $content = $this->retry();
+        if (! $content) {
+            $this->_history->back();
         }
+        return $content;
+    }
 
-        /**
-         *    Accessor for a breakdown of the frameset.
-         *    @return array   Hash tree of frames by name
-         *                    or index if no name.
-         *    @access public
-         */
-        function getFrames() {
-            return $this->_page->getFrames();
+    /**
+     *    Retries a request after setting the authentication
+     *    for the current realm.
+     *    @param string $username    Username for realm.
+     *    @param string $password    Password for realm.
+     *    @return boolean            True if successful fetch. Note
+     *                               that authentication may still have
+     *                               failed.
+     *    @access public
+     */
+    function authenticate($username, $password) {
+        if (! $this->_page->getRealm()) {
+            return false;
         }
-
-        /**
-         *    Accessor for current frame focus. Will be
-         *    false if no frame has focus.
-         *    @return integer/string/boolean    Label if any, otherwise
-         *                                      the position in the frameset
-         *                                      or false if none.
-         *    @access public
-         */
-        function getFrameFocus() {
-            return $this->_page->getFrameFocus();
+        $url = $this->_page->getUrl();
+        if (! $url) {
+            return false;
         }
+        $this->_user_agent->setIdentity(
+                $url->getHost(),
+                $this->_page->getRealm(),
+                $username,
+                $password);
+        return $this->retry();
+    }
 
-        /**
-         *    Sets the focus by index. The integer index starts from 1.
-         *    @param integer $choice    Chosen frame.
-         *    @return boolean           True if frame exists.
-         *    @access public
-         */
-        function setFrameFocusByIndex($choice) {
-            return $this->_page->setFrameFocusByIndex($choice);
-        }
+    /**
+     *    Accessor for a breakdown of the frameset.
+     *    @return array   Hash tree of frames by name
+     *                    or index if no name.
+     *    @access public
+     */
+    function getFrames() {
+        return $this->_page->getFrames();
+    }
 
-        /**
-         *    Sets the focus by name.
-         *    @param string $name    Chosen frame.
-         *    @return boolean        True if frame exists.
-         *    @access public
-         */
-        function setFrameFocus($name) {
-            return $this->_page->setFrameFocus($name);
-        }
+    /**
+     *    Accessor for current frame focus. Will be
+     *    false if no frame has focus.
+     *    @return integer/string/boolean    Label if any, otherwise
+     *                                      the position in the frameset
+     *                                      or false if none.
+     *    @access public
+     */
+    function getFrameFocus() {
+        return $this->_page->getFrameFocus();
+    }
 
-        /**
-         *    Clears the frame focus. All frames will be searched
-         *    for content.
-         *    @access public
-         */
-        function clearFrameFocus() {
-            return $this->_page->clearFrameFocus();
-        }
+    /**
+     *    Sets the focus by index. The integer index starts from 1.
+     *    @param integer $choice    Chosen frame.
+     *    @return boolean           True if frame exists.
+     *    @access public
+     */
+    function setFrameFocusByIndex($choice) {
+        return $this->_page->setFrameFocusByIndex($choice);
+    }
 
-        /**
-         *    Accessor for last error.
-         *    @return string        Error from last response.
-         *    @access public
-         */
-        function getTransportError() {
-            return $this->_page->getTransportError();
-        }
+    /**
+     *    Sets the focus by name.
+     *    @param string $name    Chosen frame.
+     *    @return boolean        True if frame exists.
+     *    @access public
+     */
+    function setFrameFocus($name) {
+        return $this->_page->setFrameFocus($name);
+    }
 
-        /**
-         *    Accessor for current MIME type.
-         *    @return string    MIME type as string; e.g. 'text/html'
-         *    @access public
-         */
-        function getMimeType() {
-            return $this->_page->getMimeType();
-        }
+    /**
+     *    Clears the frame focus. All frames will be searched
+     *    for content.
+     *    @access public
+     */
+    function clearFrameFocus() {
+        return $this->_page->clearFrameFocus();
+    }
 
-        /**
-         *    Accessor for last response code.
-         *    @return integer    Last HTTP response code received.
-         *    @access public
-         */
-        function getResponseCode() {
-            return $this->_page->getResponseCode();
-        }
+    /**
+     *    Accessor for last error.
+     *    @return string        Error from last response.
+     *    @access public
+     */
+    function getTransportError() {
+        return $this->_page->getTransportError();
+    }
 
-        /**
-         *    Accessor for last Authentication type. Only valid
-         *    straight after a challenge (401).
-         *    @return string    Description of challenge type.
-         *    @access public
-         */
-        function getAuthentication() {
-            return $this->_page->getAuthentication();
-        }
+    /**
+     *    Accessor for current MIME type.
+     *    @return string    MIME type as string; e.g. 'text/html'
+     *    @access public
+     */
+    function getMimeType() {
+        return $this->_page->getMimeType();
+    }
 
-        /**
-         *    Accessor for last Authentication realm. Only valid
-         *    straight after a challenge (401).
-         *    @return string    Name of security realm.
-         *    @access public
-         */
-        function getRealm() {
-            return $this->_page->getRealm();
-        }
+    /**
+     *    Accessor for last response code.
+     *    @return integer    Last HTTP response code received.
+     *    @access public
+     */
+    function getResponseCode() {
+        return $this->_page->getResponseCode();
+    }
 
-        /**
-         *    Accessor for current URL of page or frame if
-         *    focused.
-         *    @return string    Location of current page or frame as
-         *                      a string.
-         */
-        function getUrl() {
-            $url = $this->_page->getUrl();
-            return $url ? $url->asString() : false;
-        }
+    /**
+     *    Accessor for last Authentication type. Only valid
+     *    straight after a challenge (401).
+     *    @return string    Description of challenge type.
+     *    @access public
+     */
+    function getAuthentication() {
+        return $this->_page->getAuthentication();
+    }
 
-        /**
-         *    Accessor for raw bytes sent down the wire.
-         *    @return string      Original text sent.
-         *    @access public
-         */
-        function getRequest() {
-            return $this->_page->getRequest();
-        }
+    /**
+     *    Accessor for last Authentication realm. Only valid
+     *    straight after a challenge (401).
+     *    @return string    Name of security realm.
+     *    @access public
+     */
+    function getRealm() {
+        return $this->_page->getRealm();
+    }
 
-        /**
-         *    Accessor for raw header information.
-         *    @return string      Header block.
-         *    @access public
-         */
-        function getHeaders() {
-            return $this->_page->getHeaders();
-        }
+    /**
+     *    Accessor for current URL of page or frame if
+     *    focused.
+     *    @return string    Location of current page or frame as
+     *                      a string.
+     */
+    function getUrl() {
+        $url = $this->_page->getUrl();
+        return $url ? $url->asString() : false;
+    }
 
-        /**
-         *    Accessor for raw page information.
-         *    @return string      Original text content of web page.
-         *    @access public
-         */
-        function getContent() {
-            return $this->_page->getRaw();
-        }
+    /**
+     *    Accessor for base URL of page if set via BASE tag
+     *    @return string    base URL
+     */
+    function getBaseUrl() {
+        $url = $this->_page->getBaseUrl();
+        return $url ? $url->asString() : false;
+    }
 
-        /**
-         *    Accessor for plain text version of the page.
-         *    @return string      Normalised text representation.
-         *    @access public
-         */
-        function getContentAsText() {
-            return $this->_page->getText();
-        }
+    /**
+     *    Accessor for raw bytes sent down the wire.
+     *    @return string      Original text sent.
+     *    @access public
+     */
+    function getRequest() {
+        return $this->_page->getRequest();
+    }
 
-        /**
-         *    Accessor for parsed title.
-         *    @return string     Title or false if no title is present.
-         *    @access public
-         */
-        function getTitle() {
-            return $this->_page->getTitle();
-        }
+    /**
+     *    Accessor for raw header information.
+     *    @return string      Header block.
+     *    @access public
+     */
+    function getHeaders() {
+        return $this->_page->getHeaders();
+    }
 
-        /**
-         *    Accessor for a list of all fixed links in current page.
-         *    @return array   List of urls with scheme of
-         *                    http or https and hostname.
-         *    @access public
-         */
-        function getAbsoluteUrls() {
-            return $this->_page->getAbsoluteUrls();
-        }
+    /**
+     *    Accessor for raw page information.
+     *    @return string      Original text content of web page.
+     *    @access public
+     */
+    function getContent() {
+        return $this->_page->getRaw();
+    }
 
-        /**
-         *    Accessor for a list of all relative links.
-         *    @return array      List of urls without hostname.
-         *    @access public
-         */
-        function getRelativeUrls() {
-            return $this->_page->getRelativeUrls();
-        }
+    /**
+     *    Accessor for plain text version of the page.
+     *    @return string      Normalised text representation.
+     *    @access public
+     */
+    function getContentAsText() {
+        return $this->_page->getText();
+    }
 
-        /**
-         *    Sets all form fields with that name.
-         *    @param string $label   Name or label of field in forms.
-         *    @param string $value   New value of field.
-         *    @return boolean        True if field exists, otherwise false.
-         *    @access public
-         */
-        function setField($label, $value) {
-            return $this->_page->setField(new SimpleByLabelOrName($label), $value);
-        }
+    /**
+     *    Accessor for parsed title.
+     *    @return string     Title or false if no title is present.
+     *    @access public
+     */
+    function getTitle() {
+        return $this->_page->getTitle();
+    }
 
-        /**
-         *    Sets all form fields with that name. Will use label if
-         *    one is available (not yet implemented).
-         *    @param string $name    Name of field in forms.
-         *    @param string $value   New value of field.
-         *    @return boolean        True if field exists, otherwise false.
-         *    @access public
-         */
-        function setFieldByName($name, $value) {
-            return $this->_page->setField(new SimpleByName($name), $value);
-        }
+    /**
+     *    Accessor for a list of all links in current page.
+     *    @return array   List of urls with scheme of
+     *                    http or https and hostname.
+     *    @access public
+     */
+    function getUrls() {
+        return $this->_page->getUrls();
+    }
 
-        /**
-         *    Sets all form fields with that id attribute.
-         *    @param string/integer $id   Id of field in forms.
-         *    @param string $value        New value of field.
-         *    @return boolean             True if field exists, otherwise false.
-         *    @access public
-         */
-        function setFieldById($id, $value) {
-            return $this->_page->setField(new SimpleById($id), $value);
-        }
+    /**
+     *    Sets all form fields with that name.
+     *    @param string $label   Name or label of field in forms.
+     *    @param string $value   New value of field.
+     *    @return boolean        True if field exists, otherwise false.
+     *    @access public
+     */
+    function setField($label, $value, $position=false) {
+        return $this->_page->setField(new SimpleByLabelOrName($label), $value, $position);
+    }
 
-        /**
-         *    Accessor for a form element value within the page.
-         *    Finds the first match.
-         *    @param string $label       Field label.
-         *    @return string/boolean     A value if the field is
-         *                               present, false if unchecked
-         *                               and null if missing.
-         *    @access public
-         */
-        function getField($label) {
-            return $this->_page->getField(new SimpleByLabelOrName($label));
-        }
+    /**
+     *    Sets all form fields with that name. Will use label if
+     *    one is available (not yet implemented).
+     *    @param string $name    Name of field in forms.
+     *    @param string $value   New value of field.
+     *    @return boolean        True if field exists, otherwise false.
+     *    @access public
+     */
+    function setFieldByName($name, $value, $position=false) {
+        return $this->_page->setField(new SimpleByName($name), $value, $position);
+    }
 
-        /**
-         *    Accessor for a form element value within the page.
-         *    Finds the first match.
-         *    @param string $name        Field name.
-         *    @return string/boolean     A string if the field is
-         *                               present, false if unchecked
-         *                               and null if missing.
-         *    @access public
-         */
-        function getFieldByName($name) {
-            return $this->_page->getField(new SimpleByName($name));
-        }
+    /**
+     *    Sets all form fields with that id attribute.
+     *    @param string/integer $id   Id of field in forms.
+     *    @param string $value        New value of field.
+     *    @return boolean             True if field exists, otherwise false.
+     *    @access public
+     */
+    function setFieldById($id, $value) {
+        return $this->_page->setField(new SimpleById($id), $value);
+    }
 
-        /**
-         *    Accessor for a form element value within the page.
-         *    @param string/integer $id  Id of field in forms.
-         *    @return string/boolean     A string if the field is
-         *                               present, false if unchecked
-         *                               and null if missing.
-         *    @access public
-         */
-        function getFieldById($id) {
-            return $this->_page->getField(new SimpleById($id));
-        }
+    /**
+     *    Accessor for a form element value within the page.
+     *    Finds the first match.
+     *    @param string $label       Field label.
+     *    @return string/boolean     A value if the field is
+     *                               present, false if unchecked
+     *                               and null if missing.
+     *    @access public
+     */
+    function getField($label) {
+        return $this->_page->getField(new SimpleByLabelOrName($label));
+    }
 
-        /**
-         *    Clicks the submit button by label. The owning
-         *    form will be submitted by this.
-         *    @param string $label    Button label. An unlabeled
-         *                            button can be triggered by 'Submit'.
-         *    @param hash $additional Additional form data.
-         *    @return string/boolean  Page on success.
-         *    @access public
-         */
-        function clickSubmit($label = 'Submit', $additional = false) {
-            if (! ($form = &$this->_page->getFormBySubmit(new SimpleByLabel($label)))) {
-                return false;
-            }
-            $success = $this->_load(
-                    $form->getAction(),
-                    $form->submitButton(new SimpleByLabel($label), $additional));
-            return ($success ? $this->getContent() : $success);
-        }
+    /**
+     *    Accessor for a form element value within the page.
+     *    Finds the first match.
+     *    @param string $name        Field name.
+     *    @return string/boolean     A string if the field is
+     *                               present, false if unchecked
+     *                               and null if missing.
+     *    @access public
+     */
+    function getFieldByName($name) {
+        return $this->_page->getField(new SimpleByName($name));
+    }
 
-        /**
-         *    Clicks the submit button by name attribute. The owning
-         *    form will be submitted by this.
-         *    @param string $name     Button name.
-         *    @param hash $additional Additional form data.
-         *    @return string/boolean  Page on success.
-         *    @access public
-         */
-        function clickSubmitByName($name, $additional = false) {
-            if (! ($form = &$this->_page->getFormBySubmit(new SimpleByName($name)))) {
-                return false;
-            }
-            $success = $this->_load(
-                    $form->getAction(),
-                    $form->submitButton(new SimpleByName($name), $additional));
-            return ($success ? $this->getContent() : $success);
-        }
+    /**
+     *    Accessor for a form element value within the page.
+     *    @param string/integer $id  Id of field in forms.
+     *    @return string/boolean     A string if the field is
+     *                               present, false if unchecked
+     *                               and null if missing.
+     *    @access public
+     */
+    function getFieldById($id) {
+        return $this->_page->getField(new SimpleById($id));
+    }
 
-        /**
-         *    Clicks the submit button by ID attribute of the button
-         *    itself. The owning form will be submitted by this.
-         *    @param string $id       Button ID.
-         *    @param hash $additional Additional form data.
-         *    @return string/boolean  Page on success.
-         *    @access public
-         */
-        function clickSubmitById($id, $additional = false) {
-            if (! ($form = &$this->_page->getFormBySubmit(new SimpleById($id)))) {
-                return false;
-            }
-            $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.
-         *    @param string $label    Button label.
-         *    @return boolean         True if present.
-         *    @access public
-         */
-        function isSubmit($label) {
-            return (boolean)$this->_page->getFormBySubmit(new SimpleByLabel($label));
+    /**
+     *    Clicks the submit button by label. The owning
+     *    form will be submitted by this.
+     *    @param string $label    Button label. An unlabeled
+     *                            button can be triggered by 'Submit'.
+     *    @param hash $additional Additional form data.
+     *    @return string/boolean  Page on success.
+     *    @access public
+     */
+    function clickSubmit($label = 'Submit', $additional = false) {
+        if (! ($form = &$this->_page->getFormBySubmit(new SimpleByLabel($label)))) {
+            return false;
         }
+        $success = $this->_load(
+                $form->getAction(),
+                $form->submitButton(new SimpleByLabel($label), $additional));
+        return ($success ? $this->getContent() : $success);
+    }
 
-        /**
-         *    Clicks the submit image by some kind of label. Usually
-         *    the alt tag or the nearest equivalent. The owning
-         *    form will be submitted by this. Clicking outside of
-         *    the boundary of the coordinates will result in
-         *    a failure.
-         *    @param string $label    ID attribute of button.
-         *    @param integer $x       X-coordinate of imaginary click.
-         *    @param integer $y       Y-coordinate of imaginary click.
-         *    @param hash $additional Additional form data.
-         *    @return string/boolean  Page on success.
-         *    @access public
-         */
-        function clickImage($label, $x = 1, $y = 1, $additional = false) {
-            if (! ($form = &$this->_page->getFormByImage(new SimpleByLabel($label)))) {
-                return false;
-            }
-            $success = $this->_load(
-                    $form->getAction(),
-                    $form->submitImage(new SimpleByLabel($label), $x, $y, $additional));
-            return ($success ? $this->getContent() : $success);
+    /**
+     *    Clicks the submit button by name attribute. The owning
+     *    form will be submitted by this.
+     *    @param string $name     Button name.
+     *    @param hash $additional Additional form data.
+     *    @return string/boolean  Page on success.
+     *    @access public
+     */
+    function clickSubmitByName($name, $additional = false) {
+        if (! ($form = &$this->_page->getFormBySubmit(new SimpleByName($name)))) {
+            return false;
         }
+        $success = $this->_load(
+                $form->getAction(),
+                $form->submitButton(new SimpleByName($name), $additional));
+        return ($success ? $this->getContent() : $success);
+    }
 
-        /**
-         *    Clicks the submit image by the name. Usually
-         *    the alt tag or the nearest equivalent. The owning
-         *    form will be submitted by this. Clicking outside of
-         *    the boundary of the coordinates will result in
-         *    a failure.
-         *    @param string $name     Name attribute of button.
-         *    @param integer $x       X-coordinate of imaginary click.
-         *    @param integer $y       Y-coordinate of imaginary click.
-         *    @param hash $additional Additional form data.
-         *    @return string/boolean  Page on success.
-         *    @access public
-         */
-        function clickImageByName($name, $x = 1, $y = 1, $additional = false) {
-            if (! ($form = &$this->_page->getFormByImage(new SimpleByName($name)))) {
-                return false;
-            }
-            $success = $this->_load(
-                    $form->getAction(),
-                    $form->submitImage(new SimpleByName($name), $x, $y, $additional));
-            return ($success ? $this->getContent() : $success);
+    /**
+     *    Clicks the submit button by ID attribute of the button
+     *    itself. The owning form will be submitted by this.
+     *    @param string $id       Button ID.
+     *    @param hash $additional Additional form data.
+     *    @return string/boolean  Page on success.
+     *    @access public
+     */
+    function clickSubmitById($id, $additional = false) {
+        if (! ($form = &$this->_page->getFormBySubmit(new SimpleById($id)))) {
+            return false;
         }
+        $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.
+     *    @param string $label    Button label.
+     *    @return boolean         True if present.
+     *    @access public
+     */
+    function isSubmit($label) {
+        return (boolean)$this->_page->getFormBySubmit(new SimpleByLabel($label));
+    }
 
-        /**
-         *    Clicks the submit image by ID attribute. The owning
-         *    form will be submitted by this. Clicking outside of
-         *    the boundary of the coordinates will result in
-         *    a failure.
-         *    @param integer/string $id    ID attribute of button.
-         *    @param integer $x            X-coordinate of imaginary click.
-         *    @param integer $y            Y-coordinate of imaginary click.
-         *    @param hash $additional      Additional form data.
-         *    @return string/boolean       Page on success.
-         *    @access public
-         */
-        function clickImageById($id, $x = 1, $y = 1, $additional = false) {
-            if (! ($form = &$this->_page->getFormByImage(new SimpleById($id)))) {
-                return false;
-            }
-            $success = $this->_load(
-                    $form->getAction(),
-                    $form->submitImage(new SimpleById($id), $x, $y, $additional));
-            return ($success ? $this->getContent() : $success);
+    /**
+     *    Clicks the submit image by some kind of label. Usually
+     *    the alt tag or the nearest equivalent. The owning
+     *    form will be submitted by this. Clicking outside of
+     *    the boundary of the coordinates will result in
+     *    a failure.
+     *    @param string $label    ID attribute of button.
+     *    @param integer $x       X-coordinate of imaginary click.
+     *    @param integer $y       Y-coordinate of imaginary click.
+     *    @param hash $additional Additional form data.
+     *    @return string/boolean  Page on success.
+     *    @access public
+     */
+    function clickImage($label, $x = 1, $y = 1, $additional = false) {
+        if (! ($form = &$this->_page->getFormByImage(new SimpleByLabel($label)))) {
+            return false;
         }
-        
-        /**
-         *    Tests to see if an image exists with this
-         *    title or alt text.
-         *    @param string $label    Image text.
-         *    @return boolean         True if present.
-         *    @access public
-         */
-        function isImage($label) {
-            return (boolean)$this->_page->getFormByImage(new SimpleByLabel($label));
+        $success = $this->_load(
+                $form->getAction(),
+                $form->submitImage(new SimpleByLabel($label), $x, $y, $additional));
+        return ($success ? $this->getContent() : $success);
+    }
+
+    /**
+     *    Clicks the submit image by the name. Usually
+     *    the alt tag or the nearest equivalent. The owning
+     *    form will be submitted by this. Clicking outside of
+     *    the boundary of the coordinates will result in
+     *    a failure.
+     *    @param string $name     Name attribute of button.
+     *    @param integer $x       X-coordinate of imaginary click.
+     *    @param integer $y       Y-coordinate of imaginary click.
+     *    @param hash $additional Additional form data.
+     *    @return string/boolean  Page on success.
+     *    @access public
+     */
+    function clickImageByName($name, $x = 1, $y = 1, $additional = false) {
+        if (! ($form = &$this->_page->getFormByImage(new SimpleByName($name)))) {
+            return false;
         }
+        $success = $this->_load(
+                $form->getAction(),
+                $form->submitImage(new SimpleByName($name), $x, $y, $additional));
+        return ($success ? $this->getContent() : $success);
+    }
 
-        /**
-         *    Submits a form by the ID.
-         *    @param string $id       The form ID. No submit button value
-         *                            will be sent.
-         *    @return string/boolean  Page on success.
-         *    @access public
-         */
-        function submitFormById($id) {
-            if (! ($form = &$this->_page->getFormById($id))) {
-                return false;
-            }
-            $success = $this->_load(
-                    $form->getAction(),
-                    $form->submit());
-            return ($success ? $this->getContent() : $success);
+    /**
+     *    Clicks the submit image by ID attribute. The owning
+     *    form will be submitted by this. Clicking outside of
+     *    the boundary of the coordinates will result in
+     *    a failure.
+     *    @param integer/string $id    ID attribute of button.
+     *    @param integer $x            X-coordinate of imaginary click.
+     *    @param integer $y            Y-coordinate of imaginary click.
+     *    @param hash $additional      Additional form data.
+     *    @return string/boolean       Page on success.
+     *    @access public
+     */
+    function clickImageById($id, $x = 1, $y = 1, $additional = false) {
+        if (! ($form = &$this->_page->getFormByImage(new SimpleById($id)))) {
+            return false;
         }
+        $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.
+     *    @param string $label    Image text.
+     *    @return boolean         True if present.
+     *    @access public
+     */
+    function isImage($label) {
+        return (boolean)$this->_page->getFormByImage(new SimpleByLabel($label));
+    }
 
-        /**
-         *    Finds a URL by label. Will find the first link
-         *    found with this link text by default, or a later
-         *    one if an index is given. The match ignores case and
-         *    white space issues.
-         *    @param string $label     Text between the anchor tags.
-         *    @param integer $index    Link position counting from zero.
-         *    @return string/boolean   URL on success.
-         *    @access public
-         */
-        function getLink($label, $index = 0) {
-            $urls = $this->_page->getUrlsByLabel($label);
-            if (count($urls) == 0) {
-                return false;
-            }
-            if (count($urls) < $index + 1) {
-                return false;
-            }
-            return $urls[$index];
+    /**
+     *    Submits a form by the ID.
+     *    @param string $id       The form ID. No submit button value
+     *                            will be sent.
+     *    @return string/boolean  Page on success.
+     *    @access public
+     */
+    function submitFormById($id) {
+        if (! ($form = &$this->_page->getFormById($id))) {
+            return false;
         }
+        $success = $this->_load(
+                $form->getAction(),
+                $form->submit());
+        return ($success ? $this->getContent() : $success);
+    }
 
-        /**
-         *    Follows a link by label. Will click the first link
-         *    found with this link text by default, or a later
-         *    one if an index is given. The match ignores case and
-         *    white space issues.
-         *    @param string $label     Text between the anchor tags.
-         *    @param integer $index    Link position counting from zero.
-         *    @return string/boolean   Page on success.
-         *    @access public
-         */
-        function clickLink($label, $index = 0) {
-            $url = $this->getLink($label, $index);
-            if ($url === false) {
-                return false;
-            }
-            $this->_load($url, new SimpleGetEncoding());
-            return $this->getContent();
+    /**
+     *    Finds a URL by label. Will find the first link
+     *    found with this link text by default, or a later
+     *    one if an index is given. The match ignores case and
+     *    white space issues.
+     *    @param string $label     Text between the anchor tags.
+     *    @param integer $index    Link position counting from zero.
+     *    @return string/boolean   URL on success.
+     *    @access public
+     */
+    function getLink($label, $index = 0) {
+        $urls = $this->_page->getUrlsByLabel($label);
+        if (count($urls) == 0) {
+            return false;
         }
-        
-        /**
-         *    Finds a link by id attribute.
-         *    @param string $id        ID attribute value.
-         *    @return string/boolean   URL on success.
-         *    @access public
-         */
-        function getLinkById($id) {
-            return $this->_page->getUrlById($id);
+        if (count($urls) < $index + 1) {
+            return false;
         }
+        return $urls[$index];
+    }
 
-        /**
-         *    Follows a link by id attribute.
-         *    @param string $id        ID attribute value.
-         *    @return string/boolean   Page on success.
-         *    @access public
-         */
-        function clickLinkById($id) {
-            if (! ($url = $this->getLinkById($id))) {
-                return false;
-            }
-            $this->_load($url, new SimpleGetEncoding());
-            return $this->getContent();
+    /**
+     *    Follows a link by label. Will click the first link
+     *    found with this link text by default, or a later
+     *    one if an index is given. The match ignores case and
+     *    white space issues.
+     *    @param string $label     Text between the anchor tags.
+     *    @param integer $index    Link position counting from zero.
+     *    @return string/boolean   Page on success.
+     *    @access public
+     */
+    function clickLink($label, $index = 0) {
+        $url = $this->getLink($label, $index);
+        if ($url === false) {
+            return false;
         }
+        $this->_load($url, new SimpleGetEncoding());
+        return $this->getContent();
+    }
+    
+    /**
+     *    Finds a link by id attribute.
+     *    @param string $id        ID attribute value.
+     *    @return string/boolean   URL on success.
+     *    @access public
+     */
+    function getLinkById($id) {
+        return $this->_page->getUrlById($id);
+    }
 
-        /**
-         *    Clicks a visible text item. Will first try buttons,
-         *    then links and then images.
-         *    @param string $label        Visible text or alt text.
-         *    @return string/boolean      Raw page or false.
-         *    @access public
-         */
-        function click($label) {
-            $raw = $this->clickSubmit($label);
-            if (! $raw) {
-                $raw = $this->clickLink($label);
-            }
-            if (! $raw) {
-                $raw = $this->clickImage($label);
-            }
-            return $raw;
+    /**
+     *    Follows a link by id attribute.
+     *    @param string $id        ID attribute value.
+     *    @return string/boolean   Page on success.
+     *    @access public
+     */
+    function clickLinkById($id) {
+        if (! ($url = $this->getLinkById($id))) {
+            return false;
         }
+        $this->_load($url, new SimpleGetEncoding());
+        return $this->getContent();
+    }
 
-        /**
-         *    Tests to see if a click target exists.
-         *    @param string $label    Visible text or alt text.
-         *    @return boolean         True if target present.
-         *    @access public
-         */
-        function isClickable($label) {
-            return $this->isSubmit($label) || ($this->getLink($label) !== false) || $this->isImage($label);
+    /**
+     *    Clicks a visible text item. Will first try buttons,
+     *    then links and then images.
+     *    @param string $label        Visible text or alt text.
+     *    @return string/boolean      Raw page or false.
+     *    @access public
+     */
+    function click($label) {
+        $raw = $this->clickSubmit($label);
+        if (! $raw) {
+            $raw = $this->clickLink($label);
+        }
+        if (! $raw) {
+            $raw = $this->clickImage($label);
         }
+        return $raw;
+    }
+
+    /**
+     *    Tests to see if a click target exists.
+     *    @param string $label    Visible text or alt text.
+     *    @return boolean         True if target present.
+     *    @access public
+     */
+    function isClickable($label) {
+        return $this->isSubmit($label) || ($this->getLink($label) !== false) || $this->isImage($label);
     }
+}
 ?>
\ No newline at end of file
index 439f6b5e9dc56dc6429506b6bcd57a827b840068..3e9115434133563dc1055f034945776f68291cc3 100644 (file)
 <?php
+/**
+ * This file contains the following classes: {@link SimpleCollector},
+ * {@link SimplePatternCollector}.
+ *
+ * @author Travis Swicegood <development@domain51.com>
+ * @package SimpleTest
+ * @subpackage UnitTester
+ * @version $Id$
+ */
+
+/**
+ * The basic collector for {@link GroupTest}
+ *
+ * @see collect(), GroupTest::collect()
+ * @package SimpleTest
+ * @subpackage UnitTester
+ */
+class SimpleCollector {
+
     /**
-     * This file contains the following classes: {@link SimpleCollector},
-     * {@link SimplePatternCollector}.
-     *
-     * @author Travis Swicegood <development@domain51.com>
-     * @package SimpleTest
-     * @subpackage UnitTester
-     * @version $Id$
+     * Strips off any kind of slash at the end so as to normalise the path.
+     * @param string $path    Path to normalise.
+     * @return string         Path without trailing slash.
      */
-    
+    function _removeTrailingSlash($path) {
+        if (substr($path, -1) == DIRECTORY_SEPARATOR) {
+            return substr($path, 0, -1);
+        } elseif (substr($path, -1) == '/') {
+            return substr($path, 0, -1);
+        } else {
+            return $path;
+        }
+    }
+
     /**
-     * The basic collector for {@link GroupTest}
-     *
-     * @see collect(), GroupTest::collect()
-     * @package SimpleTest
-     * @subpackage UnitTester
+     * Scans the directory and adds what it can.
+     * @param object $test    Group test with {@link GroupTest::addTestFile()} method.
+     * @param string $path    Directory to scan.
+     * @see _attemptToAdd()
      */
-    class SimpleCollector {
-    
-        /**
-         * Strips off any kind of slash at the end so as to normalise the path.
-         * @param string $path    Path to normalise.
-         * @return string         Path without trailing slash.
-         */
-        function _removeTrailingSlash($path) {
-            if (substr($path, -1) == DIRECTORY_SEPARATOR) {
-                return substr($path, 0, -1);
-            } elseif (substr($path, -1) == '/') {
-                return substr($path, 0, -1);
-            } else {
-                return $path;
-            }
-        }
-    
-        /**
-         * Scans the directory and adds what it can.
-         * @param object $test    Group test with {@link GroupTest::addTestFile()} method.
-         * @param string $path    Directory to scan.
-         * @see _attemptToAdd()
-         */
-        function collect(&$test, $path) {
-            $path = $this->_removeTrailingSlash($path);
-            if ($handle = opendir($path)) {
-                while (($entry = readdir($handle)) !== false) {
-                    $this->_handle($test, $path . DIRECTORY_SEPARATOR . $entry);
+    function collect(&$test, $path) {
+        $path = $this->_removeTrailingSlash($path);
+        if ($handle = opendir($path)) {
+            while (($entry = readdir($handle)) !== false) {
+                if ($this->_isHidden($entry)) {
+                    continue;
                 }
-                closedir($handle);
-            }
-        }
-    
-        /**
-         * This method determines what should be done with a given file and adds
-         * it via {@link GroupTest::addTestFile()} if necessary.
-         *
-         * This method should be overriden to provide custom matching criteria,
-         * such as pattern matching, recursive matching, etc.  For an example, see
-         * {@link SimplePatternCollector::_handle()}.
-         *
-         * @param object $test      Group test with {@link GroupTest::addTestFile()} method.
-         * @param string $filename  A filename as generated by {@link collect()}
-         * @see collect()
-         * @access protected
-         */
-        function _handle(&$test, $file) {
-            if (! is_dir($file)) {
-                $test->addTestFile($file);
+                $this->_handle($test, $path . DIRECTORY_SEPARATOR . $entry);
             }
+            closedir($handle);
         }
     }
-    
+
     /**
-     * An extension to {@link SimpleCollector} that only adds files matching a
-     * given pattern.
+     * This method determines what should be done with a given file and adds
+     * it via {@link GroupTest::addTestFile()} if necessary.
+     *
+     * This method should be overriden to provide custom matching criteria,
+     * such as pattern matching, recursive matching, etc.  For an example, see
+     * {@link SimplePatternCollector::_handle()}.
      *
-     * @package SimpleTest
-     * @subpackage UnitTester
-     * @see SimpleCollector
+     * @param object $test      Group test with {@link GroupTest::addTestFile()} method.
+     * @param string $filename  A filename as generated by {@link collect()}
+     * @see collect()
+     * @access protected
      */
-    class SimplePatternCollector extends SimpleCollector {
-        var $_pattern;
-    
-        /**
-         *
-         * @param string $pattern   Perl compatible regex to test name against
-         *  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 _handle(&$test, $file) {
+        if (is_dir($file)) {
+            return;
         }
+        $test->addTestFile($file);
+    }
     
-        /**
-         * Attempts to add files that match a given pattern.
-         *
-         * @see SimpleCollector::_handle()
-         * @param object $test    Group test with {@link GroupTest::addTestFile()} method.
-         * @param string $path    Directory to scan.
-         * @access protected
-         */
-        function _handle(&$test, $filename) {
-            if (preg_match($this->_pattern, $filename)) {
-                parent::_handle($test, $filename);
-            }
+    /**
+     *  Tests for hidden files so as to skip them. Currently
+     *  only tests for Unix hidden files.
+     *  @param string $filename        Plain filename.
+     *  @return boolean                True if hidden file.
+     *  @access private
+     */
+    function _isHidden($filename) {
+        return strncmp($filename, '.', 1) == 0;
+    }
+}
+
+/**
+ * An extension to {@link SimpleCollector} that only adds files matching a
+ * given pattern.
+ *
+ * @package SimpleTest
+ * @subpackage UnitTester
+ * @see SimpleCollector
+ */
+class SimplePatternCollector extends SimpleCollector {
+    var $_pattern;
+
+    /**
+     *
+     * @param string $pattern   Perl compatible regex to test name against
+     *  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;
+    }
+
+    /**
+     * Attempts to add files that match a given pattern.
+     *
+     * @see SimpleCollector::_handle()
+     * @param object $test    Group test with {@link GroupTest::addTestFile()} method.
+     * @param string $path    Directory to scan.
+     * @access protected
+     */
+    function _handle(&$test, $filename) {
+        if (preg_match($this->_pattern, $filename)) {
+            parent::_handle($test, $filename);
         }
     }
+}
 ?>
\ No newline at end of file
index eefb6029b873badd52da8336fa3fa95b425b3872..bd65a96d7bd0867a9ebfdf1dfcf4d380cdd8250b 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @version    $Id$
+ */
+
+/**
+ *  Static methods for compatibility between different
+ *  PHP versions.
+ *  @package    SimpleTest
+ */
+class SimpleTestCompatibility {
+    
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @version        $Id$
+     *    Creates a copy whether in PHP5 or PHP4.
+     *    @param object $object     Thing to copy.
+     *    @return object            A copy.
+     *    @access public
+     *    @static
      */
+    function copy($object) {
+        if (version_compare(phpversion(), '5') >= 0) {
+            eval('$copy = clone $object;');
+            return $copy;
+        }
+        return $object;
+    }
     
     /**
-     *  Static methods for compatibility between different
-     *  PHP versions.
-     *  @package       SimpleTest
+     *    Identity test. Drops back to equality + types for PHP5
+     *    objects as the === operator counts as the
+     *    stronger reference constraint.
+     *    @param mixed $first    Test subject.
+     *    @param mixed $second   Comparison object.
+     *    @return boolean        True if identical.
+     *    @access public
+     *    @static
      */
-    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) {
-            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
-         *    stronger reference constraint.
-         *    @param mixed $first    Test subject.
-         *    @param mixed $second   Comparison object.
-         *       @return boolean                True if identical.
-         *    @access public
-         *    @static
-         */
-        function isIdentical($first, $second) {
-            if ($first != $second) {
-                return false;
-            }
-            if (version_compare(phpversion(), '5') >= 0) {
-                return SimpleTestCompatibility::_isIdenticalType($first, $second);
-            }
-            return ($first === $second);
+    function isIdentical($first, $second) {
+        if (version_compare(phpversion(), '5') >= 0) {
+            return SimpleTestCompatibility::_isIdenticalType($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) {
-            if (gettype($first) != gettype($second)) {
-                return false;
-            }
-            if (is_object($first) && is_object($second)) {
-                if (get_class($first) != get_class($second)) {
-                    return false;
-                }
-                return SimpleTestCompatibility::_isArrayOfIdenticalTypes(
-                        get_object_vars($first),
-                        get_object_vars($second));
-            }
-            if (is_array($first) && is_array($second)) {
-                return SimpleTestCompatibility::_isArrayOfIdenticalTypes($first, $second);
-            }
-            if ($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) {
+        if (gettype($first) != gettype($second)) {
+            return false;
+        }
+        if (is_object($first) && is_object($second)) {
+            if (get_class($first) != get_class($second)) {
                 return false;
             }
-            return true;
+            return SimpleTestCompatibility::_isArrayOfIdenticalTypes(
+                    get_object_vars($first),
+                    get_object_vars($second));
+        }
+        if (is_array($first) && is_array($second)) {
+            return SimpleTestCompatibility::_isArrayOfIdenticalTypes($first, $second);
+        }
+        if ($first !== $second) {
+            return false;
         }
-        
-        /**
-         *    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) {
-            if (array_keys($first) != array_keys($second)) {
+        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) {
+        if (array_keys($first) != array_keys($second)) {
+            return false;
+        }
+        foreach (array_keys($first) as $key) {
+            $is_identical = SimpleTestCompatibility::_isIdenticalType(
+                    $first[$key],
+                    $second[$key]);
+            if (! $is_identical) {
                 return false;
             }
-            foreach (array_keys($first) as $key) {
-                $is_identical = SimpleTestCompatibility::_isIdenticalType(
-                        $first[$key],
-                        $second[$key]);
-                if (! $is_identical) {
-                    return false;
-                }
-            }
-            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) {
-            if (version_compare(phpversion(), '5', '>=') && is_object($first)) {
-                   return ($first === $second);
-               }
-               if (is_object($first) && is_object($second)) {
-                $id = uniqid("test");
-                $first->$id = true;
-                $is_ref = isset($second->$id);
-                unset($first->$id);
-                return $is_ref;
-               }
-               $temp = $first;
-            $first = uniqid("test");
-            $is_ref = ($first === $second);
-            $first = $temp;
+        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) {
+        if (version_compare(phpversion(), '5', '>=') && is_object($first)) {
+            return ($first === $second);
+        }
+        if (is_object($first) && is_object($second)) {
+            $id = uniqid("test");
+            $first->$id = true;
+            $is_ref = isset($second->$id);
+            unset($first->$id);
             return $is_ref;
         }
-        
-        /**
-         *    Test to see if an object is a member of a
-         *    class hiearchy.
-         *    @param object $object    Object to test.
-         *    @param string $class     Root name of hiearchy.
-         *    @return boolean            True if class in hiearchy.
-         *    @access public
-         *    @static
-         */
-        function isA($object, $class) {
-            if (version_compare(phpversion(), '5') >= 0) {
-                if (! class_exists($class, false)) {
-                    if (function_exists('interface_exists')) {
-                        if (! interface_exists($class, false))  {
-                            return false;
-                        }
+        $temp = $first;
+        $first = uniqid("test");
+        $is_ref = ($first === $second);
+        $first = $temp;
+        return $is_ref;
+    }
+    
+    /**
+     *    Test to see if an object is a member of a
+     *    class hiearchy.
+     *    @param object $object    Object to test.
+     *    @param string $class     Root name of hiearchy.
+     *    @return boolean         True if class in hiearchy.
+     *    @access public
+     *    @static
+     */
+    function isA($object, $class) {
+        if (version_compare(phpversion(), '5') >= 0) {
+            if (! class_exists($class, false)) {
+                if (function_exists('interface_exists')) {
+                    if (! interface_exists($class, false))  {
+                        return false;
                     }
                 }
-                eval("\$is_a = \$object instanceof $class;");
-                return $is_a;
-            }
-            if (function_exists('is_a')) {
-                return is_a($object, $class);
             }
-            return ((strtolower($class) == get_class($object))
-                    or (is_subclass_of($object, $class)));
+            eval("\$is_a = \$object instanceof $class;");
+            return $is_a;
         }
-        
-        /**
-         *    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) {
-            if (function_exists('stream_set_timeout')) {
-                stream_set_timeout($handle, $timeout, 0);
-            } elseif (function_exists('socket_set_timeout')) {
-                socket_set_timeout($handle, $timeout, 0);
-            } elseif (function_exists('set_socket_timeout')) {
-                set_socket_timeout($handle, $timeout, 0);
-            }
+        if (function_exists('is_a')) {
+            return is_a($object, $class);
+        }
+        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) {
+        if (function_exists('stream_set_timeout')) {
+            stream_set_timeout($handle, $timeout, 0);
+        } elseif (function_exists('socket_set_timeout')) {
+            socket_set_timeout($handle, $timeout, 0);
+        } elseif (function_exists('set_socket_timeout')) {
+            set_socket_timeout($handle, $timeout, 0);
         }
     }
+}
 ?>
\ No newline at end of file
index b346a7962e8bb07d97d46b32f4e061f1538c61a6..abbc2ff2cb5aec665f1c7c7fdc80aa2f3d377a20 100644 (file)
 <?php
+/**
+ *  Base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage WebTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ *  include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/url.php');
+/**#@-*/
+
+/**
+ *    Cookie data holder. Cookie rules are full of pretty
+ *    arbitary stuff. I have used...
+ *    http://wp.netscape.com/newsref/std/cookie_spec.html
+ *    http://www.cookiecentral.com/faq/
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleCookie {
+    var $_host;
+    var $_name;
+    var $_value;
+    var $_path;
+    var $_expiry;
+    var $_is_secure;
+    
     /**
-     * Base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     WebTester
-     * @version        $Id$
+     *    Constructor. Sets the stored values.
+     *    @param string $name            Cookie key.
+     *    @param string $value           Value of cookie.
+     *    @param string $path            Cookie path if not host wide.
+     *    @param string $expiry          Expiry date as string.
+     *    @param boolean $is_secure      Currently ignored.
      */
-
-    /**#@+
-     * include other SimpleTest class files
+    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;
+        if (is_string($expiry)) {
+            $this->_expiry = strtotime($expiry);
+        } elseif (is_integer($expiry)) {
+            $this->_expiry = $expiry;
+        }
+        $this->_is_secure = $is_secure;
+    }
+    
+    /**
+     *    Sets the host. The cookie rules determine
+     *    that the first two parts are taken for
+     *    certain TLDs and three for others. If the
+     *    new host does not match these rules then the
+     *    call will fail.
+     *    @param string $host       New hostname.
+     *    @return boolean           True if hostname is valid.
+     *    @access public
      */
-    require_once(dirname(__FILE__) . '/url.php');
-    /**#@-*/
+    function setHost($host) {
+        if ($host = $this->_truncateHost($host)) {
+            $this->_host = $host;
+            return true;
+        }
+        return false;
+    }
     
     /**
-     *    Cookie data holder. Cookie rules are full of pretty
-     *    arbitary stuff. I have used...
-     *    http://wp.netscape.com/newsref/std/cookie_spec.html
-     *    http://www.cookiecentral.com/faq/
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Accessor for the truncated host to which this
+     *    cookie applies.
+     *    @return string       Truncated hostname.
+     *    @access public
      */
-    class SimpleCookie {
-        var $_host;
-        var $_name;
-        var $_value;
-        var $_path;
-        var $_expiry;
-        var $_is_secure;
-        
-        /**
-         *    Constructor. Sets the stored values.
-         *    @param string $name            Cookie key.
-         *    @param string $value           Value of cookie.
-         *    @param string $path            Cookie path if not host wide.
-         *    @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;
-            if (is_string($expiry)) {
-                $this->_expiry = strtotime($expiry);
-            } elseif (is_integer($expiry)) {
-                $this->_expiry = $expiry;
-            }
-            $this->_is_secure = $is_secure;
+    function getHost() {
+        return $this->_host;
+    }
+    
+    /**
+     *    Test for a cookie being valid for a host name.
+     *    @param string $host    Host to test against.
+     *    @return boolean        True if the cookie would be valid
+     *                           here.
+     */
+    function isValidHost($host) {
+        return ($this->_truncateHost($host) === $this->getHost());
+    }
+    
+    /**
+     *    Extracts just the domain part that determines a
+     *    cookie's host validity.
+     *    @param string $host    Host name to truncate.
+     *    @return string        Domain or false on a bad host.
+     *    @access private
+     */
+    function _truncateHost($host) {
+        $tlds = SimpleUrl::getAllTopLevelDomains();
+        if (preg_match('/[a-z\-]+\.(' . $tlds . ')$/i', $host, $matches)) {
+            return $matches[0];
+        } elseif (preg_match('/[a-z\-]+\.[a-z\-]+\.[a-z\-]+$/i', $host, $matches)) {
+            return $matches[0];
         }
-        
-        /**
-         *    Sets the host. The cookie rules determine
-         *    that the first two parts are taken for
-         *    certain TLDs and three for others. If the
-         *    new host does not match these rules then the
-         *    call will fail.
-         *    @param string $host       New hostname.
-         *    @return boolean           True if hostname is valid.
-         *    @access public
-         */
-        function setHost($host) {
-            if ($host = $this->_truncateHost($host)) {
-                $this->_host = $host;
-                return true;
-            }
+        return false;
+    }
+    
+    /**
+     *    Accessor for name.
+     *    @return string       Cookie key.
+     *    @access public
+     */
+    function getName() {
+        return $this->_name;
+    }
+    
+    /**
+     *    Accessor for value. A deleted cookie will
+     *    have an empty string for this.
+     *    @return string       Cookie value.
+     *    @access public
+     */
+    function getValue() {
+        return $this->_value;
+    }
+    
+    /**
+     *    Accessor for path.
+     *    @return string       Valid cookie path.
+     *    @access public
+     */
+    function getPath() {
+        return $this->_path;
+    }
+    
+    /**
+     *    Tests a path to see if the cookie applies
+     *    there. The test path must be longer or
+     *    equal to the cookie path.
+     *    @param string $path       Path to test against.
+     *    @return boolean           True if cookie valid here.
+     *    @access public
+     */
+    function isValidPath($path) {
+        return (strncmp(
+                $this->_fixPath($path),
+                $this->getPath(),
+                strlen($this->getPath())) == 0);
+    }
+    
+    /**
+     *    Accessor for expiry.
+     *    @return string       Expiry string.
+     *    @access public
+     */
+    function getExpiry() {
+        if (! $this->_expiry) {
             return false;
         }
-        
-        /**
-         *    Accessor for the truncated host to which this
-         *    cookie applies.
-         *    @return string       Truncated hostname.
-         *    @access public
-         */
-        function getHost() {
-            return $this->_host;
-        }
-        
-        /**
-         *    Test for a cookie being valid for a host name.
-         *    @param string $host    Host to test against.
-         *    @return boolean        True if the cookie would be valid
-         *                           here.
-         */
-        function isValidHost($host) {
-            return ($this->_truncateHost($host) === $this->getHost());
-        }
-        
-        /**
-         *    Extracts just the domain part that determines a
-         *    cookie's host validity.
-         *    @param string $host    Host name to truncate.
-         *    @return string        Domain or false on a bad host.
-         *    @access private
-         */
-        function _truncateHost($host) {
-            $tlds = SimpleUrl::getAllTopLevelDomains();
-            if (preg_match('/[a-z\-]+\.(' . $tlds . ')$/i', $host, $matches)) {
-                return $matches[0];
-            } elseif (preg_match('/[a-z\-]+\.[a-z\-]+\.[a-z\-]+$/i', $host, $matches)) {
-                return $matches[0];
-            }
-            return false;
+        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.
+     *    Will give true for a session cookie.
+     *    @param integer/string $now  Time to test against. Result
+     *                                will be false if this time
+     *                                is later than the cookie expiry.
+     *                                Can be either a timestamp integer
+     *                                or a cookie format date.
+     *    @access public
+     */
+    function isExpired($now) {
+        if (! $this->_expiry) {
+            return true;
         }
-        
-        /**
-         *    Accessor for name.
-         *    @return string       Cookie key.
-         *    @access public
-         */
-        function getName() {
-            return $this->_name;
+        if (is_string($now)) {
+            $now = strtotime($now);
         }
-        
-        /**
-         *    Accessor for value. A deleted cookie will
-         *    have an empty string for this.
-         *    @return string       Cookie value.
-         *    @access public
-         */
-        function getValue() {
-            return $this->_value;
+        return ($this->_expiry < $now);
+    }
+    
+    /**
+     *    Ages the cookie by the specified number of
+     *    seconds.
+     *    @param integer $interval   In seconds.
+     *    @public
+     */
+    function agePrematurely($interval) {
+        if ($this->_expiry) {
+            $this->_expiry -= $interval;
         }
-        
-        /**
-         *    Accessor for path.
-         *    @return string       Valid cookie path.
-         *    @access public
-         */
-        function getPath() {
-            return $this->_path;
+    }
+    
+    /**
+     *    Accessor for the secure flag.
+     *    @return boolean       True if cookie needs SSL.
+     *    @access public
+     */
+    function isSecure() {
+        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) {
+        if (substr($path, 0, 1) != '/') {
+            $path = '/' . $path;
         }
-        
-        /**
-         *    Tests a path to see if the cookie applies
-         *    there. The test path must be longer or
-         *    equal to the cookie path.
-         *    @param string $path       Path to test against.
-         *    @return boolean           True if cookie valid here.
-         *    @access public
-         */
-        function isValidPath($path) {
-            return (strncmp(
-                    $this->_fixPath($path),
-                    $this->getPath(),
-                    strlen($this->getPath())) == 0);
+        if (substr($path, -1, 1) != '/') {
+            $path .= '/';
         }
-        
-        /**
-         *    Accessor for expiry.
-         *    @return string       Expiry string.
-         *    @access public
-         */
-        function getExpiry() {
-            if (! $this->_expiry) {
-                return false;
+        return $path;
+    }
+}
+
+/**
+ *    Repository for cookies. This stuff is a
+ *    tiny bit browser dependent.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleCookieJar {
+    var $_cookies;
+    
+    /**
+     *    Constructor. Jar starts empty.
+     *    @access public
+     */
+    function SimpleCookieJar() {
+        $this->_cookies = array();
+    }
+    
+    /**
+     *    Removes expired and temporary cookies as if
+     *    the browser was closed and re-opened.
+     *    @param string/integer $now   Time to test expiry against.
+     *    @access public
+     */
+    function restartSession($date = false) {
+        $surviving_cookies = array();
+        for ($i = 0; $i < count($this->_cookies); $i++) {
+            if (! $this->_cookies[$i]->getValue()) {
+                continue;
             }
-            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.
-         *    Will give true for a session cookie.
-         *    @param integer/string $now  Time to test against. Result
-         *                                will be false if this time
-         *                                is later than the cookie expiry.
-         *                                Can be either a timestamp integer
-         *                                or a cookie format date.
-         *    @access public
-         */
-        function isExpired($now) {
-            if (! $this->_expiry) {
-                return true;
+            if (! $this->_cookies[$i]->getExpiry()) {
+                continue;
             }
-            if (is_string($now)) {
-                $now = strtotime($now);
+            if ($date && $this->_cookies[$i]->isExpired($date)) {
+                continue;
             }
-            return ($this->_expiry < $now);
+            $surviving_cookies[] = $this->_cookies[$i];
         }
-        
-        /**
-         *    Ages the cookie by the specified number of
-         *    seconds.
-         *    @param integer $interval   In seconds.
-         *    @public
-         */
-        function agePrematurely($interval) {
-            if ($this->_expiry) {
-                $this->_expiry -= $interval;
-            }
+        $this->_cookies = $surviving_cookies;
+    }
+    
+    /**
+     *    Ages all cookies in the cookie jar.
+     *    @param integer $interval     The old session is moved
+     *                                 into the past by this number
+     *                                 of seconds. Cookies now over
+     *                                 age will be removed.
+     *    @access public
+     */
+    function agePrematurely($interval) {
+        for ($i = 0; $i < count($this->_cookies); $i++) {
+            $this->_cookies[$i]->agePrematurely($interval);
         }
-        
-        /**
-         *    Accessor for the secure flag.
-         *    @return boolean       True if cookie needs SSL.
-         *    @access public
-         */
-        function isSecure() {
-            return $this->_is_secure;
+    }
+    
+    /**
+     *    Sets an additional cookie. If a cookie has
+     *    the same name and path it is replaced.
+     *    @param string $name       Cookie key.
+     *    @param string $value      Value of cookie.
+     *    @param string $host       Host upon which the cookie is valid.
+     *    @param string $path       Cookie path if not host wide.
+     *    @param string $expiry     Expiry date.
+     *    @access public
+     */
+    function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
+        $cookie = new SimpleCookie($name, $value, $path, $expiry);
+        if ($host) {
+            $cookie->setHost($host);
         }
-        
-        /**
-         *    Adds a trailing and leading slash to the path
-         *    if missing.
-         *    @param string $path            Path to fix.
-         *    @access private
-         */
-        function _fixPath($path) {
-            if (substr($path, 0, 1) != '/') {
-                $path = '/' . $path;
-            }
-            if (substr($path, -1, 1) != '/') {
-                $path .= '/';
+        $this->_cookies[$this->_findFirstMatch($cookie)] = $cookie;
+    }
+    
+    /**
+     *    Finds a matching cookie to write over or the
+     *    first empty slot if none.
+     *    @param SimpleCookie $cookie    Cookie to write into jar.
+     *    @return integer                Available slot.
+     *    @access private
+     */
+    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());
+            if ($is_match) {
+                return $i;
             }
-            return $path;
         }
+        return count($this->_cookies);
     }
     
     /**
-     *    Repository for cookies. This stuff is a
-     *    tiny bit browser dependent.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Reads the most specific cookie value from the
+     *    browser cookies. Looks for the longest path that
+     *    matches.
+     *    @param string $host        Host to search.
+     *    @param string $path        Applicable path.
+     *    @param string $name        Name of cookie to read.
+     *    @return string             False if not present, else the
+     *                               value as a string.
+     *    @access public
      */
-    class SimpleCookieJar {
-        var $_cookies;
-        
-        /**
-         *    Constructor. Jar starts empty.
-         *    @access public
-         */
-        function SimpleCookieJar() {
-            $this->_cookies = array();
-        }
-        
-        /**
-         *    Removes expired and temporary cookies as if
-         *    the browser was closed and re-opened.
-         *    @param string/integer $now   Time to test expiry against.
-         *    @access public
-         */
-        function restartSession($date = false) {
-            $surviving_cookies = array();
-            for ($i = 0; $i < count($this->_cookies); $i++) {
-                if (! $this->_cookies[$i]->getValue()) {
-                    continue;
-                }
-                if (! $this->_cookies[$i]->getExpiry()) {
-                    continue;
-                }
-                if ($date && $this->_cookies[$i]->isExpired($date)) {
-                    continue;
+    function getCookieValue($host, $path, $name) {
+        $longest_path = '';
+        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();
                 }
-                $surviving_cookies[] = $this->_cookies[$i];
-            }
-            $this->_cookies = $surviving_cookies;
-        }
-        
-        /**
-         *    Ages all cookies in the cookie jar.
-         *    @param integer $interval     The old session is moved
-         *                                 into the past by this number
-         *                                 of seconds. Cookies now over
-         *                                 age will be removed.
-         *    @access public
-         */
-        function 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.
-         *    @param string $name       Cookie key.
-         *    @param string $value      Value of cookie.
-         *    @param string $host       Host upon which the cookie is valid.
-         *    @param string $path       Cookie path if not host wide.
-         *    @param string $expiry     Expiry date.
-         *    @access public
-         */
-        function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
-            $cookie = new SimpleCookie($name, $value, $path, $expiry);
-            if ($host) {
-                $cookie->setHost($host);
             }
-            $this->_cookies[$this->_findFirstMatch($cookie)] = $cookie;
         }
-        
-        /**
-         *    Finds a matching cookie to write over or the
-         *    first empty slot if none.
-         *    @param SimpleCookie $cookie    Cookie to write into jar.
-         *    @return integer                Available slot.
-         *    @access private
-         */
-        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());
-                if ($is_match) {
-                    return $i;
-                }
-            }
-            return count($this->_cookies);
+        return (isset($value) ? $value : false);
+    }
+    
+    /**
+     *    Tests cookie for matching against search
+     *    criteria.
+     *    @param SimpleTest $cookie    Cookie to test.
+     *    @param string $host          Host must match.
+     *    @param string $path          Cookie path must be shorter than
+     *                                 this path.
+     *    @param string $name          Name must match.
+     *    @return boolean              True if matched.
+     *    @access private
+     */
+    function _isMatch($cookie, $host, $path, $name) {
+        if ($cookie->getName() != $name) {
+            return false;
         }
-        
-        /**
-         *    Reads the most specific cookie value from the
-         *    browser cookies. Looks for the longest path that
-         *    matches.
-         *    @param string $host        Host to search.
-         *    @param string $path        Applicable path.
-         *    @param string $name        Name of cookie to read.
-         *    @return string             False if not present, else the
-         *                               value as a string.
-         *    @access public
-         */
-        function getCookieValue($host, $path, $name) {
-            $longest_path = '';
-            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();
-                    }
-                }
-            }
-            return (isset($value) ? $value : false);
+        if ($host && $cookie->getHost() && ! $cookie->isValidHost($host)) {
+            return false;
         }
-        
-        /**
-         *    Tests cookie for matching against search
-         *    criteria.
-         *    @param SimpleTest $cookie    Cookie to test.
-         *    @param string $host          Host must match.
-         *    @param string $path          Cookie path must be shorter than
-         *                                 this path.
-         *    @param string $name          Name must match.
-         *    @return boolean              True if matched.
-         *    @access private
-         */
-        function _isMatch($cookie, $host, $path, $name) {
-            if ($cookie->getName() != $name) {
-                return false;
-            }
-            if ($host && $cookie->getHost() && ! $cookie->isValidHost($host)) {
-                return false;
-            }
-            if (! $cookie->isValidPath($path)) {
-                return false;
-            }
-            return true;
+        if (! $cookie->isValidPath($path)) {
+            return false;
         }
-        
-        /**
-         *    Uses a URL to sift relevant cookies by host and
-         *    path. Results are list of strings of form "name=value".
-         *    @param SimpleUrl $url       Url to select by.
-         *    @return array               Valid name and value pairs.
-         *    @access public
-         */
-        function selectAsPairs($url) {
-            $pairs = array();
-            foreach ($this->_cookies as $cookie) {
-                if ($this->_isMatch($cookie, $url->getHost(), $url->getPath(), $cookie->getName())) {
-                    $pairs[] = $cookie->getName() . '=' . $cookie->getValue();
-                }
+        return true;
+    }
+    
+    /**
+     *    Uses a URL to sift relevant cookies by host and
+     *    path. Results are list of strings of form "name=value".
+     *    @param SimpleUrl $url       Url to select by.
+     *    @return array               Valid name and value pairs.
+     *    @access public
+     */
+    function selectAsPairs($url) {
+        $pairs = array();
+        foreach ($this->_cookies as $cookie) {
+            if ($this->_isMatch($cookie, $url->getHost(), $url->getPath(), $cookie->getName())) {
+                $pairs[] = $cookie->getName() . '=' . $cookie->getValue();
             }
-            return $pairs;
         }
+        return $pairs;
     }
+}
 ?>
\ No newline at end of file
diff --git a/lib/simpletestlib/default_reporter.php b/lib/simpletestlib/default_reporter.php
new file mode 100644 (file)
index 0000000..69b0797
--- /dev/null
@@ -0,0 +1,133 @@
+<?php
+/**
+ *  Optional include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ *  include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/simpletest.php');
+require_once(dirname(__FILE__) . '/scorer.php');
+require_once(dirname(__FILE__) . '/reporter.php');
+require_once(dirname(__FILE__) . '/xml.php');
+/**#@-*/
+
+/**
+ *    Parser for command line arguments. Extracts
+ *    the a specific test to run and engages XML
+ *    reporting when necessary.
+ *    @package SimpleTest
+ *    @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;
+    
+    /**
+     *    Parses raw command line arguments into object properties.
+     *    @param string $arguments        Raw commend line arguments.
+     */
+    function SimpleCommandLineParser($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]];
+                $this->$property = $matches[2];
+            } elseif (preg_match('/^--?(test|case|t|c)$/', $argument, $matches)) {
+                $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;
+            } elseif (preg_match('/^--?(no-skip|no-skips|s)$/', $argument)) {
+                $this->_no_skips = true;
+            }
+        }
+    }
+    
+    /**
+     *    Run only this test.
+     *    @return string        Test name to run.
+     *    @access public
+     */
+    function getTest() {
+        return $this->_test;
+    }
+    
+    /**
+     *    Run only this test suite.
+     *    @return string        Test class name to run.
+     *    @access public
+     */
+    function getTestCase() {
+        return $this->_case;
+    }
+    
+    /**
+     *    Output should be XML or not.
+     *    @return boolean        True if XML desired.
+     *    @access public
+     */
+    function isXml() {
+        return $this->_xml;
+    }
+    
+    /**
+     *    Output should suppress skip messages.
+     *    @return boolean        True for no skips.
+     *    @access public
+     */
+    function noSkips() {
+        return $this->_no_skips;
+    }
+}
+
+/**
+ *    The default reporter used by SimpleTest's autorun
+ *    feature. The actual reporters used are dependency
+ *    injected and can be overridden.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class DefaultReporter extends SimpleReporterDecorator {
+    
+    /**
+     *  Assembles the appopriate reporter for the environment.
+     */
+    function DefaultReporter() {
+        if (SimpleReporter::inCli()) {
+            global $argv;
+            $parser = new SimpleCommandLineParser($argv);
+            $interfaces = $parser->isXml() ? array('XmlReporter') : array('TextReporter');
+            $reporter = &new SelectiveReporter(
+                    SimpleTest::preferred($interfaces),
+                    $parser->getTestCase(),
+                    $parser->getTest());
+            if ($parser->noSkips()) {
+                $reporter = &new NoSkipsReporter($reporter);
+            }
+        } else {
+            $reporter = &new SelectiveReporter(
+                    SimpleTest::preferred('HtmlReporter'),
+                    @$_GET['c'],
+                    @$_GET['t']);
+            if (@$_GET['skips'] == 'no' || @$_GET['show-skips'] == 'no') {
+                $reporter = &new NoSkipsReporter($reporter);
+            }
+        }
+        $this->SimpleReporterDecorator($reporter);
+    }
+}
+?>
\ No newline at end of file
index 35ac1331c4832f89d51414b5fa41852d4b00837e..708e6e1c2e6e1ddb4973efe186dd8fc21965c218 100644 (file)
@@ -1,96 +1,96 @@
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ *  include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/xml.php');
+require_once(dirname(__FILE__) . '/shell_tester.php');
+/**#@-*/
+
+/**
+ *    Runs an XML formated test in a separate process.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class DetachedTestCase {
+    var $_command;
+    var $_dry_command;
+    var $_size;
+
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
+     *    Sets the location of the remote test.
+     *    @param string $command       Test script.
+     *    @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;
+    }
 
-    /**#@+
-     * include other SimpleTest class files
+    /**
+     *    Accessor for the test name for subclasses.
+     *    @return string       Name of the test.
+     *    @access public
      */
-    require_once(dirname(__FILE__) . '/xml.php');
-    require_once(dirname(__FILE__) . '/shell_tester.php');
-    /**#@-*/
+    function getLabel() {
+        return $this->_command;
+    }
 
     /**
-     *    Runs an XML formated test in a separate process.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Runs the top level test for this class. Currently
+     *    reads the data as a single chunk. I'll fix this
+     *    once I have added iteration to the browser.
+     *    @param SimpleReporter $reporter    Target of test results.
+     *    @returns boolean                   True if no failures.
+     *    @access public
      */
-    class DetachedTestCase {
-        var $_command;
-        var $_dry_command;
-        var $_size;
-
-        /**
-         *    Sets the location of the remote test.
-         *    @param string $command       Test script.
-         *    @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;
-        }
-
-        /**
-         *    Accessor for the test name for subclasses.
-         *    @return string       Name of the test.
-         *    @access public
-         */
-        function getLabel() {
-            return $this->_command;
+    function run(&$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 . ']');
+            return false;
         }
+        return true;
+    }
 
-        /**
-         *    Runs the top level test for this class. Currently
-         *    reads the data as a single chunk. I'll fix this
-         *    once I have added iteration to the browser.
-         *    @param SimpleReporter $reporter    Target of test results.
-         *    @returns boolean                   True if no failures.
-         *    @access public
-         */
-        function run(&$reporter) {
-                       $shell = &new SimpleShell();
-                       $shell->execute($this->_command);
+    /**
+     *    Accessor for the number of subtests.
+     *    @return integer       Number of test cases.
+     *    @access public
+     */
+    function getSize() {
+        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->_command . ']');
+                trigger_error('Cannot parse incoming XML from [' . $this->_dry_command . ']');
                 return false;
             }
-            return true;
-        }
-
-        /**
-         *    Accessor for the number of subtests.
-         *    @return integer       Number of test cases.
-         *    @access public
-         */
-        function getSize() {
-            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 . ']');
-                    return false;
-                }
-                $this->_size = $reporter->getTestCaseCount();
-            }
-            return $this->_size;
+            $this->_size = $reporter->getTestCaseCount();
         }
+        return $this->_size;
+    }
 
-        /**
-         *    Creates the XML parser.
-         *    @param SimpleReporter $reporter    Target of test results.
-         *    @return SimpleTestXmlListener      XML reader.
-         *    @access protected
-         */
-        function &_createParser(&$reporter) {
-            return new SimpleTestXmlParser($reporter);
-        }
+    /**
+     *    Creates the XML parser.
+     *    @param SimpleReporter $reporter    Target of test results.
+     *    @return SimpleTestXmlListener      XML reader.
+     *    @access protected
+     */
+    function &_createParser(&$reporter) {
+        return new SimpleTestXmlParser($reporter);
     }
+}
 ?>
\ No newline at end of file
index ea21e6455d59b892fec3e1e4532cd73b470d9a84..d2281032c342922e61ffaa9d7ce1b517c8501384 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+/**
+ * does type matter
+ */
+if (! defined('TYPE_MATTERS')) {
+    define('TYPE_MATTERS', true);
+}
+
+/**
+ *    Displays variables as text and does diffs.
+ *    @package  SimpleTest
+ *    @subpackage   UnitTester
+ */
+class SimpleDumper {
+    
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
-     */
-    /**
-     * does type matter
+     *    Renders a variable in a shorter form than print_r().
+     *    @param mixed $value      Variable to render as a string.
+     *    @return string           Human readable string form.
+     *    @access public
      */
-    if (! defined('TYPE_MATTERS')) {
-        define('TYPE_MATTERS', true);
+    function describeValue($value) {
+        $type = $this->getType($value);
+        switch($type) {
+            case "Null":
+                return "NULL";
+            case "Boolean":
+                return "Boolean: " . ($value ? "true" : "false");
+            case "Array":
+                return "Array: " . count($value) . " items";
+            case "Object":
+                return "Object: of " . get_class($value);
+            case "String":
+                return "String: " . $this->clipString($value, 200);
+            default:
+                return "$type: $value";
+        }
+        return "Unknown";
     }
     
     /**
-     *    Displays variables as text and does diffs.
-        *        @package      SimpleTest
-        *        @subpackage   UnitTester
+     *    Gets the string representation of a type.
+     *    @param mixed $value    Variable to check against.
+     *    @return string         Type.
+     *    @access public
      */
-    class SimpleDumper {
-        
-        /**
-         *    Renders a variable in a shorter form than print_r().
-         *    @param mixed $value      Variable to render as a string.
-         *    @return string           Human readable string form.
-         *    @access public
-         */
-        function describeValue($value) {
-            $type = $this->getType($value);
-            switch($type) {
-                case "Null":
-                    return "NULL";
-                case "Boolean":
-                    return "Boolean: " . ($value ? "true" : "false");
-                case "Array":
-                    return "Array: " . count($value) . " items";
-                case "Object":
-                    return "Object: of " . get_class($value);
-                case "String":
-                    return "String: " . $this->clipString($value, 200);
-                default:
-                    return "$type: $value";
-            }
-            return "Unknown";
-        }
-        
-        /**
-         *    Gets the string representation of a type.
-         *    @param mixed $value    Variable to check against.
-         *    @return string         Type.
-         *    @access public
-         */
-        function getType($value) {
-            if (! isset($value)) {
-                return "Null";
-            } elseif (is_bool($value)) {
-                return "Boolean";
-            } elseif (is_string($value)) {
-                return "String";
-            } elseif (is_integer($value)) {
-                return "Integer";
-            } elseif (is_float($value)) {
-                return "Float";
-            } elseif (is_array($value)) {
-                return "Array";
-            } elseif (is_resource($value)) {
-                return "Resource";
-            } elseif (is_object($value)) {
-                return "Object";
-            }
-            return "Unknown";
+    function getType($value) {
+        if (! isset($value)) {
+            return "Null";
+        } elseif (is_bool($value)) {
+            return "Boolean";
+        } elseif (is_string($value)) {
+            return "String";
+        } elseif (is_integer($value)) {
+            return "Integer";
+        } elseif (is_float($value)) {
+            return "Float";
+        } elseif (is_array($value)) {
+            return "Array";
+        } elseif (is_resource($value)) {
+            return "Resource";
+        } elseif (is_object($value)) {
+            return "Object";
         }
+        return "Unknown";
+    }
 
-        /**
-         *    Creates a human readable description of the
-         *    difference between two variables. Uses a
-         *    dynamic call.
-         *    @param mixed $first        First variable.
-         *    @param mixed $second       Value to compare with.
-         *    @param boolean $identical  If true then type anomolies count.
-         *    @return string             Description of difference.
-         *    @access public
-         */
-        function describeDifference($first, $second, $identical = false) {
-            if ($identical) {
-                if (! $this->_isTypeMatch($first, $second)) {
-                    return "with type mismatch as [" . $this->describeValue($first) .
-                        "] does not match [" . $this->describeValue($second) . "]";
-                }
-            }
-            $type = $this->getType($first);
-            if ($type == "Unknown") {
-                return "with unknown type";
+    /**
+     *    Creates a human readable description of the
+     *    difference between two variables. Uses a
+     *    dynamic call.
+     *    @param mixed $first        First variable.
+     *    @param mixed $second       Value to compare with.
+     *    @param boolean $identical  If true then type anomolies count.
+     *    @return string             Description of difference.
+     *    @access public
+     */
+    function describeDifference($first, $second, $identical = false) {
+        if ($identical) {
+            if (! $this->_isTypeMatch($first, $second)) {
+                return "with type mismatch as [" . $this->describeValue($first) .
+                    "] does not match [" . $this->describeValue($second) . "]";
             }
-            $method = '_describe' . $type . 'Difference';
-            return $this->$method($first, $second, $identical);
         }
-        
-        /**
-         *    Tests to see if types match.
-         *    @param mixed $first        First variable.
-         *    @param mixed $second       Value to compare with.
-         *    @return boolean            True if matches.
-         *    @access private
-         */
-        function _isTypeMatch($first, $second) {
-            return ($this->getType($first) == $this->getType($second));
+        $type = $this->getType($first);
+        if ($type == "Unknown") {
+            return "with unknown type";
         }
+        $method = '_describe' . $type . 'Difference';
+        return $this->$method($first, $second, $identical);
+    }
+    
+    /**
+     *    Tests to see if types match.
+     *    @param mixed $first        First variable.
+     *    @param mixed $second       Value to compare with.
+     *    @return boolean            True if matches.
+     *    @access private
+     */
+    function _isTypeMatch($first, $second) {
+        return ($this->getType($first) == $this->getType($second));
+    }
 
-        /**
-         *    Clips a string to a maximum length.
-         *    @param string $value         String to truncate.
-         *    @param integer $size         Minimum string size to show.
-         *    @param integer $position     Centre of string section.
-         *    @return string               Shortened version.
-         *    @access public
-         */
-        function clipString($value, $size, $position = 0) {
-            $length = strlen($value);
-            if ($length <= $size) {
-                return $value;
-            }
-            $position = min($position, $length);
-            $start = ($size/2 > $position ? 0 : $position - $size/2);
-            if ($start + $size > $length) {
-                $start = $length - $size;
-            }
-            $value = substr($value, $start, $size);
-            return ($start > 0 ? "..." : "") . $value . ($start + $size < $length ? "..." : "");
+    /**
+     *    Clips a string to a maximum length.
+     *    @param string $value         String to truncate.
+     *    @param integer $size         Minimum string size to show.
+     *    @param integer $position     Centre of string section.
+     *    @return string               Shortened version.
+     *    @access public
+     */
+    function clipString($value, $size, $position = 0) {
+        $length = strlen($value);
+        if ($length <= $size) {
+            return $value;
         }
-        
-        /**
-         *    Creates a human readable description of the
-         *    difference between two variables. The minimal
-         *    version.
-         *    @param null $first          First value.
-         *    @param mixed $second        Value to compare with.
-         *    @return string              Human readable description.
-         *    @access private
-         */
-        function _describeGenericDifference($first, $second) {
-            return "as [" . $this->describeValue($first) .
-                    "] does not match [" .
-                    $this->describeValue($second) . "]";
+        $position = min($position, $length);
+        $start = ($size/2 > $position ? 0 : $position - $size/2);
+        if ($start + $size > $length) {
+            $start = $length - $size;
         }
-        
-        /**
-         *    Creates a human readable description of the
-         *    difference between a null and another variable.
-         *    @param null $first          First null.
-         *    @param mixed $second        Null to compare with.
-         *    @param boolean $identical   If true then type anomolies count.
-         *    @return string              Human readable description.
-         *    @access private
-         */
-        function _describeNullDifference($first, $second, $identical) {
+        $value = substr($value, $start, $size);
+        return ($start > 0 ? "..." : "") . $value . ($start + $size < $length ? "..." : "");
+    }
+    
+    /**
+     *    Creates a human readable description of the
+     *    difference between two variables. The minimal
+     *    version.
+     *    @param null $first          First value.
+     *    @param mixed $second        Value to compare with.
+     *    @return string              Human readable description.
+     *    @access private
+     */
+    function _describeGenericDifference($first, $second) {
+        return "as [" . $this->describeValue($first) .
+                "] does not match [" .
+                $this->describeValue($second) . "]";
+    }
+    
+    /**
+     *    Creates a human readable description of the
+     *    difference between a null and another variable.
+     *    @param null $first          First null.
+     *    @param mixed $second        Null to compare with.
+     *    @param boolean $identical   If true then type anomolies count.
+     *    @return string              Human readable description.
+     *    @access private
+     */
+    function _describeNullDifference($first, $second, $identical) {
+        return $this->_describeGenericDifference($first, $second);
+    }
+    
+    /**
+     *    Creates a human readable description of the
+     *    difference between a boolean and another variable.
+     *    @param boolean $first       First boolean.
+     *    @param mixed $second        Boolean to compare with.
+     *    @param boolean $identical   If true then type anomolies count.
+     *    @return string              Human readable description.
+     *    @access private
+     */
+    function _describeBooleanDifference($first, $second, $identical) {
+        return $this->_describeGenericDifference($first, $second);
+    }
+    
+    /**
+     *    Creates a human readable description of the
+     *    difference between a string and another variable.
+     *    @param string $first        First string.
+     *    @param mixed $second        String to compare with.
+     *    @param boolean $identical   If true then type anomolies count.
+     *    @return string              Human readable description.
+     *    @access private
+     */
+    function _describeStringDifference($first, $second, $identical) {
+        if (is_object($second) || is_array($second)) {
             return $this->_describeGenericDifference($first, $second);
         }
-        
-        /**
-         *    Creates a human readable description of the
-         *    difference between a boolean and another variable.
-         *    @param boolean $first       First boolean.
-         *    @param mixed $second        Boolean to compare with.
-         *    @param boolean $identical   If true then type anomolies count.
-         *    @return string              Human readable description.
-         *    @access private
-         */
-        function _describeBooleanDifference($first, $second, $identical) {
+        $position = $this->_stringDiffersAt($first, $second);
+        $message = "at character $position";
+        $message .= " with [" .
+                $this->clipString($first, 200, $position) . "] and [" .
+                $this->clipString($second, 200, $position) . "]";
+        return $message;
+    }
+    
+    /**
+     *    Creates a human readable description of the
+     *    difference between an integer and another variable.
+     *    @param integer $first       First number.
+     *    @param mixed $second        Number to compare with.
+     *    @param boolean $identical   If true then type anomolies count.
+     *    @return string              Human readable description.
+     *    @access private
+     */
+    function _describeIntegerDifference($first, $second, $identical) {
+        if (is_object($second) || is_array($second)) {
             return $this->_describeGenericDifference($first, $second);
         }
-        
-        /**
-         *    Creates a human readable description of the
-         *    difference between a string and another variable.
-         *    @param string $first        First string.
-         *    @param mixed $second        String to compare with.
-         *    @param boolean $identical   If true then type anomolies count.
-         *    @return string              Human readable description.
-         *    @access private
-         */
-        function _describeStringDifference($first, $second, $identical) {
-            if (is_object($second) || is_array($second)) {
-                return $this->_describeGenericDifference($first, $second);
-            }
-            $position = $this->_stringDiffersAt($first, $second);
-            $message = "at character $position";
-            $message .= " with [" .
-                    $this->clipString($first, 200, $position) . "] and [" .
-                    $this->clipString($second, 200, $position) . "]";
-            return $message;
+        return "because [" . $this->describeValue($first) .
+                "] differs from [" .
+                $this->describeValue($second) . "] by " .
+                abs($first - $second);
+    }
+    
+    /**
+     *    Creates a human readable description of the
+     *    difference between two floating point numbers.
+     *    @param float $first         First float.
+     *    @param mixed $second        Float to compare with.
+     *    @param boolean $identical   If true then type anomolies count.
+     *    @return string              Human readable description.
+     *    @access private
+     */
+    function _describeFloatDifference($first, $second, $identical) {
+        if (is_object($second) || is_array($second)) {
+            return $this->_describeGenericDifference($first, $second);
         }
-        
-        /**
-         *    Creates a human readable description of the
-         *    difference between an integer and another variable.
-         *    @param integer $first       First number.
-         *    @param mixed $second        Number to compare with.
-         *    @param boolean $identical   If true then type anomolies count.
-         *    @return string              Human readable description.
-         *    @access private
-         */
-        function _describeIntegerDifference($first, $second, $identical) {
-            if (is_object($second) || is_array($second)) {
-                return $this->_describeGenericDifference($first, $second);
-            }
-            return "because [" . $this->describeValue($first) .
-                    "] differs from [" .
-                    $this->describeValue($second) . "] by " .
-                    abs($first - $second);
+        return "because [" . $this->describeValue($first) .
+                "] differs from [" .
+                $this->describeValue($second) . "] by " .
+                abs($first - $second);
+    }
+    
+    /**
+     *    Creates a human readable description of the
+     *    difference between two arrays.
+     *    @param array $first         First array.
+     *    @param mixed $second        Array to compare with.
+     *    @param boolean $identical   If true then type anomolies count.
+     *    @return string              Human readable description.
+     *    @access private
+     */
+    function _describeArrayDifference($first, $second, $identical) {
+        if (! is_array($second)) {
+            return $this->_describeGenericDifference($first, $second);
         }
-        
-        /**
-         *    Creates a human readable description of the
-         *    difference between two floating point numbers.
-         *    @param float $first         First float.
-         *    @param mixed $second        Float to compare with.
-         *    @param boolean $identical   If true then type anomolies count.
-         *    @return string              Human readable description.
-         *    @access private
-         */
-        function _describeFloatDifference($first, $second, $identical) {
-            if (is_object($second) || is_array($second)) {
-                return $this->_describeGenericDifference($first, $second);
-            }
-            return "because [" . $this->describeValue($first) .
-                    "] differs from [" .
-                    $this->describeValue($second) . "] by " .
-                    abs($first - $second);
+        if (! $this->_isMatchingKeys($first, $second, $identical)) {
+            return "as key list [" .
+                    implode(", ", array_keys($first)) . "] does not match key list [" .
+                    implode(", ", array_keys($second)) . "]";
         }
-        
-        /**
-         *    Creates a human readable description of the
-         *    difference between two arrays.
-         *    @param array $first         First array.
-         *    @param mixed $second        Array to compare with.
-         *    @param boolean $identical   If true then type anomolies count.
-         *    @return string              Human readable description.
-         *    @access private
-         */
-        function _describeArrayDifference($first, $second, $identical) {
-            if (! is_array($second)) {
-                return $this->_describeGenericDifference($first, $second);
-            }
-            if (! $this->_isMatchingKeys($first, $second, $identical)) {
-                return "as key list [" .
-                        implode(", ", array_keys($first)) . "] does not match key list [" .
-                        implode(", ", array_keys($second)) . "]";
+        foreach (array_keys($first) as $key) {
+            if ($identical && ($first[$key] === $second[$key])) {
+                continue;
             }
-            foreach (array_keys($first) as $key) {
-                if ($identical && ($first[$key] === $second[$key])) {
-                    continue;
-                }
-                if (! $identical && ($first[$key] == $second[$key])) {
-                    continue;
-                }
-                return "with member [$key] " . $this->describeDifference(
-                        $first[$key],
-                        $second[$key],
-                        $identical);
+            if (! $identical && ($first[$key] == $second[$key])) {
+                continue;
             }
-            return "";
+            return "with member [$key] " . $this->describeDifference(
+                    $first[$key],
+                    $second[$key],
+                    $identical);
         }
-        
-        /**
-         *    Compares two arrays to see if their key lists match.
-         *    For an identical match, the ordering and types of the keys
-         *    is significant.
-         *    @param array $first         First array.
-         *    @param array $second        Array to compare with.
-         *    @param boolean $identical   If true then type anomolies count.
-         *    @return boolean             True if matching.
-         *    @access private
-         */
-        function _isMatchingKeys($first, $second, $identical) {
-            $first_keys = array_keys($first);
-            $second_keys = array_keys($second);
-            if ($identical) {
-                return ($first_keys === $second_keys);
-            }
-            sort($first_keys);
-            sort($second_keys);
-            return ($first_keys == $second_keys);
+        return "";
+    }
+    
+    /**
+     *    Compares two arrays to see if their key lists match.
+     *    For an identical match, the ordering and types of the keys
+     *    is significant.
+     *    @param array $first         First array.
+     *    @param array $second        Array to compare with.
+     *    @param boolean $identical   If true then type anomolies count.
+     *    @return boolean             True if matching.
+     *    @access private
+     */
+    function _isMatchingKeys($first, $second, $identical) {
+        $first_keys = array_keys($first);
+        $second_keys = array_keys($second);
+        if ($identical) {
+            return ($first_keys === $second_keys);
         }
-        
-        /**
-         *    Creates a human readable description of the
-         *    difference between a resource and another variable.
-         *    @param resource $first       First resource.
-         *    @param mixed $second         Resource to compare with.
-         *    @param boolean $identical    If true then type anomolies count.
-         *    @return string              Human readable description.
-         *    @access private
-         */
-        function _describeResourceDifference($first, $second, $identical) {
+        sort($first_keys);
+        sort($second_keys);
+        return ($first_keys == $second_keys);
+    }
+    
+    /**
+     *    Creates a human readable description of the
+     *    difference between a resource and another variable.
+     *    @param resource $first       First resource.
+     *    @param mixed $second         Resource to compare with.
+     *    @param boolean $identical    If true then type anomolies count.
+     *    @return string              Human readable description.
+     *    @access private
+     */
+    function _describeResourceDifference($first, $second, $identical) {
+        return $this->_describeGenericDifference($first, $second);
+    }
+    
+    /**
+     *    Creates a human readable description of the
+     *    difference between two objects.
+     *    @param object $first        First object.
+     *    @param mixed $second        Object to compare with.
+     *    @param boolean $identical   If true then type anomolies count.
+     *    @return string              Human readable description.
+     *    @access private
+     */
+    function _describeObjectDifference($first, $second, $identical) {
+        if (! is_object($second)) {
             return $this->_describeGenericDifference($first, $second);
         }
-        
-        /**
-         *    Creates a human readable description of the
-         *    difference between two objects.
-         *    @param object $first        First object.
-         *    @param mixed $second        Object to compare with.
-         *    @param boolean $identical   If true then type anomolies count.
-         *    @return string              Human readable description.
-         *    @access private
-         */
-        function _describeObjectDifference($first, $second, $identical) {
-            if (! is_object($second)) {
-                return $this->_describeGenericDifference($first, $second);
-            }
-            return $this->_describeArrayDifference(
-                    get_object_vars($first),
-                    get_object_vars($second),
-                    $identical);
+        return $this->_describeArrayDifference(
+                get_object_vars($first),
+                get_object_vars($second),
+                $identical);
+    }
+    
+    /**
+     *    Find the first character position that differs
+     *    in two strings by binary chop.
+     *    @param string $first        First string.
+     *    @param string $second       String to compare with.
+     *    @return integer             Position of first differing
+     *                                character.
+     *    @access private
+     */
+    function _stringDiffersAt($first, $second) {
+        if (! $first || ! $second) {
+            return 0;
         }
-        
-        /**
-         *    Find the first character position that differs
-         *    in two strings by binary chop.
-         *    @param string $first        First string.
-         *    @param string $second       String to compare with.
-         *    @return integer             Position of first differing
-         *                                character.
-         *    @access private
-         */
-        function _stringDiffersAt($first, $second) {
-            if (! $first || ! $second) {
-                return 0;
-            }
-            if (strlen($first) < strlen($second)) {
-                list($first, $second) = array($second, $first);
-            }
-            $position = 0;
-            $step = strlen($first);
-            while ($step > 1) {
-                $step = (integer)(($step + 1) / 2);
-                if (strncmp($first, $second, $position + $step) == 0) {
-                    $position += $step;
-                }
-            }
-            return $position;
+        if (strlen($first) < strlen($second)) {
+            list($first, $second) = array($second, $first);
         }
-        
-        /**
-         *    Sends a formatted dump of a variable to a string.
-         *    @param mixed $variable    Variable to display.
-         *    @return string            Output from print_r().
-         *    @access public
-         *    @static
-         */
-        function dump($variable) {
-            ob_start();
-            print_r($variable);
-            $formatted = ob_get_contents();
-            ob_end_clean();
-            return $formatted;
+        $position = 0;
+        $step = strlen($first);
+        while ($step > 1) {
+            $step = (integer)(($step + 1) / 2);
+            if (strncmp($first, $second, $position + $step) == 0) {
+                $position += $step;
+            }
         }
+        return $position;
+    }
+    
+    /**
+     *    Sends a formatted dump of a variable to a string.
+     *    @param mixed $variable    Variable to display.
+     *    @return string            Output from print_r().
+     *    @access public
+     *    @static
+     */
+    function dump($variable) {
+        ob_start();
+        print_r($variable);
+        $formatted = ob_get_contents();
+        ob_end_clean();
+        return $formatted;
     }
+}
 ?>
\ No newline at end of file
index abd7251ebbfd1426353295575f10520a14f73512..c5e715458cad63d875fc93b7c1543bd581d8d307 100644 (file)
 <?php
 /**
-*      base include file for eclipse plugin 
-*      @package        SimpleTest
-*      @version        $Id$
-*/
-include_once "xml.php";
-include_once "invoker.php";
-include_once "socket.php";
-class EclipseReporter extends XmlReporter {
-       var $_port;
-       function EclipseReporter($port){
-               $this->_port = $port;
-               $this->XmlReporter();
-       }
-       
-       function &createInvoker(&$invoker){
-               $eclinvoker = &new EclipseInvoker($invoker, $this->_port);
-               return $eclinvoker;
-       }
-       
-       function paintMethodStart($method) {
-               parent::paintGroupStart($this->_group, $this->_size);
-               parent::paintCaseStart($this->_case);
-               parent::paintMethodStart($method);
-       }
-       
-       function paintMethodEnd($method){
-               parent::paintMethodEnd($method);
-               parent::paintCaseEnd($this->_case);
-               parent::paintGroupEnd($this->_group);
-               
-       }
-       
-       function paintCaseStart($case){
-               $this->_case = $case;
-       }
-       
-       function paintCaseEnd($case){
-               $this->_case = "";
-       }
-       function paintGroupStart($group,$size){
-               $this->_group = $group;
-       }
-       function paintGroupEnd($group){
-               $this->_group = "";
-       }
+ *  base include file for eclipse plugin  
+ *  @package    SimpleTest
+ *  @subpackage Eclipse
+ *  @version    $Id$
+ */
+/**#@+
+ * simpletest include files
+ */
+include_once 'unit_tester.php';
+include_once 'test_case.php';
+include_once 'invoker.php';
+include_once 'socket.php';
+include_once 'mock_objects.php';
+/**#@-*/
+
+/**
+ *  base reported class for eclipse plugin  
+ *  @package    SimpleTest
+ *  @subpackage Eclipse
+ */
+class EclipseReporter extends SimpleScorer {
+    
+    /**
+     *    Reporter to be run inside of Eclipse interface.
+     *    @param object $listener   Eclipse listener (?).
+     *    @param boolean $cc        Whether to include test coverage.
+     */
+    function EclipseReporter(&$listener, $cc=false){
+        $this->_listener = &$listener;
+        $this->SimpleScorer();
+        $this->_case = "";
+        $this->_group = "";
+        $this->_method = "";
+        $this->_cc = $cc;
+        $this->_error = false;
+        $this->_fail = false;
+    }
+    
+    /**
+     *    Means to display human readable object comparisons.
+     *    @return SimpleDumper        Visual comparer.
+     */
+    function getDumper() {
+        return new SimpleDumper();
+    }
+    
+    /**
+     *    Localhost connection from Eclipse.
+     *    @param integer $port      Port to connect to Eclipse.
+     *    @param string $host       Normally localhost.
+     *    @return SimpleSocket      Connection to Eclipse.
+     */
+    function &createListener($port, $host="127.0.0.1"){
+        $tmplistener = &new SimpleSocket($host, $port, 5);
+        return $tmplistener;
+    }
+    
+    /**
+     *    Wraps the test in an output buffer.
+     *    @param SimpleInvoker $invoker     Current test runner.
+     *    @return EclipseInvoker            Decorator with output buffering.
+     *    @access public
+     */
+    function &createInvoker(&$invoker){
+        $eclinvoker = &new EclipseInvoker($invoker, $this->_listener);
+        return $eclinvoker;
+    }
+    
+    /**
+     *    C style escaping.
+     *    @param string $raw    String with backslashes, quotes and whitespace.
+     *    @return string        Replaced with C backslashed tokens.
+     */
+    function escapeVal($raw){
+        $needle = array("\\","\"","/","\b","\f","\n","\r","\t");
+        $replace = array('\\\\','\"','\/','\b','\f','\n','\r','\t');
+        return str_replace($needle, $replace, $raw);
+    }
+    
+    /**
+     *    Stash the first passing item. Clicking the test
+     *    item goes to first pass.
+     *    @param string $message    Test message, but we only wnat the first.
+     *    @access public
+     */
+    function paintPass($message){
+        if (! $this->_pass){
+            $this->_message = $this->escapeVal($message);
+        }
+        $this->_pass = true;
+    }
+    
+    /**
+     *    Stash the first failing item. Clicking the test
+     *    item goes to first fail.
+     *    @param string $message    Test message, but we only wnat the first.
+     *    @access public
+     */
+    function paintFail($message){
+        //only get the first failure or error
+        if (! $this->_fail && ! $this->_error){
+            $this->_fail = true;
+            $this->_message = $this->escapeVal($message);
+            $this->_listener->write('{status:"fail",message:"'.$this->_message.'",group:"'.$this->_group.'",case:"'.$this->_case.'",method:"'.$this->_method.'"}');
+        }
+    }
+    
+    /**
+     *    Stash the first error. Clicking the test
+     *    item goes to first error.
+     *    @param string $message    Test message, but we only wnat the first.
+     *    @access public
+     */
+    function paintError($message){
+        if (! $this->_fail && ! $this->_error){
+            $this->_error = true;
+            $this->_message = $this->escapeVal($message);
+            $this->_listener->write('{status:"error",message:"'.$this->_message.'",group:"'.$this->_group.'",case:"'.$this->_case.'",method:"'.$this->_method.'"}');
+        }
+    }
+    
+    
+    /**
+     *    Stash the first exception. Clicking the test
+     *    item goes to first message.
+     *    @param string $message    Test message, but we only wnat the first.
+     *    @access public
+     */
+    function paintException($exception){
+        if (! $this->_fail && ! $this->_error){
+            $this->_error = true;
+            $message = 'Unexpected exception of type[' . get_class($exception) .
+                    '] with message [' . $exception->getMessage() . '] in [' .
+                    $exception->getFile() .' line '. $exception->getLine() . ']';
+            $this->_message = $this->escapeVal($message);
+            $this->_listener->write(
+                    '{status:"error",message:"' . $this->_message . '",group:"' .
+                    $this->_group . '",case:"' . $this->_case . '",method:"' . $this->_method
+                    . '"}');
+        }
+    }
+    
+
+    /**
+     *    We don't display any special header.
+     *    @param string $test_name     First test top level
+     *                                 to start.
+     *    @access public
+     */
+    function paintHeader($test_name) {
+    }
+
+    /**
+     *    We don't display any special footer.
+     *    @param string $test_name        The top level test.
+     *    @access public
+     */
+    function paintFooter($test_name) {
+    }
+    
+    /**
+     *    Paints nothing at the start of a test method, but stash
+     *    the method name for later.
+     *    @param string $test_name   Name of test that is starting.
+     *    @access public
+     */
+    function paintMethodStart($method) {
+        $this->_pass = false;
+        $this->_fail = false;
+        $this->_error = false;
+        $this->_method = $this->escapeVal($method);
+    }
+        
+    /**
+     *    Only send one message if the test passes, after that
+     *    suppress the message.
+     *    @param string $test_name   Name of test that is ending.
+     *    @access public
+     */
+    function paintMethodEnd($method){   
+        if ($this->_fail || $this->_error || ! $this->_pass){
+        } else {
+            $this->_listener->write(
+                        '{status:"pass",message:"' . $this->_message . '",group:"' .
+                        $this->_group . '",case:"' . $this->_case . '",method:"' .
+                        $this->_method . '"}');
+        }
+    }
+    
+    /**
+     *    Stashes the test case name for the later failure message.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintCaseStart($case){
+        $this->_case = $this->escapeVal($case);
+    }
+    
+    /**
+     *    Drops the name.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintCaseEnd($case){
+        $this->_case = "";
+    }
+    
+    /**
+     *    Stashes the name of the test suite. Starts test coverage
+     *    if enabled.
+     *    @param string $group     Name of test or other label.
+     *    @param integer $size     Number of test cases starting.
+     *    @access public
+     */
+    function paintGroupStart($group, $size){
+        $this->_group = $this->escapeVal($group);
+        if ($this->_cc){
+            if (extension_loaded('xdebug')){
+                xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); 
+            }
+        }
+    }
+
+    /**
+     *    Paints coverage report if enabled.
+     *    @param string $group     Name of test or other label.
+     *    @access public
+     */
+    function paintGroupEnd($group){
+        $this->_group = "";
+        $cc = "";
+        if ($this->_cc){
+            if (extension_loaded('xdebug')){
+                $arrfiles = xdebug_get_code_coverage();
+                xdebug_stop_code_coverage();
+                $thisdir = dirname(__FILE__);
+                $thisdirlen = strlen($thisdir);
+                foreach ($arrfiles as $index=>$file){
+                    if (substr($index, 0, $thisdirlen)===$thisdir){
+                        continue;
+                    }
+                    $lcnt = 0;
+                    $ccnt = 0;
+                    foreach ($file as $line){
+                        if ($line == -2){
+                            continue;
+                        }
+                        $lcnt++;
+                        if ($line==1){
+                            $ccnt++;
+                        }
+                    }
+                    if ($lcnt > 0){
+                        $cc .= round(($ccnt/$lcnt) * 100, 2) . '%';
+                    }else{
+                        $cc .= "0.00%";
+                    }
+                    $cc.= "\t". $index . "\n";
+                }
+            }
+        }
+        $this->_listener->write('{status:"coverage",message:"' .
+                                EclipseReporter::escapeVal($cc) . '"}');
+    }
 }
 
+/**
+ *  Invoker decorator for Eclipse. Captures output until
+ *  the end of the test.  
+ *  @package    SimpleTest
+ *  @subpackage Eclipse
+ */
 class EclipseInvoker extends SimpleInvokerDecorator{
-       var $_port;
-       function EclipseInvoker(&$invoker,$port) {
-               $this->_port = $port;
-               $this->SimpleInvokerDecorator($invoker);
-       }
-       
-       function invoke($method) {
-               ob_start();
-               parent::invoke($method);
-               $output = ob_get_contents();
-               ob_end_clean();
+    function EclipseInvoker(&$invoker, &$listener) {
+        $this->_listener = &$listener;
+        $this->SimpleInvokerDecorator($invoker);
+    }
+    
+    /**
+     *    Starts output buffering.
+     *    @param string $method    Test method to call.
+     *    @access public
+     */
+    function before($method){
+        ob_start();
+        $this->_invoker->before($method);
+    }
 
-               $sock = new SimpleSocket("127.0.0.1",$this->_port,5);
-               $sock->write($output);
-               $sock->close();
-               echo $sock->getError();
-       }
+    /**
+     *    Stops output buffering and send the captured output
+     *    to the listener.
+     *    @param string $method    Test method to call.
+     *    @access public
+     */
+    function after($method) {
+        $this->_invoker->after($method);
+        $output = ob_get_contents();
+        ob_end_clean();
+        if ($output !== ""){
+            $result = $this->_listener->write('{status:"info",message:"' .
+                                              EclipseReporter::escapeVal($output) . '"}');
+        }
+    }
 }
-       
 ?>
\ No newline at end of file
index bffa0713199035dd11735c7d2b784a0acaa675c5..4a4b9749016af1fbed16e9d0ee034b09f459cb12 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage WebTester
+ *  @version    $Id$
+ */
+    
+/**#@+
+ *  include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/socket.php');
+/**#@-*/
+
+/**
+ *    Single post parameter.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleEncodedPair {
+    var $_key;
+    var $_value;
+    
+    /**
+     *    Stashes the data for rendering later.
+     *    @param string $key       Form element name.
+     *    @param string $value     Data to send.
+     */
+    function SimpleEncodedPair($key, $value) {
+        $this->_key = $key;
+        $this->_value = $value;
+    }
+    
+    /**
+     *    The pair as a single string.
+     *    @return string        Encoded pair.
+     *    @access public
+     */
+    function asRequest() {
+        return urlencode($this->_key) . '=' . urlencode($this->_value);
+    }
+    
+    /**
+     *    The MIME part as a string.
+     *    @return string        MIME part encoding.
+     *    @access public
+     */
+    function asMime() {
+        $part = 'Content-Disposition: form-data; ';
+        $part .= "name=\"" . $this->_key . "\"\r\n";
+        $part .= "\r\n" . $this->_value;
+        return $part;
+    }
+    
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     WebTester
-     * @version        $Id$
+     *    Is this the value we are looking for?
+     *    @param string $key    Identifier.
+     *    @return boolean       True if matched.
+     *    @access public
      */
-     
-    /**#@+
-     * include other SimpleTest class files
+    function isKey($key) {
+        return $key == $this->_key;
+    }
+    
+    /**
+     *    Is this the value we are looking for?
+     *    @return string       Identifier.
+     *    @access public
+     */
+    function getKey() {
+        return $this->_key;
+    }
+    
+    /**
+     *    Is this the value we are looking for?
+     *    @return string       Content.
+     *    @access public
      */
-    require_once(dirname(__FILE__) . '/socket.php');
-    /**#@-*/
+    function getValue() {
+        return $this->_value;
+    }
+}
 
+/**
+ *    Single post parameter.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleAttachment {
+    var $_key;
+    var $_content;
+    var $_filename;
+    
     /**
-     *    Single post parameter.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Stashes the data for rendering later.
+     *    @param string $key          Key to add value to.
+     *    @param string $content      Raw data.
+     *    @param hash $filename       Original filename.
      */
-    class SimpleEncodedPair {
-        var $_key;
-        var $_value;
-        
-        /**
-         *    Stashes the data for rendering later.
-         *    @param string $key       Form element name.
-         *    @param string $value     Data to send.
-         */
-        function SimpleEncodedPair($key, $value) {
-            $this->_key = $key;
-            $this->_value = $value;
-        }
-        
-        /**
-         *    The pair as a single string.
-         *    @return string        Encoded pair.
-         *    @access public
-         */
-        function asRequest() {
-            return urlencode($this->_key) . '=' . urlencode($this->_value);
-        }
-        
-        /**
-         *    The MIME part as a string.
-         *    @return string        MIME part encoding.
-         *    @access public
-         */
-        function asMime() {
-            $part = 'Content-Disposition: form-data; ';
-            $part .= "name=\"" . $this->_key . "\"\r\n";
-            $part .= "\r\n" . $this->_value;
-            return $part;
-        }
-        
-        /**
-         *    Is this the value we are looking for?
-         *    @param string $key    Identifier.
-         *    @return boolean       True if matched.
-         *    @access public
-         */
-        function isKey($key) {
-            return $key == $this->_key;
-        }
-        
-        /**
-         *    Is this the value we are looking for?
-         *    @return string       Identifier.
-         *    @access public
-         */
-        function getKey() {
-            return $this->_key;
+    function SimpleAttachment($key, $content, $filename) {
+        $this->_key = $key;
+        $this->_content = $content;
+        $this->_filename = $filename;
+    }
+    
+    /**
+     *    The pair as a single string.
+     *    @return string        Encoded pair.
+     *    @access public
+     */
+    function asRequest() {
+        return '';
+    }
+    
+    /**
+     *    The MIME part as a string.
+     *    @return string        MIME part encoding.
+     *    @access public
+     */
+    function asMime() {
+        $part = 'Content-Disposition: form-data; ';
+        $part .= 'name="' . $this->_key . '"; ';
+        $part .= 'filename="' . $this->_filename . '"';
+        $part .= "\r\nContent-Type: " . $this->_deduceMimeType();
+        $part .= "\r\n\r\n" . $this->_content;
+        return $part;
+    }
+    
+    /**
+     *    Attempts to figure out the MIME type from the
+     *    file extension and the content.
+     *    @return string        MIME type.
+     *    @access private
+     */
+    function _deduceMimeType() {
+        if ($this->_isOnlyAscii($this->_content)) {
+            return 'text/plain';
         }
-        
-        /**
-         *    Is this the value we are looking for?
-         *    @return string       Content.
-         *    @access public
-         */
-        function getValue() {
-            return $this->_value;
+        return 'application/octet-stream';
+    }
+    
+    /**
+     *    Tests each character is in the range 0-127.
+     *    @param string $ascii    String to test.
+     *    @access private
+     */
+    function _isOnlyAscii($ascii) {
+        for ($i = 0, $length = strlen($ascii); $i < $length; $i++) {
+            if (ord($ascii[$i]) > 127) {
+                return false;
+            }
         }
+        return true;
+    }
+    
+    /**
+     *    Is this the value we are looking for?
+     *    @param string $key    Identifier.
+     *    @return boolean       True if matched.
+     *    @access public
+     */
+    function isKey($key) {
+        return $key == $this->_key;
+    }
+    
+    /**
+     *    Is this the value we are looking for?
+     *    @return string       Identifier.
+     *    @access public
+     */
+    function getKey() {
+        return $this->_key;
+    }
+    
+    /**
+     *    Is this the value we are looking for?
+     *    @return string       Content.
+     *    @access public
+     */
+    function getValue() {
+        return $this->_filename;
     }
+}
 
+/**
+ *    Bundle of GET/POST parameters. Can include
+ *    repeated parameters.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleEncoding {
+    var $_request;
+    
     /**
-     *    Single post parameter.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Starts empty.
+     *    @param array $query       Hash of parameters.
+     *                              Multiple values are
+     *                              as lists on a single key.
+     *    @access public
      */
-    class SimpleAttachment {
-        var $_key;
-        var $_content;
-        var $_filename;
-        
-        /**
-         *    Stashes the data for rendering later.
-         *    @param string $key          Key to add value to.
-         *    @param string $content      Raw data.
-         *    @param hash $filename       Original filename.
-         */
-        function SimpleAttachment($key, $content, $filename) {
-            $this->_key = $key;
-            $this->_content = $content;
-            $this->_filename = $filename;
+    function SimpleEncoding($query = false) {
+        if (! $query) {
+            $query = array();
         }
-        
-        /**
-         *    The pair as a single string.
-         *    @return string        Encoded pair.
-         *    @access public
-         */
-        function asRequest() {
-            return '';
-        }
-        
-        /**
-         *    The MIME part as a string.
-         *    @return string        MIME part encoding.
-         *    @access public
-         */
-        function asMime() {
-            $part = 'Content-Disposition: form-data; ';
-            $part .= 'name="' . $this->_key . '"; ';
-            $part .= 'filename="' . $this->_filename . '"';
-            $part .= "\r\nContent-Type: " . $this->_deduceMimeType();
-            $part .= "\r\n\r\n" . $this->_content;
-            return $part;
+        $this->clear();
+        $this->merge($query);
+    }
+    
+    /**
+     *    Empties the request of parameters.
+     *    @access public
+     */
+    function clear() {
+        $this->_request = array();
+    }
+    
+    /**
+     *    Adds a parameter to the query.
+     *    @param string $key            Key to add value to.
+     *    @param string/array $value    New data.
+     *    @access public
+     */
+    function add($key, $value) {
+        if ($value === false) {
+            return;
         }
-        
-        /**
-         *    Attempts to figure out the MIME type from the
-         *    file extension and the content.
-         *    @return string        MIME type.
-         *    @access private
-         */
-        function _deduceMimeType() {
-            if ($this->_isOnlyAscii($this->_content)) {
-                return 'text/plain';
+        if (is_array($value)) {
+            foreach ($value as $item) {
+                $this->_addPair($key, $item);
             }
-            return 'application/octet-stream';
+        } else {
+            $this->_addPair($key, $value);
         }
-        
-        /**
-         *    Tests each character is in the range 0-127.
-         *    @param string $ascii    String to test.
-         *    @access private
-         */
-        function _isOnlyAscii($ascii) {
-            for ($i = 0, $length = strlen($ascii); $i < $length; $i++) {
-                if (ord($ascii[$i]) > 127) {
-                    return false;
-                }
+    }
+    
+    /**
+     *    Adds a new value into the request.
+     *    @param string $key            Key to add value to.
+     *    @param string/array $value    New data.
+     *    @access private
+     */
+    function _addPair($key, $value) {
+        $this->_request[] = new SimpleEncodedPair($key, $value);
+    }
+    
+    /**
+     *    Adds a MIME part to the query. Does nothing for a
+     *    form encoded packet.
+     *    @param string $key          Key to add value to.
+     *    @param string $content      Raw data.
+     *    @param hash $filename       Original filename.
+     *    @access public
+     */
+    function attach($key, $content, $filename) {
+        $this->_request[] = new SimpleAttachment($key, $content, $filename);
+    }
+    
+    /**
+     *    Adds a set of parameters to this query.
+     *    @param array/SimpleQueryString $query  Multiple values are
+     *                                           as lists on a single key.
+     *    @access public
+     */
+    function merge($query) {
+        if (is_object($query)) {
+            $this->_request = array_merge($this->_request, $query->getAll());
+        } elseif (is_array($query)) {
+            foreach ($query as $key => $value) {
+                $this->add($key, $value);
             }
-            return true;
-        }
-        
-        /**
-         *    Is this the value we are looking for?
-         *    @param string $key    Identifier.
-         *    @return boolean       True if matched.
-         *    @access public
-         */
-        function isKey($key) {
-            return $key == $this->_key;
-        }
-        
-        /**
-         *    Is this the value we are looking for?
-         *    @return string       Identifier.
-         *    @access public
-         */
-        function getKey() {
-            return $this->_key;
-        }
-        
-        /**
-         *    Is this the value we are looking for?
-         *    @return string       Content.
-         *    @access public
-         */
-        function getValue() {
-            return $this->_filename;
         }
     }
-
+    
     /**
-     *    Bundle of GET/POST parameters. Can include
-     *    repeated parameters.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Accessor for single value.
+     *    @return string/array    False if missing, string
+     *                            if present and array if
+     *                            multiple entries.
+     *    @access public
      */
-    class SimpleEncoding {
-        var $_request;
-        
-        /**
-         *    Starts empty.
-         *    @param array $query       Hash of parameters.
-         *                              Multiple values are
-         *                              as lists on a single key.
-         *    @access public
-         */
-        function SimpleEncoding($query = false) {
-            if (! $query) {
-                $query = array();
+    function getValue($key) {
+        $values = array();
+        foreach ($this->_request as $pair) {
+            if ($pair->isKey($key)) {
+                $values[] = $pair->getValue();
             }
-            $this->clear();
-            $this->merge($query);
         }
-        
-        /**
-         *    Empties the request of parameters.
-         *    @access public
-         */
-        function clear() {
-            $this->_request = array();
+        if (count($values) == 0) {
+            return false;
+        } elseif (count($values) == 1) {
+            return $values[0];
+        } else {
+            return $values;
         }
-        
-        /**
-         *    Adds a parameter to the query.
-         *    @param string $key            Key to add value to.
-         *    @param string/array $value    New data.
-         *    @access public
-         */
-        function add($key, $value) {
-            if ($value === false) {
-                return;
-            }
-            if (is_array($value)) {
-                foreach ($value as $item) {
-                    $this->_addPair($key, $item);
-                }
-            } else {
-                $this->_addPair($key, $value);
+    }
+    
+    /**
+     *    Accessor for listing of pairs.
+     *    @return array        All pair objects.
+     *    @access public
+     */
+    function getAll() {
+        return $this->_request;
+    }
+    
+    /**
+     *    Renders the query string as a URL encoded
+     *    request part.
+     *    @return string        Part of URL.
+     *    @access protected
+     */
+    function _encode() {
+        $statements = array();
+        foreach ($this->_request as $pair) {
+            if ($statement = $pair->asRequest()) {
+                $statements[] = $statement;
             }
         }
-        
-        /**
-         *    Adds a new value into the request.
-         *    @param string $key            Key to add value to.
-         *    @param string/array $value    New data.
-         *    @access private
-         */
-        function _addPair($key, $value) {
-            $this->_request[] = new SimpleEncodedPair($key, $value);
-        }
-        
-        /**
-         *    Adds a MIME part to the query. Does nothing for a
-         *    form encoded packet.
-         *    @param string $key          Key to add value to.
-         *    @param string $content      Raw data.
-         *    @param hash $filename       Original filename.
-         *    @access public
-         */
-        function attach($key, $content, $filename) {
-            $this->_request[] = new SimpleAttachment($key, $content, $filename);
+        return implode('&', $statements);
+    }
+}
+
+/**
+ *    Bundle of GET parameters. Can include
+ *    repeated parameters.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleGetEncoding extends SimpleEncoding {
+    
+    /**
+     *    Starts empty.
+     *    @param array $query       Hash of parameters.
+     *                              Multiple values are
+     *                              as lists on a single key.
+     *    @access public
+     */
+    function SimpleGetEncoding($query = false) {
+        $this->SimpleEncoding($query);
+    }
+    
+    /**
+     *    HTTP request method.
+     *    @return string        Always GET.
+     *    @access public
+     */
+    function getMethod() {
+        return 'GET';
+    }
+    
+    /**
+     *    Writes no extra headers.
+     *    @param SimpleSocket $socket        Socket to write to.
+     *    @access public
+     */
+    function writeHeadersTo(&$socket) {
+    }
+    
+    /**
+     *    No data is sent to the socket as the data is encoded into
+     *    the URL.
+     *    @param SimpleSocket $socket        Socket to write to.
+     *    @access public
+     */
+    function writeTo(&$socket) {
+    }
+    
+    /**
+     *    Renders the query string as a URL encoded
+     *    request part for attaching to a URL.
+     *    @return string        Part of URL.
+     *    @access public
+     */
+    function asUrlRequest() {
+        return $this->_encode();
+    }
+}
+
+/**
+ *    Bundle of URL parameters for a HEAD request.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleHeadEncoding extends SimpleGetEncoding {
+    
+    /**
+     *    Starts empty.
+     *    @param array $query       Hash of parameters.
+     *                              Multiple values are
+     *                              as lists on a single key.
+     *    @access public
+     */
+    function SimpleHeadEncoding($query = false) {
+        $this->SimpleGetEncoding($query);
+    }
+    
+    /**
+     *    HTTP request method.
+     *    @return string        Always HEAD.
+     *    @access public
+     */
+    function getMethod() {
+        return 'HEAD';
+    }
+}
+
+/**
+ *    Bundle of POST parameters. Can include
+ *    repeated parameters.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimplePostEncoding extends SimpleEncoding {
+    
+    /**
+     *    Starts empty.
+     *    @param array $query       Hash of parameters.
+     *                              Multiple values are
+     *                              as lists on a single key.
+     *    @access public
+     */
+    function SimplePostEncoding($query = false) {
+        if (is_array($query) and $this->hasMoreThanOneLevel($query)) {
+            $query = $this->rewriteArrayWithMultipleLevels($query);
         }
-        
-        /**
-         *    Adds a set of parameters to this query.
-         *    @param array/SimpleQueryString $query  Multiple values are
-         *                                           as lists on a single key.
-         *    @access public
-         */
-        function merge($query) {
-            if (is_object($query)) {
-                $this->_request = array_merge($this->_request, $query->getAll());
-            } elseif (is_array($query)) {
-                foreach ($query as $key => $value) {
-                    $this->add($key, $value);
-                }
+        $this->SimpleEncoding($query);
+    }
+    
+    function hasMoreThanOneLevel($query) {
+        foreach ($query as $key => $value) {
+            if (is_array($value)) {
+                return true;
             }
         }
-        
-        /**
-         *    Accessor for single value.
-         *    @return string/array    False if missing, string
-         *                            if present and array if
-         *                            multiple entries.
-         *    @access public
-         */
-        function getValue($key) {
-            $values = array();
-            foreach ($this->_request as $pair) {
-                if ($pair->isKey($key)) {
-                    $values[] = $pair->getValue();
+        return false;
+    }
+
+    function rewriteArrayWithMultipleLevels($query) {
+        $query_ = array();
+        foreach ($query as $key => $value) {
+            if (is_array($value)) {
+                foreach ($value as $sub_key => $sub_value) {
+                    $query_[$key."[".$sub_key."]"] = $sub_value;
                 }
-            }
-            if (count($values) == 0) {
-                return false;
-            } elseif (count($values) == 1) {
-                return $values[0];
             } else {
-                return $values;
+                $query_[$key] = $value;
             }
         }
-        
-        /**
-         *    Accessor for listing of pairs.
-         *    @return array        All pair objects.
-         *    @access public
-         */
-        function getAll() {
-            return $this->_request;
+        if ($this->hasMoreThanOneLevel($query_)) {
+            $query_ = $this->rewriteArrayWithMultipleLevels($query_);
         }
         
-        /**
-         *    Renders the query string as a URL encoded
-         *    request part.
-         *    @return string        Part of URL.
-         *    @access protected
-         */
-        function _encode() {
-            $statements = array();
-            foreach ($this->_request as $pair) {
-                if ($statement = $pair->asRequest()) {
-                    $statements[] = $statement;
-                }
-            }
-            return implode('&', $statements);
-        }
+        return $query_;
     }
     
+    
     /**
-     *    Bundle of GET parameters. Can include
-     *    repeated parameters.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    HTTP request method.
+     *    @return string        Always POST.
+     *    @access public
      */
-    class SimpleGetEncoding extends SimpleEncoding {
-        
-        /**
-         *    Starts empty.
-         *    @param array $query       Hash of parameters.
-         *                              Multiple values are
-         *                              as lists on a single key.
-         *    @access public
-         */
-        function SimpleGetEncoding($query = false) {
-            $this->SimpleEncoding($query);
-        }
-        
-        /**
-         *    HTTP request method.
-         *    @return string        Always GET.
-         *    @access public
-         */
-        function getMethod() {
-            return 'GET';
-        }
-        
-        /**
-         *    Writes no extra headers.
-         *    @param SimpleSocket $socket        Socket to write to.
-         *    @access public
-         */
-        function writeHeadersTo(&$socket) {
-        }
-        
-        /**
-         *    No data is sent to the socket as the data is encoded into
-         *    the URL.
-         *    @param SimpleSocket $socket        Socket to write to.
-         *    @access public
-         */
-        function writeTo(&$socket) {
-        }
-        
-        /**
-         *    Renders the query string as a URL encoded
-         *    request part for attaching to a URL.
-         *    @return string        Part of URL.
-         *    @access public
-         */
-        function asUrlRequest() {
-            return $this->_encode();
-        }
+    function getMethod() {
+        return 'POST';
     }
     
     /**
-     *    Bundle of URL parameters for a HEAD request.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Dispatches the form headers down the socket.
+     *    @param SimpleSocket $socket        Socket to write to.
+     *    @access public
      */
-    class SimpleHeadEncoding extends SimpleGetEncoding {
-        
-        /**
-         *    Starts empty.
-         *    @param array $query       Hash of parameters.
-         *                              Multiple values are
-         *                              as lists on a single key.
-         *    @access public
-         */
-        function SimpleHeadEncoding($query = false) {
-            $this->SimpleGetEncoding($query);
-        }
-        
-        /**
-         *    HTTP request method.
-         *    @return string        Always HEAD.
-         *    @access public
-         */
-        function getMethod() {
-            return 'HEAD';
-        }
+    function writeHeadersTo(&$socket) {
+        $socket->write("Content-Length: " . (integer)strlen($this->_encode()) . "\r\n");
+        $socket->write("Content-Type: application/x-www-form-urlencoded\r\n");
     }
     
     /**
-     *    Bundle of POST parameters. Can include
-     *    repeated parameters.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Dispatches the form data down the socket.
+     *    @param SimpleSocket $socket        Socket to write to.
+     *    @access public
      */
-    class SimplePostEncoding extends SimpleEncoding {
-        
-        /**
-         *    Starts empty.
-         *    @param array $query       Hash of parameters.
-         *                              Multiple values are
-         *                              as lists on a single key.
-         *    @access public
-         */
-        function SimplePostEncoding($query = false) {
-            $this->SimpleEncoding($query);
-        }
-        
-        /**
-         *    HTTP request method.
-         *    @return string        Always POST.
-         *    @access public
-         */
-        function getMethod() {
-            return 'POST';
-        }
-        
-        /**
-         *    Dispatches the form headers down the socket.
-         *    @param SimpleSocket $socket        Socket to write to.
-         *    @access public
-         */
-        function writeHeadersTo(&$socket) {
-            $socket->write("Content-Length: " . (integer)strlen($this->_encode()) . "\r\n");
-            $socket->write("Content-Type: application/x-www-form-urlencoded\r\n");
-        }
-        
-        /**
-         *    Dispatches the form data down the socket.
-         *    @param SimpleSocket $socket        Socket to write to.
-         *    @access public
-         */
-        function writeTo(&$socket) {
-            $socket->write($this->_encode());
-        }
-        
-        /**
-         *    Renders the query string as a URL encoded
-         *    request part for attaching to a URL.
-         *    @return string        Part of URL.
-         *    @access public
-         */
-        function asUrlRequest() {
-            return '';
-        }
+    function writeTo(&$socket) {
+        $socket->write($this->_encode());
     }
     
     /**
-     *    Bundle of POST parameters in the multipart
-     *    format. Can include file uploads.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Renders the query string as a URL encoded
+     *    request part for attaching to a URL.
+     *    @return string        Part of URL.
+     *    @access public
      */
-    class SimpleMultipartEncoding extends SimplePostEncoding {
-        var $_boundary;
-        
-        /**
-         *    Starts empty.
-         *    @param array $query       Hash of parameters.
-         *                              Multiple values are
-         *                              as lists on a single key.
-         *    @access public
-         */
-        function SimpleMultipartEncoding($query = false, $boundary = false) {
-            $this->SimplePostEncoding($query);
-            $this->_boundary = ($boundary === false ? uniqid('st') : $boundary);
-        }
-        
-        /**
-         *    Dispatches the form headers down the socket.
-         *    @param SimpleSocket $socket        Socket to write to.
-         *    @access public
-         */
-        function writeHeadersTo(&$socket) {
-            $socket->write("Content-Length: " . (integer)strlen($this->_encode()) . "\r\n");
-            $socket->write("Content-Type: multipart/form-data, boundary=" . $this->_boundary . "\r\n");
-        }
-        
-        /**
-         *    Dispatches the form data down the socket.
-         *    @param SimpleSocket $socket        Socket to write to.
-         *    @access public
-         */
-        function writeTo(&$socket) {
-            $socket->write($this->_encode());
-        }
-        
-        /**
-         *    Renders the query string as a URL encoded
-         *    request part.
-         *    @return string        Part of URL.
-         *    @access public
-         */
-        function _encode() {
-            $stream = '';
-            foreach ($this->_request as $pair) {
-                $stream .= "--" . $this->_boundary . "\r\n";
-                $stream .= $pair->asMime() . "\r\n";
-            }
-            $stream .= "--" . $this->_boundary . "--\r\n";
-            return $stream;
-        }
+    function asUrlRequest() {
+        return '';
+    }
+}
+
+/**
+ *    Bundle of POST parameters in the multipart
+ *    format. Can include file uploads.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleMultipartEncoding extends SimplePostEncoding {
+    var $_boundary;
+    
+    /**
+     *    Starts empty.
+     *    @param array $query       Hash of parameters.
+     *                              Multiple values are
+     *                              as lists on a single key.
+     *    @access public
+     */
+    function SimpleMultipartEncoding($query = false, $boundary = false) {
+        $this->SimplePostEncoding($query);
+        $this->_boundary = ($boundary === false ? uniqid('st') : $boundary);
+    }
+    
+    /**
+     *    Dispatches the form headers down the socket.
+     *    @param SimpleSocket $socket        Socket to write to.
+     *    @access public
+     */
+    function writeHeadersTo(&$socket) {
+        $socket->write("Content-Length: " . (integer)strlen($this->_encode()) . "\r\n");
+        $socket->write("Content-Type: multipart/form-data, boundary=" . $this->_boundary . "\r\n");
+    }
+    
+    /**
+     *    Dispatches the form data down the socket.
+     *    @param SimpleSocket $socket        Socket to write to.
+     *    @access public
+     */
+    function writeTo(&$socket) {
+        $socket->write($this->_encode());
+    }
+    
+    /**
+     *    Renders the query string as a URL encoded
+     *    request part.
+     *    @return string        Part of URL.
+     *    @access public
+     */
+    function _encode() {
+        $stream = '';
+        foreach ($this->_request as $pair) {
+            $stream .= "--" . $this->_boundary . "\r\n";
+            $stream .= $pair->asMime() . "\r\n";
+        }
+        $stream .= "--" . $this->_boundary . "--\r\n";
+        return $stream;
     }
+}
 ?>
\ No newline at end of file
index 1fdcdb1e9d4bcb54ae1e87fe9343a014a59c3b2c..14c4d0626682a374bd1539e803a5b7e6f8d144bc 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**
+ * @ignore - PHP5 compatibility fix.
+ */
+if (! defined('E_STRICT')) {
+    define('E_STRICT', 2048);
+}
+
+/**#@+
+ * Includes SimpleTest files.
+ */
+require_once dirname(__FILE__) . '/invoker.php';
+require_once dirname(__FILE__) . '/test_case.php';
+require_once dirname(__FILE__) . '/expectation.php';
+/**#@-*/
+
+/**
+ *    Extension that traps errors into an error queue.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class SimpleErrorTrappingInvoker extends SimpleInvokerDecorator {
+
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
+     *    Stores the invoker to wrap.
+     *    @param SimpleInvoker $invoker  Test method runner.
      */
-
-    /** @ignore - PHP5 compatibility fix. */
-    if (! defined('E_STRICT')) {
-        define('E_STRICT', 2048);
+    function SimpleErrorTrappingInvoker(&$invoker) {
+        $this->SimpleInvokerDecorator($invoker);
     }
 
-    /**#@+
-     * Includes SimpleTest files.
+    /**
+     *    Invokes a test method and dispatches any
+     *    untrapped errors. Called back from
+     *    the visiting runner.
+     *    @param string $method    Test method to call.
+     *    @access public
+     */
+    function invoke($method) {
+        $queue = &$this->_createErrorQueue();
+        set_error_handler('SimpleTestErrorHandler');
+        parent::invoke($method);
+        restore_error_handler();
+        $queue->tally();
+    }
+    
+    /**
+     *    Wires up the error queue for a single test.
+     *    @return SimpleErrorQueue    Queue connected to the test.
+     *    @access private
      */
-    require_once(dirname(__FILE__) . '/invoker.php');
-    require_once(dirname(__FILE__) . '/test_case.php');
-    require_once(dirname(__FILE__) . '/expectation.php');
+    function &_createErrorQueue() {
+        $context = &SimpleTest::getContext();
+        $test = &$this->getTestCase();
+        $queue = &$context->get('SimpleErrorQueue');
+        $queue->setTestCase($test);
+        return $queue;
+    }
+}
+
+/**
+ *    Error queue used to record trapped
+ *    errors.
+ *    @package  SimpleTest
+ *    @subpackage   UnitTester
+ */
+class SimpleErrorQueue {
+    var $_queue;
+    var $_expectation_queue;
+    var $_test;
+    var $_using_expect_style = false;
 
     /**
-     *    Extension that traps errors into an error queue.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Starts with an empty queue.
      */
-    class SimpleErrorTrappingInvoker extends SimpleInvokerDecorator {
-
-        /**
-         *    Stores the invoker to wrap.
-         *    @param SimpleInvoker $invoker  Test method runner.
-         */
-        function SimpleErrorTrappingInvoker(&$invoker) {
-            $this->SimpleInvokerDecorator($invoker);
-        }
+    function SimpleErrorQueue() {
+        $this->clear();
+    }
 
-        /**
-         *    Invokes a test method and dispatches any
-         *    untrapped errors. Called back from
-         *    the visiting runner.
-         *    @param string $method    Test method to call.
-         *    @access public
-         */
-        function invoke($method) {
-            $context = &SimpleTest::getContext();
-            $queue = &$context->get('SimpleErrorQueue');
-            $queue->setTestCase($this->GetTestCase());
-            set_error_handler('SimpleTestErrorHandler');
-            parent::invoke($method);
-            while (list($severity, $message, $file, $line) = $queue->extract()) {
-                $severity = SimpleErrorQueue::getSeverityAsString($severity);
-                $test = &$this->getTestCase();
-                $test->error($severity, $message, $file, $line);
-            }
-            restore_error_handler();
-        }
+    /**
+     *    Discards the contents of the error queue.
+     *    @access public
+     */
+    function clear() {
+        $this->_queue = array();
+        $this->_expectation_queue = array();
     }
 
     /**
-     *    Singleton error queue used to record trapped
-     *    errors.
-        *        @package      SimpleTest
-        *        @subpackage   UnitTester
+     *    Sets the currently running test case.
+     *    @param SimpleTestCase $test    Test case to send messages to.
+     *    @access public
      */
-    class SimpleErrorQueue {
-        var $_queue;
-        var $_expectation_queue;
-        var $_test;
-
-        /**
-         *    Starts with an empty queue.
-         */
-        function SimpleErrorQueue() {
-            $this->clear();
-        }
+    function setTestCase(&$test) {
+        $this->_test = &$test;
+    }
 
-        /**
-         *    Sets the currently running test case.
-         *    @param SimpleTestCase $test    Test case to send messages to.
-         *    @access public
-         */
-        function setTestCase(&$test) {
-            $this->_test = &$test;
-        }
+    /**
+     *    Sets up an expectation of an error. If this is
+     *    not fulfilled at the end of the test, a failure
+     *    will occour. If the error does happen, then this
+     *    will cancel it out and send a pass message.
+     *    @param SimpleExpectation $expected    Expected error match.
+     *    @param string $message                Message to display.
+     *    @access public
+     */
+    function expectError($expected, $message) {
+        $this->_using_expect_style = true;
+        array_push($this->_expectation_queue, array($expected, $message));
+    }
 
-        /**
-         *    Adds an error to the front of the queue.
-         *    @param integer $severity       PHP error code.
-         *    @param string $content         Text of error.
-         *    @param string $filename        File error occoured in.
-         *    @param integer $line           Line number of error.
-         *    @access public
-         */
-        function add($severity, $content, $filename, $line) {
-                       $content = str_replace('%', '%%', $content);
-            if (count($this->_expectation_queue)) {
-                $this->_testLatestError($severity, $content, $filename, $line);
-            } else {
-                array_push(
-                        $this->_queue,
-                        array($severity, $content, $filename, $line));
-            }
+    /**
+     *    Adds an error to the front of the queue.
+     *    @param integer $severity       PHP error code.
+     *    @param string $content         Text of error.
+     *    @param string $filename        File error occoured in.
+     *    @param integer $line           Line number of error.
+     *    @access public
+     */
+    function add($severity, $content, $filename, $line) {
+        $content = str_replace('%', '%%', $content);
+        if ($this->_using_expect_style) {
+            $this->_testLatestError($severity, $content, $filename, $line);
+        } else {
+            array_push(
+                    $this->_queue,
+                    array($severity, $content, $filename, $line));
         }
-
-        /**
-         *    Tests the error against the most recent expected
-         *    error.
-         *    @param integer $severity       PHP error code.
-         *    @param string $content         Text of error.
-         *    @param string $filename        File error occoured in.
-         *    @param integer $line           Line number of error.
-         *    @access private
-         */
-        function _testLatestError($severity, $content, $filename, $line) {
-            list($expected, $message) = array_shift($this->_expectation_queue);
+    }
+    
+    /**
+     *    Any errors still in the queue are sent to the test
+     *    case. Any unfulfilled expectations trigger failures.
+     *    @access public
+     */
+    function tally() {
+        while (list($severity, $message, $file, $line) = $this->extract()) {
             $severity = $this->getSeverityAsString($severity);
-            $is_match = $this->_test->assert(
-                    $expected,
-                    $content,
-                    sprintf($message, "%s -> PHP error [$content] severity [$severity] in [$filename] line [$line]"));
-            if (! $is_match) {
-                $this->_test->error($severity, $content, $filename, $line);
-            }
+            $this->_test->error($severity, $message, $file, $line);
         }
-
-        /**
-         *    Pulls the earliest error from the queue.
-         *    @return     False if none, or a list of error
-         *                information. Elements are: severity
-         *                as the PHP error code, the error message,
-         *                the file with the error, the line number
-         *                and a list of PHP super global arrays.
-         *    @access public
-         */
-        function extract() {
-            if (count($this->_queue)) {
-                return array_shift($this->_queue);
-            }
-            return false;
+        while (list($expected, $message) = $this->_extractExpectation()) {
+            $this->_test->assert($expected, false, "%s -> Expected error not caught");
         }
+    }
 
-        /**
-         *    Discards the contents of the error queue.
-         *    @access public
-         */
-        function clear() {
-            $this->_queue = array();
-            $this->_expectation_queue = array();
+    /**
+     *    Tests the error against the most recent expected
+     *    error.
+     *    @param integer $severity       PHP error code.
+     *    @param string $content         Text of error.
+     *    @param string $filename        File error occoured in.
+     *    @param integer $line           Line number of error.
+     *    @access private
+     */
+    function _testLatestError($severity, $content, $filename, $line) {
+        if ($expectation = $this->_extractExpectation()) {
+            list($expected, $message) = $expectation;
+            $this->_test->assert($expected, $content, sprintf(
+                    $message,
+                    "%s -> PHP error [$content] severity [" .
+                            $this->getSeverityAsString($severity) .
+                            "] in [$filename] line [$line]"));
+        } else {
+            $this->_test->error($severity, $content, $filename, $line);
         }
+    }
 
-        /**
-         *    @deprecated
-         */
-        function assertNoErrors($message) {
-            return $this->_test->assert(
-                                       new TrueExpectation(),
-                    count($this->_queue) == 0,
-                    sprintf($message, 'Should be no errors'));
+    /**
+     *    Pulls the earliest error from the queue.
+     *    @return  mixed    False if none, or a list of error
+     *                      information. Elements are: severity
+     *                      as the PHP error code, the error message,
+     *                      the file with the error, the line number
+     *                      and a list of PHP super global arrays.
+     *    @access public
+     */
+    function extract() {
+        if (count($this->_queue)) {
+            return array_shift($this->_queue);
         }
+        return false;
+    }
 
-        /**
-         *    @deprecated
-         */
-        function assertError($expected, $message) {
-            if (count($this->_queue) == 0) {
-                $this->_test->fail(sprintf($message, 'Expected error not found'));
-                return false;
-            }
-            list($severity, $content, $file, $line) = $this->extract();
-            $severity = $this->getSeverityAsString($severity);
-            return $this->_test->assert(
-                    $expected,
-                    $content,
-                    sprintf($message, "Expected PHP error [$content] severity [$severity] in [$file] line [$line]"));
+    /**
+     *    Pulls the earliest expectation from the queue.
+     *    @return     SimpleExpectation    False if none.
+     *    @access private
+     */
+    function _extractExpectation() {
+        if (count($this->_expectation_queue)) {
+            return array_shift($this->_expectation_queue);
         }
+        return false;
+    }
 
-        /**
-         *    Sets up an expectation of an error. If this is
-         *    not fulfilled at the end of the test, a failure
-         *    will occour. If the error does happen, then this
-         *    will cancel it out and send a pass message.
-         *    @param SimpleExpectation $expected    Expected error match.
-         *    @param string $message                Message to display.
-         *    @access public
-         */
-        function expectError($expected, $message) {
-            array_push(
-                    $this->_expectation_queue,
-                    array($expected, $message));
-        }
+    /**
+     *    @deprecated
+     */
+    function assertNoErrors($message) {
+        return $this->_test->assert(
+                new TrueExpectation(),
+                count($this->_queue) == 0,
+                sprintf($message, 'Should be no errors'));
+    }
 
-        /**
-         *    Converts an error code into it's string
-         *    representation.
-         *    @param $severity  PHP integer error code.
-         *    @return           String version of error code.
-         *    @access public
-         *    @static
-         */
-        function getSeverityAsString($severity) {
-            static $map = array(
-                    E_STRICT => 'E_STRICT',
-                    E_ERROR => 'E_ERROR',
-                    E_WARNING => 'E_WARNING',
-                    E_PARSE => 'E_PARSE',
-                    E_NOTICE => 'E_NOTICE',
-                    E_CORE_ERROR => 'E_CORE_ERROR',
-                    E_CORE_WARNING => 'E_CORE_WARNING',
-                    E_COMPILE_ERROR => 'E_COMPILE_ERROR',
-                    E_COMPILE_WARNING => 'E_COMPILE_WARNING',
-                    E_USER_ERROR => 'E_USER_ERROR',
-                    E_USER_WARNING => 'E_USER_WARNING',
-                    E_USER_NOTICE => 'E_USER_NOTICE');
-            if (!isset($map[$severity])) {
-                debug_print_backtrace();
-                die();
-            }
-            return $map[$severity];
+    /**
+     *    @deprecated
+     */
+    function assertError($expected, $message) {
+        if (count($this->_queue) == 0) {
+            $this->_test->fail(sprintf($message, 'Expected error not found'));
+            return false;
         }
+        list($severity, $content, $file, $line) = $this->extract();
+        $severity = $this->getSeverityAsString($severity);
+        return $this->_test->assert(
+                $expected,
+                $content,
+                sprintf($message, "Expected PHP error [$content] severity [$severity] in [$file] line [$line]"));
     }
 
     /**
-     *    Error handler that simply stashes any errors into the global
-     *    error queue. Simulates the existing behaviour with respect to
-     *    logging errors, but this feature may be removed in future.
-     *    @param $severity        PHP error code.
-     *    @param $message         Text of error.
-     *    @param $filename        File error occoured in.
-     *    @param $line            Line number of error.
-     *    @param $super_globals   Hash of PHP super global arrays.
-     *    @static
+     *    Converts an error code into it's string
+     *    representation.
+     *    @param $severity  PHP integer error code.
+     *    @return           String version of error code.
      *    @access public
+     *    @static
      */
-    function SimpleTestErrorHandler($severity, $message, $filename, $line, $super_globals) {
-        if ($severity = $severity & error_reporting()) {
-            restore_error_handler();
-            if (ini_get('log_errors')) {
-                $label = SimpleErrorQueue::getSeverityAsString($severity);
-                error_log("$label: $message in $filename on line $line");
-            }
-            $context = &SimpleTest::getContext();
-            $queue = &$context->get('SimpleErrorQueue');
-            $queue->add($severity, $message, $filename, $line);
-            set_error_handler('SimpleTestErrorHandler');
+    function getSeverityAsString($severity) {
+        static $map = array(
+                E_STRICT => 'E_STRICT',
+                E_ERROR => 'E_ERROR',
+                E_WARNING => 'E_WARNING',
+                E_PARSE => 'E_PARSE',
+                E_NOTICE => 'E_NOTICE',
+                E_CORE_ERROR => 'E_CORE_ERROR',
+                E_CORE_WARNING => 'E_CORE_WARNING',
+                E_COMPILE_ERROR => 'E_COMPILE_ERROR',
+                E_COMPILE_WARNING => 'E_COMPILE_WARNING',
+                E_USER_ERROR => 'E_USER_ERROR',
+                E_USER_WARNING => 'E_USER_WARNING',
+                E_USER_NOTICE => 'E_USER_NOTICE');
+        if (defined('E_RECOVERABLE_ERROR')) {
+            $map[E_RECOVERABLE_ERROR] = 'E_RECOVERABLE_ERROR';
+        }
+        if (defined('E_DEPRECATED')) {
+            $map[E_DEPRECATED] = 'E_DEPRECATED';
+        }
+        return $map[$severity];
+    }
+}
+
+/**
+ *    Error handler that simply stashes any errors into the global
+ *    error queue. Simulates the existing behaviour with respect to
+ *    logging errors, but this feature may be removed in future.
+ *    @param $severity        PHP error code.
+ *    @param $message         Text of error.
+ *    @param $filename        File error occoured in.
+ *    @param $line            Line number of error.
+ *    @param $super_globals   Hash of PHP super global arrays.
+ *    @static
+ *    @access public
+ */
+function SimpleTestErrorHandler($severity, $message, $filename = null, $line = null, $super_globals = null, $mask = null) {
+    $severity = $severity & error_reporting();
+    if ($severity) {
+        restore_error_handler();
+        if (ini_get('log_errors')) {
+            $label = SimpleErrorQueue::getSeverityAsString($severity);
+            error_log("$label: $message in $filename on line $line");
         }
+        $context = &SimpleTest::getContext();
+        $queue = &$context->get('SimpleErrorQueue');
+        $queue->add($severity, $message, $filename, $line);
+        set_error_handler('SimpleTestErrorHandler');
     }
-?>
+    return true;
+}
+?>
\ No newline at end of file
index a2bf0afff07f7dfb36d2bcea6140ce6ce1ed5464..70a243a3cd5a78a456a8c3d98b4b2b5f9552665f 100644 (file)
 <?php
-    /**
    * base include file for SimpleTest
    * @package        SimpleTest
    * @subpackage     UnitTester
    * @version        $Id$
    */
+/**
 base include file for SimpleTest
*  @package    SimpleTest
*  @subpackage UnitTester
*  @version    $Id$
+ */
 
-    /**#@+
-     * Includes SimpleTest files and defined the root constant
-     * for dependent libraries.
-     */
-    require_once(dirname(__FILE__) . '/invoker.php');
-    require_once(dirname(__FILE__) . '/expectation.php');
+/**#@+
+ * Include required SimpleTest files 
+ */
+require_once dirname(__FILE__) . '/invoker.php';
+require_once dirname(__FILE__) . '/expectation.php';
+/**#@-*/
+
+/**
+ *    Extension that traps exceptions and turns them into
+ *    an error message. PHP5 only.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class SimpleExceptionTrappingInvoker extends SimpleInvokerDecorator {
 
     /**
-     *    Extension that traps exceptions and turns them into
-     *    an error message. PHP5 only.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Stores the invoker to be wrapped.
+     *    @param SimpleInvoker $invoker   Test method runner.
      */
-    class SimpleExceptionTrappingInvoker extends SimpleInvokerDecorator {
-
-        /**
-         *    Stores the invoker to be wrapped.
-         *    @param SimpleInvoker $invoker   Test method runner.
-         */
-        function SimpleExceptionTrappingInvoker($invoker) {
-            $this->SimpleInvokerDecorator($invoker);
-        }
+    function SimpleExceptionTrappingInvoker($invoker) {
+        $this->SimpleInvokerDecorator($invoker);
+    }
 
-        /**
-         *    Invokes a test method whilst trapping expected
-         *    exceptions. Any left over unthrown exceptions
-         *    are then reported as failures.
-         *    @param string $method    Test method to call.
-         */
-        function invoke($method) {
-            $trap = SimpleTest::getContext()->get('SimpleExceptionTrap');
+    /**
+     *    Invokes a test method whilst trapping expected
+     *    exceptions. Any left over unthrown exceptions
+     *    are then reported as failures.
+     *    @param string $method    Test method to call.
+     */
+    function invoke($method) {
+        $trap = SimpleTest::getContext()->get('SimpleExceptionTrap');
+        $trap->clear();
+        try {
+            $has_thrown = false;
+            parent::invoke($method);
+        } catch (Exception $exception) {
+            $has_thrown = true;
+            if (! $trap->isExpected($this->getTestCase(), $exception)) {
+                $this->getTestCase()->exception($exception);
+            }
             $trap->clear();
+        }
+        if ($message = $trap->getOutstanding()) {
+            $this->getTestCase()->fail($message);
+        }
+        if ($has_thrown) {
             try {
-                parent::invoke($method);
-            } catch (Exception $exception) {
-                   if (! $trap->isExpected($this->getTestCase(), $exception)) {
-                       $this->getTestCase()->exception($exception);
-                   }
-                   $trap->clear();
-            }
-                       if ($message = $trap->getOutstanding()) {
-                               $this->getTestCase()->fail($message);
-                       }
+                parent::getTestCase()->tearDown();
+            } catch (Exception $e) { }
         }
     }
+}
+
+/**
+ *    Tests exceptions either by type or the exact
+ *    exception. This could be improved to accept
+ *    a pattern expectation to test the error
+ *    message, but that will have to come later.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class ExceptionExpectation extends SimpleExpectation {
+    private $expected;
 
     /**
-     *    Tests exceptions either by type or the exact
-     *    exception. This could be improved to accept
-     *    a pattern expectation to test the error
-     *    message, but that will have to come later.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Sets up the conditions to test against.
+     *    If the expected value is a string, then
+     *    it will act as a test of the class name.
+     *    An exception as the comparison will
+     *    trigger an identical match. Writing this
+     *    down now makes it look doubly dumb. I hope
+     *    come up with a better scheme later.
+     *    @param mixed $expected   A class name or an actual
+     *                             exception to compare with.
+     *    @param string $message   Message to display.
      */
-    class ExceptionExpectation extends SimpleExpectation {
-        private $expected;
+    function __construct($expected, $message = '%s') {
+        $this->expected = $expected;
+        parent::__construct($message);
+    }
 
-        /**
-         *    Sets up the conditions to test against.
-         *    If the expected value is a string, then
-         *    it will act as a test of the class name.
-         *    An exception as the comparison will
-         *    trigger an identical match. Writing this
-         *    down now makes it look doubly dumb. I hope
-         *    come up with a better scheme later.
-         *    @param mixed $expected   A class name or an actual
-         *                             exception to compare with.
-         *    @param string $message   Message to display.
-         */
-        function __construct($expected, $message = '%s') {
-            $this->expected = $expected;
-            parent::__construct($message);
+    /**
+     *    Carry out the test.
+     *    @param Exception $compare    Value to check.
+     *    @return boolean              True if matched.
+     */
+    function test($compare) {
+        if (is_string($this->expected)) {
+            return ($compare instanceof $this->expected);
         }
-
-        /**
-         *    Carry out the test.
-         *    @param Exception $compare    Value to check.
-         *    @return boolean              True if matched.
-         */
-        function test($compare) {
-            if (is_string($this->expected)) {
-                return ($compare instanceof $this->expected);
-            }
-            if (get_class($compare) != get_class($this->expected)) {
-                return false;
-            }
-            return $compare->getMessage() == $this->expected->getMessage();
+        if (get_class($compare) != get_class($this->expected)) {
+            return false;
         }
+        return $compare->getMessage() == $this->expected->getMessage();
+    }
 
-        /**
-         *    Create the message to display describing the test.
-         *    @param Exception $compare     Exception to match.
-         *    @return string                Final message.
-         */
-        function testMessage($compare) {
-            if (is_string($this->expected)) {
-                return "Exception [" . $this->describeException($compare) .
-                        "] should be type [" . $this->expected . "]";
-            }
+    /**
+     *    Create the message to display describing the test.
+     *    @param Exception $compare     Exception to match.
+     *    @return string                Final message.
+     */
+    function testMessage($compare) {
+        if (is_string($this->expected)) {
             return "Exception [" . $this->describeException($compare) .
-                    "] should match [" .
-                    $this->describeException($this->expected) . "]";
+                    "] should be type [" . $this->expected . "]";
         }
+        return "Exception [" . $this->describeException($compare) .
+                "] should match [" .
+                $this->describeException($this->expected) . "]";
+    }
 
-        /**
-         *    Summary of an Exception object.
-         *    @param Exception $compare     Exception to describe.
-         *    @return string                Text description.
-         */
-        protected function describeException($exception) {
-            return get_class($exception) . ": " . $exception->getMessage();
-        }
+    /**
+     *    Summary of an Exception object.
+     *    @param Exception $compare     Exception to describe.
+     *    @return string                Text description.
+     */
+    protected function describeException($exception) {
+        return get_class($exception) . ": " . $exception->getMessage();
     }
+}
+
+/**
+ *    Stores expected exceptions for when they
+ *    get thrown. Saves the irritating try...catch
+ *    block.
+ *    @package  SimpleTest
+ *    @subpackage   UnitTester
+ */
+class SimpleExceptionTrap {
+    private $expected;
+    private $message;
 
     /**
-     *    Stores expected exceptions for when they
-     *    get thrown. Saves the irritating try...catch
-     *    block.
-        *        @package      SimpleTest
-        *        @subpackage   UnitTester
+     *    Clears down the queue ready for action.
      */
-    class SimpleExceptionTrap {
-        private $expected;
-        private $message;
+    function __construct() {
+        $this->clear();
+    }
 
-        /**
-         *    Clears down the queue ready for action.
-         */
-        function __construct() {
-            $this->clear();
+    /**
+     *    Sets up an expectation of an exception.
+     *    This has the effect of intercepting an
+     *    exception that matches.
+     *    @param SimpleExpectation $expected    Expected exception to match.
+     *    @param string $message                Message to display.
+     *    @access public
+     */
+    function expectException($expected = false, $message = '%s') {
+        if ($expected === false) {
+            $expected = new AnythingExpectation();
         }
-
-        /**
-         *    Sets up an expectation of an exception.
-         *    This has the effect of intercepting an
-         *    exception that matches.
-         *    @param SimpleExpectation $expected    Expected exception to match.
-         *    @param string $message                Message to display.
-         *    @access public
-         */
-        function expectException($expected = false, $message = '%s') {
-            if ($expected === false) {
-                $expected = new AnythingExpectation();
-            }
-            if (! SimpleExpectation::isExpectation($expected)) {
-                $expected = new ExceptionExpectation($expected);
-            }
-            $this->expected = $expected;
-            $this->message = $message;
+        if (! SimpleExpectation::isExpectation($expected)) {
+            $expected = new ExceptionExpectation($expected);
         }
+        $this->expected = $expected;
+        $this->message = $message;
+    }
 
-        /**
-         *    Compares the expected exception with any
-         *    in the queue. Issues a pass or fail and
-         *    returns the state of the test.
-         *    @param SimpleTestCase $test    Test case to send messages to.
-         *    @param Exception $exception    Exception to compare.
-         *    @return boolean                False on no match.
-         */
-        function isExpected($test, $exception) {
-            if ($this->expected) {
-                   return $test->assert($this->expected, $exception, $this->message);
-            }
-                       return false;
+    /**
+     *    Compares the expected exception with any
+     *    in the queue. Issues a pass or fail and
+     *    returns the state of the test.
+     *    @param SimpleTestCase $test    Test case to send messages to.
+     *    @param Exception $exception    Exception to compare.
+     *    @return boolean                False on no match.
+     */
+    function isExpected($test, $exception) {
+        if ($this->expected) {
+            return $test->assert($this->expected, $exception, $this->message);
         }
+        return false;
+    }
 
-               /**
-                *    Tests for any left over exception.
-                *    @return string/false     The failure message or false if none.
-                */
-               function getOutstanding() {
-                       return sprintf($this->message, 'Failed to trap exception');
-               }
+    /**
+     *    Tests for any left over exception.
+     *    @return string/false     The failure message or false if none.
+     */
+    function getOutstanding() {
+        return sprintf($this->message, 'Failed to trap exception');
+    }
 
-        /**
-         *    Discards the contents of the error queue.
-         */
-        function clear() {
-            $this->expected = false;
-            $this->message = false;
-        }
+    /**
+     *    Discards the contents of the error queue.
+     */
+    function clear() {
+        $this->expected = false;
+        $this->message = false;
     }
+}
 ?>
\ No newline at end of file
index a4e9de25f7a892dc36051a18d2690f0b47086779..11bc95b148f7b6fff823592efa96afe8ea3d5bda 100644 (file)
 <?php
+/**
+ *    base include file for SimpleTest
+ *    @package    SimpleTest
+ *    @subpackage    UnitTester
+ *    @version    $Id$
+ */
+
+/**#@+
+ *    include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/dumper.php');
+require_once(dirname(__FILE__) . '/compatibility.php');
+/**#@-*/
+
+/**
+ *    Assertion that can display failure information.
+ *    Also includes various helper methods.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ *    @abstract
+ */
+class SimpleExpectation {
+    var $_dumper = false;
+    var $_message;
+
     /**
-     *    base include file for SimpleTest
-     *    @package    SimpleTest
-     *    @subpackage    UnitTester
-     *    @version    $Id$
+     *    Creates a dumper for displaying values and sets
+     *    the test message.
+     *    @param string $message    Customised message on failure.
      */
-     
-    /**#@+
-     *    include other SimpleTest class files
+    function SimpleExpectation($message = '%s') {
+        $this->_message = $message;
+    }
+
+    /**
+     *    Tests the expectation. True if correct.
+     *    @param mixed $compare        Comparison value.
+     *    @return boolean              True if correct.
+     *    @access public
+     *    @abstract
      */
-    require_once(dirname(__FILE__) . '/dumper.php');
-    require_once(dirname(__FILE__) . '/compatibility.php');
-    /**#@-*/
-    
+    function test($compare) {
+    }
+
     /**
-     *    Assertion that can display failure information.
-     *    Also includes various helper methods.
-     *    @package SimpleTest
-     *    @subpackage UnitTester
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
      *    @abstract
      */
-    class SimpleExpectation {
-        var $_dumper;
-        var $_message;
-        
-        /**
-         *    Creates a dumper for displaying values and sets
-         *    the test message.
-         *    @param string $message    Customised message on failure.
-         */
-        function SimpleExpectation($message = '%s') {
-            $this->_message = $message;
-        }
-        
-        /**
-         *    Tests the expectation. True if correct.
-         *    @param mixed $compare        Comparison value.
-         *    @return boolean              True if correct.
-         *    @access public
-         *    @abstract
-         */
-        function test($compare) {
-        }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         *    @abstract
-         */
-        function testMessage($compare) {
-        }
-        
-        /**
-         *    Overlays the generated message onto the stored user
-         *    message. An additional message can be interjected.
-         *    @param mixed $compare        Comparison value.
-         *    @param SimpleDumper $dumper  For formatting the results.
-         *    @return string               Description of success
-         *                                 or failure.
-         *    @access public
-         */
-        function overlayMessage($compare, $dumper) {
-            $this->_dumper = $dumper;
-            return sprintf($this->_message, $this->testMessage($compare));
-        }
-        
-        /**
-         *    Accessor for the dumper.
-         *    @return SimpleDumper    Current value dumper.
-         *    @access protected
-         */
-        function &_getDumper() {
-            return $this->_dumper;
-        }
-        
-        /**
-         *    Test to see if a value is an expectation object.
-         *    A useful utility method.
-         *    @param mixed $expectation    Hopefully an Epectation
-         *                                 class.
-         *    @return boolean              True if descended from
-         *                                 this class.
-         *    @access public
-         *    @static
-         */
-        function isExpectation($expectation) {
-            return is_object($expectation) &&
-                    SimpleTestCompatibility::isA($expectation, 'SimpleExpectation');
-        }
+    function testMessage($compare) {
     }
 
     /**
-     *    A wildcard expectation always matches.
-     *    @package SimpleTest
-     *    @subpackage MockObjects
+     *    Overlays the generated message onto the stored user
+     *    message. An additional message can be interjected.
+     *    @param mixed $compare        Comparison value.
+     *    @param SimpleDumper $dumper  For formatting the results.
+     *    @return string               Description of success
+     *                                 or failure.
+     *    @access public
      */
-    class AnythingExpectation extends SimpleExpectation {
+    function overlayMessage($compare, $dumper) {
+        $this->_dumper = $dumper;
+        return sprintf($this->_message, $this->testMessage($compare));
+    }
 
-        /**
-         *    Tests the expectation. Always true.
-         *    @param mixed $compare  Ignored.
-         *    @return boolean        True.
-         *    @access public
-         */
-        function test($compare) {
-            return true;
+    /**
+     *    Accessor for the dumper.
+     *    @return SimpleDumper    Current value dumper.
+     *    @access protected
+     */
+    function &_getDumper() {
+        if (! $this->_dumper) {
+            $dumper = &new SimpleDumper();
+            return $dumper;
         }
+        return $this->_dumper;
+    }
 
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            $dumper = &$this->_getDumper();
-            return 'Anything always matches [' . $dumper->describeValue($compare) . ']';
-        }
+    /**
+     *    Test to see if a value is an expectation object.
+     *    A useful utility method.
+     *    @param mixed $expectation    Hopefully an Epectation
+     *                                 class.
+     *    @return boolean              True if descended from
+     *                                 this class.
+     *    @access public
+     *    @static
+     */
+    function isExpectation($expectation) {
+        return is_object($expectation) &&
+                SimpleTestCompatibility::isA($expectation, 'SimpleExpectation');
     }
+}
+
+/**
+ *    A wildcard expectation always matches.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class AnythingExpectation extends SimpleExpectation {
 
     /**
-     *    An expectation that passes on boolean true.
-     *    @package SimpleTest
-     *    @subpackage MockObjects
+     *    Tests the expectation. Always true.
+     *    @param mixed $compare  Ignored.
+     *    @return boolean        True.
+     *    @access public
      */
-    class TrueExpectation extends SimpleExpectation {
+    function test($compare) {
+        return true;
+    }
 
-        /**
-         *    Tests the expectation.
-         *    @param mixed $compare  Should be true.
-         *    @return boolean        True on match.
-         *    @access public
-         */
-        function test($compare) {
-            return (boolean)$compare;
-        }
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        $dumper = &$this->_getDumper();
+        return 'Anything always matches [' . $dumper->describeValue($compare) . ']';
+    }
+}
 
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            $dumper = &$this->_getDumper();
-            return 'Expected true, got [' . $dumper->describeValue($compare) . ']';
-        }
+/**
+ *    An expectation that never matches.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class FailedExpectation extends SimpleExpectation {
+
+    /**
+     *    Tests the expectation. Always false.
+     *    @param mixed $compare  Ignored.
+     *    @return boolean        True.
+     *    @access public
+     */
+    function test($compare) {
+        return false;
     }
-    
+
     /**
-     *    An expectation that passes on boolean false.
-     *    @package SimpleTest
-     *    @subpackage MockObjects
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of failure.
+     *    @access public
      */
-    class FalseExpectation extends SimpleExpectation {
+    function testMessage($compare) {
+        $dumper = &$this->_getDumper();
+        return 'Failed expectation never matches [' . $dumper->describeValue($compare) . ']';
+    }
+}
 
-        /**
-         *    Tests the expectation.
-         *    @param mixed $compare  Should be false.
-         *    @return boolean        True on match.
-         *    @access public
-         */
-        function test($compare) {
-            return ! (boolean)$compare;
-        }
+/**
+ *    An expectation that passes on boolean true.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class TrueExpectation extends SimpleExpectation {
 
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            $dumper = &$this->_getDumper();
-            return 'Expected false, got [' . $dumper->describeValue($compare) . ']';
-        }
+    /**
+     *    Tests the expectation.
+     *    @param mixed $compare  Should be true.
+     *    @return boolean        True on match.
+     *    @access public
+     */
+    function test($compare) {
+        return (boolean)$compare;
     }
-    
-    /**
-     *    Test for equality.
-     *      @package SimpleTest
-     *      @subpackage UnitTester
-     */
-    class EqualExpectation extends SimpleExpectation {
-        var $_value;
-        
-        /**
-         *    Sets the value to compare against.
-         *    @param mixed $value        Test value to match.
-         *    @param string $message     Customised message on failure.
-         *    @access public
-         */
-        function EqualExpectation($value, $message = '%s') {
-            $this->SimpleExpectation($message);
-            $this->_value = $value;
-        }
-        
-        /**
-         *    Tests the expectation. True if it matches the
-         *    held value.
-         *    @param mixed $compare        Comparison value.
-         *    @return boolean              True if correct.
-         *    @access public
-         */
-        function test($compare) {
-            return (($this->_value == $compare) && ($compare == $this->_value));
-        }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            if ($this->test($compare)) {
-                return "Equal expectation [" . $this->_dumper->describeValue($this->_value) . "]";
-            } else {
-                return "Equal expectation fails " .
-                        $this->_dumper->describeDifference($this->_value, $compare);
-            }
-        }
 
-        /**
-         *    Accessor for comparison value.
-         *    @return mixed       Held value to compare with.
-         *    @access protected
-         */
-        function _getValue() {
-            return $this->_value;
-        }
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        $dumper = &$this->_getDumper();
+        return 'Expected true, got [' . $dumper->describeValue($compare) . ']';
     }
-    
-    /**
-     *    Test for inequality.
-     *      @package SimpleTest
-     *      @subpackage UnitTester
-     */
-    class NotEqualExpectation extends EqualExpectation {
-        
-        /**
-         *    Sets the value to compare against.
-         *    @param mixed $value       Test value to match.
-         *    @param string $message    Customised message on failure.
-         *    @access public
-         */
-        function NotEqualExpectation($value, $message = '%s') {
-            $this->EqualExpectation($value, $message);
-        }
-        
-        /**
-         *    Tests the expectation. True if it differs from the
-         *    held value.
-         *    @param mixed $compare        Comparison value.
-         *    @return boolean              True if correct.
-         *    @access public
-         */
-        function test($compare) {
-            return ! parent::test($compare);
-        }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            $dumper = &$this->_getDumper();
-            if ($this->test($compare)) {
-                return "Not equal expectation passes " .
-                        $dumper->describeDifference($this->_getValue(), $compare);
-            } else {
-                return "Not equal expectation fails [" .
-                        $dumper->describeValue($this->_getValue()) .
-                        "] matches";
-            }
-        }
+}
+
+/**
+ *    An expectation that passes on boolean false.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class FalseExpectation extends SimpleExpectation {
+
+    /**
+     *    Tests the expectation.
+     *    @param mixed $compare  Should be false.
+     *    @return boolean        True on match.
+     *    @access public
+     */
+    function test($compare) {
+        return ! (boolean)$compare;
     }
-    
-    /**
-     *    Test for being within a range.
-     *      @package SimpleTest
-     *      @subpackage UnitTester
-     */
-    class WithinMarginExpectation extends SimpleExpectation {
-        var $_upper;
-        var $_lower;
-        
-        /**
-         *    Sets the value to compare against and the fuzziness of
-         *    the match. Used for comparing floating point values.
-         *    @param mixed $value        Test value to match.
-         *    @param mixed $margin       Fuzziness of match.
-         *    @param string $message     Customised message on failure.
-         *    @access public
-         */
-        function WithinMarginExpectation($value, $margin, $message = '%s') {
-            $this->SimpleExpectation($message);
-            $this->_upper = $value + $margin;
-            $this->_lower = $value - $margin;
-        }
-        
-        /**
-         *    Tests the expectation. True if it matches the
-         *    held value.
-         *    @param mixed $compare        Comparison value.
-         *    @return boolean              True if correct.
-         *    @access public
-         */
-        function test($compare) {
-            return (($compare <= $this->_upper) && ($compare >= $this->_lower));
-        }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            if ($this->test($compare)) {
-                return $this->_withinMessage($compare);
-            } else {
-                return $this->_outsideMessage($compare);
-            }
-        }
-        
-        /**
-         *    Creates a the message for being within the range.
-         *    @param mixed $compare        Value being tested.
-         *    @access private
-         */
-        function _withinMessage($compare) {
-            return "Within expectation [" . $this->_dumper->describeValue($this->_lower) . "] and [" .
-                    $this->_dumper->describeValue($this->_upper) . "]";
-        }
-        
-        /**
-         *    Creates a the message for being within the range.
-         *    @param mixed $compare        Value being tested.
-         *    @access private
-         */
-        function _outsideMessage($compare) {
-            if ($compare > $this->_upper) {
-                return "Outside expectation " .
-                        $this->_dumper->describeDifference($compare, $this->_upper);
-            } else {
-                return "Outside expectation " .
-                        $this->_dumper->describeDifference($compare, $this->_lower);
-            }
-        }
+
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        $dumper = &$this->_getDumper();
+        return 'Expected false, got [' . $dumper->describeValue($compare) . ']';
     }
-    
-    /**
-     *    Test for being outside of a range.
-     *      @package SimpleTest
-     *      @subpackage UnitTester
-     */
-    class OutsideMarginExpectation extends WithinMarginExpectation {
-        
-        /**
-         *    Sets the value to compare against and the fuzziness of
-         *    the match. Used for comparing floating point values.
-         *    @param mixed $value        Test value to not match.
-         *    @param mixed $margin       Fuzziness of match.
-         *    @param string $message     Customised message on failure.
-         *    @access public
-         */
-        function OutsideMarginExpectation($value, $margin, $message = '%s') {
-            $this->WithinMarginExpectation($value, $margin, $message);
-        }
-        
-        /**
-         *    Tests the expectation. True if it matches the
-         *    held value.
-         *    @param mixed $compare        Comparison value.
-         *    @return boolean              True if correct.
-         *    @access public
-         */
-        function test($compare) {
-            return ! parent::test($compare);
-        }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            if (! $this->test($compare)) {
-                return $this->_withinMessage($compare);
-            } else {
-                return $this->_outsideMessage($compare);
-            }
-        }
+}
+
+/**
+ *    Test for equality.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class EqualExpectation extends SimpleExpectation {
+    var $_value;
+
+    /**
+     *    Sets the value to compare against.
+     *    @param mixed $value        Test value to match.
+     *    @param string $message     Customised message on failure.
+     *    @access public
+     */
+    function EqualExpectation($value, $message = '%s') {
+        $this->SimpleExpectation($message);
+        $this->_value = $value;
     }
-    
-    /**
-     *    Test for identity.
-     *    @package SimpleTest
-     *    @subpackage UnitTester
-     */
-    class IdenticalExpectation extends EqualExpectation {
-        
-        /**
-         *    Sets the value to compare against.
-         *    @param mixed $value       Test value to match.
-         *    @param string $message    Customised message on failure.
-         *    @access public
-         */
-        function IdenticalExpectation($value, $message = '%s') {
-            $this->EqualExpectation($value, $message);
-        }
-        
-        /**
-         *    Tests the expectation. True if it exactly
-         *    matches the held value.
-         *    @param mixed $compare        Comparison value.
-         *    @return boolean              True if correct.
-         *    @access public
-         */
-        function test($compare) {
-            return SimpleTestCompatibility::isIdentical($this->_getValue(), $compare);
-        }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            $dumper = &$this->_getDumper();
-            if ($this->test($compare)) {
-                return "Identical expectation [" . $dumper->describeValue($this->_getValue()) . "]";
-            } else {
-                return "Identical expectation [" . $dumper->describeValue($this->_getValue()) .
-                        "] fails with [" .
-                        $dumper->describeValue($compare) . "] " .
-                        $dumper->describeDifference($this->_getValue(), $compare, TYPE_MATTERS);
-            }
-        }
+
+    /**
+     *    Tests the expectation. True if it matches the
+     *    held value.
+     *    @param mixed $compare        Comparison value.
+     *    @return boolean              True if correct.
+     *    @access public
+     */
+    function test($compare) {
+        return (($this->_value == $compare) && ($compare == $this->_value));
     }
-    
-    /**
-     *    Test for non-identity.
-     *    @package SimpleTest
-     *    @subpackage UnitTester
-     */
-    class NotIdenticalExpectation extends IdenticalExpectation {
-        
-        /**
-         *    Sets the value to compare against.
-         *    @param mixed $value        Test value to match.
-         *    @param string $message     Customised message on failure.
-         *    @access public
-         */
-        function NotIdenticalExpectation($value, $message = '%s') {
-            $this->IdenticalExpectation($value, $message);
-        }
-        
-        /**
-         *    Tests the expectation. True if it differs from the
-         *    held value.
-         *    @param mixed $compare        Comparison value.
-         *    @return boolean              True if correct.
-         *    @access public
-         */
-        function test($compare) {
-            return ! parent::test($compare);
-        }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            $dumper = &$this->_getDumper();
-            if ($this->test($compare)) {
-                return "Not identical expectation passes " .
-                        $dumper->describeDifference($this->_getValue(), $compare, TYPE_MATTERS);
-            } else {
-                return "Not identical expectation [" . $dumper->describeValue($this->_getValue()) . "] matches";
-            }
+
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        if ($this->test($compare)) {
+            return "Equal expectation [" . $this->_dumper->describeValue($this->_value) . "]";
+        } else {
+            return "Equal expectation fails " .
+                    $this->_dumper->describeDifference($this->_value, $compare);
         }
     }
-    
-    /**
-     *    Test for a pattern using Perl regex rules.
-     *    @package SimpleTest
-     *    @subpackage UnitTester
-     */
-    class PatternExpectation extends SimpleExpectation {
-        var $_pattern;
-        
-        /**
-         *    Sets the value to compare against.
-         *    @param string $pattern    Pattern to search for.
-         *    @param string $message    Customised message on failure.
-         *    @access public
-         */
-        function PatternExpectation($pattern, $message = '%s') {
-            $this->SimpleExpectation($message);
-            $this->_pattern = $pattern;
-        }
-        
-        /**
-         *    Accessor for the pattern.
-         *    @return string       Perl regex as string.
-         *    @access protected
-         */
-        function _getPattern() {
-            return $this->_pattern;
-        }
-        
-        /**
-         *    Tests the expectation. True if the Perl regex
-         *    matches the comparison value.
-         *    @param string $compare        Comparison value.
-         *    @return boolean               True if correct.
-         *    @access public
-         */
-        function test($compare) {
-            return (boolean)preg_match($this->_getPattern(), $compare);
-        }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            if ($this->test($compare)) {
-                return $this->_describePatternMatch($this->_getPattern(), $compare);
-            } else {
-                $dumper = &$this->_getDumper();
-                return "Pattern [" . $this->_getPattern() .
-                        "] not detected in [" .
-                        $dumper->describeValue($compare) . "]";
-            }
-        }
-        
-        /**
-         *    Describes a pattern match including the string
-         *    found and it's position.
-         *    @package SimpleTest
-         *    @subpackage UnitTester
-         *    @param string $pattern        Regex to match against.
-         *    @param string $subject        Subject to search.
-         *    @access protected
-         */
-        function _describePatternMatch($pattern, $subject) {
-            preg_match($pattern, $subject, $matches);
-            $position = strpos($subject, $matches[0]);
-            $dumper = $this->_getDumper();
-            return "Pattern [$pattern] detected at character [$position] in [" .
-                    $dumper->describeValue($subject) . "] as [" .
-                    $matches[0] . "] in region [" .
-                    $dumper->clipString($subject, 100, $position) . "]";
-        }
+
+    /**
+     *    Accessor for comparison value.
+     *    @return mixed       Held value to compare with.
+     *    @access protected
+     */
+    function _getValue() {
+        return $this->_value;
+    }
+}
+
+/**
+ *    Test for inequality.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class NotEqualExpectation extends EqualExpectation {
+
+    /**
+     *    Sets the value to compare against.
+     *    @param mixed $value       Test value to match.
+     *    @param string $message    Customised message on failure.
+     *    @access public
+     */
+    function NotEqualExpectation($value, $message = '%s') {
+        $this->EqualExpectation($value, $message);
     }
-    
+
     /**
-     *      @deprecated
+     *    Tests the expectation. True if it differs from the
+     *    held value.
+     *    @param mixed $compare        Comparison value.
+     *    @return boolean              True if correct.
+     *    @access public
      */
-    class WantedPatternExpectation extends PatternExpectation {
+    function test($compare) {
+        return ! parent::test($compare);
     }
-    
+
     /**
-     *    Fail if a pattern is detected within the
-     *    comparison.
-     *      @package SimpleTest
-     *      @subpackage UnitTester
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
      */
-    class NoPatternExpectation extends PatternExpectation {
-        
-        /**
-         *    Sets the reject pattern
-         *    @param string $pattern    Pattern to search for.
-         *    @param string $message    Customised message on failure.
-         *    @access public
-         */
-        function NoPatternExpectation($pattern, $message = '%s') {
-            $this->PatternExpectation($pattern, $message);
+    function testMessage($compare) {
+        $dumper = &$this->_getDumper();
+        if ($this->test($compare)) {
+            return "Not equal expectation passes " .
+                    $dumper->describeDifference($this->_getValue(), $compare);
+        } else {
+            return "Not equal expectation fails [" .
+                    $dumper->describeValue($this->_getValue()) .
+                    "] matches";
         }
-        
-        /**
-         *    Tests the expectation. False if the Perl regex
-         *    matches the comparison value.
-         *    @param string $compare        Comparison value.
-         *    @return boolean               True if correct.
-         *    @access public
-         */
-        function test($compare) {
-            return ! parent::test($compare);
+    }
+}
+
+/**
+ *    Test for being within a range.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class WithinMarginExpectation extends SimpleExpectation {
+    var $_upper;
+    var $_lower;
+
+    /**
+     *    Sets the value to compare against and the fuzziness of
+     *    the match. Used for comparing floating point values.
+     *    @param mixed $value        Test value to match.
+     *    @param mixed $margin       Fuzziness of match.
+     *    @param string $message     Customised message on failure.
+     *    @access public
+     */
+    function WithinMarginExpectation($value, $margin, $message = '%s') {
+        $this->SimpleExpectation($message);
+        $this->_upper = $value + $margin;
+        $this->_lower = $value - $margin;
+    }
+
+    /**
+     *    Tests the expectation. True if it matches the
+     *    held value.
+     *    @param mixed $compare        Comparison value.
+     *    @return boolean              True if correct.
+     *    @access public
+     */
+    function test($compare) {
+        return (($compare <= $this->_upper) && ($compare >= $this->_lower));
+    }
+
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        if ($this->test($compare)) {
+            return $this->_withinMessage($compare);
+        } else {
+            return $this->_outsideMessage($compare);
         }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param string $compare      Comparison value.
-         *    @return string              Description of success
-         *                                or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            if ($this->test($compare)) {
-                $dumper = &$this->_getDumper();
-                return "Pattern [" . $this->_getPattern() .
-                        "] not detected in [" .
-                        $dumper->describeValue($compare) . "]";
-            } else {
-                return $this->_describePatternMatch($this->_getPattern(), $compare);
-            }
+    }
+
+    /**
+     *    Creates a the message for being within the range.
+     *    @param mixed $compare        Value being tested.
+     *    @access private
+     */
+    function _withinMessage($compare) {
+        return "Within expectation [" . $this->_dumper->describeValue($this->_lower) . "] and [" .
+                $this->_dumper->describeValue($this->_upper) . "]";
+    }
+
+    /**
+     *    Creates a the message for being within the range.
+     *    @param mixed $compare        Value being tested.
+     *    @access private
+     */
+    function _outsideMessage($compare) {
+        if ($compare > $this->_upper) {
+            return "Outside expectation " .
+                    $this->_dumper->describeDifference($compare, $this->_upper);
+        } else {
+            return "Outside expectation " .
+                    $this->_dumper->describeDifference($compare, $this->_lower);
         }
     }
-    
+}
+
+/**
+ *    Test for being outside of a range.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class OutsideMarginExpectation extends WithinMarginExpectation {
+
+    /**
+     *    Sets the value to compare against and the fuzziness of
+     *    the match. Used for comparing floating point values.
+     *    @param mixed $value        Test value to not match.
+     *    @param mixed $margin       Fuzziness of match.
+     *    @param string $message     Customised message on failure.
+     *    @access public
+     */
+    function OutsideMarginExpectation($value, $margin, $message = '%s') {
+        $this->WithinMarginExpectation($value, $margin, $message);
+    }
+
     /**
-     *    @package SimpleTest
-     *    @subpackage UnitTester
-     *      @deprecated
+     *    Tests the expectation. True if it matches the
+     *    held value.
+     *    @param mixed $compare        Comparison value.
+     *    @return boolean              True if correct.
+     *    @access public
      */
-    class UnwantedPatternExpectation extends NoPatternExpectation {
+    function test($compare) {
+        return ! parent::test($compare);
     }
-    
+
     /**
-     *    Tests either type or class name if it's an object.
-     *      @package SimpleTest
-     *      @subpackage UnitTester
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
      */
-    class IsAExpectation extends SimpleExpectation {
-        var $_type;
-        
-        /**
-         *    Sets the type to compare with.
-         *    @param string $type       Type or class name.
-         *    @param string $message    Customised message on failure.
-         *    @access public
-         */
-        function IsAExpectation($type, $message = '%s') {
-            $this->SimpleExpectation($message);
-            $this->_type = $type;
+    function testMessage($compare) {
+        if (! $this->test($compare)) {
+            return $this->_withinMessage($compare);
+        } else {
+            return $this->_outsideMessage($compare);
         }
-        
-        /**
-         *    Accessor for type to check against.
-         *    @return string    Type or class name.
-         *    @access protected
-         */
-        function _getType() {
-            return $this->_type;
+    }
+}
+
+/**
+ *    Test for reference.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class ReferenceExpectation extends SimpleExpectation {
+    var $_value;
+
+    /**
+     *    Sets the reference value to compare against.
+     *    @param mixed $value       Test reference to match.
+     *    @param string $message    Customised message on failure.
+     *    @access public
+     */
+    function ReferenceExpectation(&$value, $message = '%s') {
+        $this->SimpleExpectation($message);
+        $this->_value =& $value;
+    }
+
+    /**
+     *    Tests the expectation. True if it exactly
+     *    references the held value.
+     *    @param mixed $compare        Comparison reference.
+     *    @return boolean              True if correct.
+     *    @access public
+     */
+    function test(&$compare) {
+        return SimpleTestCompatibility::isReference($this->_value, $compare);
+    }
+
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        if ($this->test($compare)) {
+            return "Reference expectation [" . $this->_dumper->describeValue($this->_value) . "]";
+        } else {
+            return "Reference expectation fails " .
+                    $this->_dumper->describeDifference($this->_value, $compare);
         }
-        
-        /**
-         *    Tests the expectation. True if the type or
-         *    class matches the string value.
-         *    @param string $compare        Comparison value.
-         *    @return boolean               True if correct.
-         *    @access public
-         */
-        function test($compare) {
-            if (is_object($compare)) {
-                return SimpleTestCompatibility::isA($compare, $this->_type);
-            } else {
-                return (strtolower(gettype($compare)) == $this->_canonicalType($this->_type));
-            }
+    }
+
+    function _getValue() {
+        return $this->_value;
+    }
+}
+
+/**
+ *    Test for identity.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class IdenticalExpectation extends EqualExpectation {
+
+    /**
+     *    Sets the value to compare against.
+     *    @param mixed $value       Test value to match.
+     *    @param string $message    Customised message on failure.
+     *    @access public
+     */
+    function IdenticalExpectation($value, $message = '%s') {
+        $this->EqualExpectation($value, $message);
+    }
+
+    /**
+     *    Tests the expectation. True if it exactly
+     *    matches the held value.
+     *    @param mixed $compare        Comparison value.
+     *    @return boolean              True if correct.
+     *    @access public
+     */
+    function test($compare) {
+        return SimpleTestCompatibility::isIdentical($this->_getValue(), $compare);
+    }
+
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        $dumper = &$this->_getDumper();
+        if ($this->test($compare)) {
+            return "Identical expectation [" . $dumper->describeValue($this->_getValue()) . "]";
+        } else {
+            return "Identical expectation [" . $dumper->describeValue($this->_getValue()) .
+                    "] fails with [" .
+                    $dumper->describeValue($compare) . "] " .
+                    $dumper->describeDifference($this->_getValue(), $compare, TYPE_MATTERS);
         }
+    }
+}
 
-        /**
-         *    Coerces type name into a gettype() match.
-         *    @param string $type        User type.
-         *    @return string             Simpler type.
-         *    @access private
-         */
-        function _canonicalType($type) {
-            $type = strtolower($type);
-            $map = array(
-                    'bool' => 'boolean',
-                    'float' => 'double',
-                    'real' => 'double',
-                    'int' => 'integer');
-            if (isset($map[$type])) {
-                $type = $map[$type];
-            }
-            return $type;
+/**
+ *    Test for non-identity.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class NotIdenticalExpectation extends IdenticalExpectation {
+
+    /**
+     *    Sets the value to compare against.
+     *    @param mixed $value        Test value to match.
+     *    @param string $message     Customised message on failure.
+     *    @access public
+     */
+    function NotIdenticalExpectation($value, $message = '%s') {
+        $this->IdenticalExpectation($value, $message);
+    }
+
+    /**
+     *    Tests the expectation. True if it differs from the
+     *    held value.
+     *    @param mixed $compare        Comparison value.
+     *    @return boolean              True if correct.
+     *    @access public
+     */
+    function test($compare) {
+        return ! parent::test($compare);
+    }
+
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        $dumper = &$this->_getDumper();
+        if ($this->test($compare)) {
+            return "Not identical expectation passes " .
+                    $dumper->describeDifference($this->_getValue(), $compare, TYPE_MATTERS);
+        } else {
+            return "Not identical expectation [" . $dumper->describeValue($this->_getValue()) . "] matches";
         }
+    }
+}
 
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
+/**
+ *    Test for a pattern using Perl regex rules.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class PatternExpectation extends SimpleExpectation {
+    var $_pattern;
+
+    /**
+     *    Sets the value to compare against.
+     *    @param string $pattern    Pattern to search for.
+     *    @param string $message    Customised message on failure.
+     *    @access public
+     */
+    function PatternExpectation($pattern, $message = '%s') {
+        $this->SimpleExpectation($message);
+        $this->_pattern = $pattern;
+    }
+
+    /**
+     *    Accessor for the pattern.
+     *    @return string       Perl regex as string.
+     *    @access protected
+     */
+    function _getPattern() {
+        return $this->_pattern;
+    }
+
+    /**
+     *    Tests the expectation. True if the Perl regex
+     *    matches the comparison value.
+     *    @param string $compare        Comparison value.
+     *    @return boolean               True if correct.
+     *    @access public
+     */
+    function test($compare) {
+        return (boolean)preg_match($this->_getPattern(), $compare);
+    }
+
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        if ($this->test($compare)) {
+            return $this->_describePatternMatch($this->_getPattern(), $compare);
+        } else {
             $dumper = &$this->_getDumper();
-            return "Value [" . $dumper->describeValue($compare) .
-                    "] should be type [" . $this->_type . "]";
+            return "Pattern [" . $this->_getPattern() .
+                    "] not detected in [" .
+                    $dumper->describeValue($compare) . "]";
         }
     }
-    
-    /**
-     *    Tests either type or class name if it's an object.
-     *    Will succeed if the type does not match.
-     *      @package SimpleTest
-     *      @subpackage UnitTester
-     */
-    class NotAExpectation extends IsAExpectation {
-        var $_type;
-        
-        /**
-         *    Sets the type to compare with.
-         *    @param string $type       Type or class name.
-         *    @param string $message    Customised message on failure.
-         *    @access public
-         */
-        function NotAExpectation($type, $message = '%s') {
-            $this->IsAExpectation($type, $message);
-        }
-        
-        /**
-         *    Tests the expectation. False if the type or
-         *    class matches the string value.
-         *    @param string $compare        Comparison value.
-         *    @return boolean               True if different.
-         *    @access public
-         */
-        function test($compare) {
-            return ! parent::test($compare);
-        }
 
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
+    /**
+     *    Describes a pattern match including the string
+     *    found and it's position.
+     *    @param string $pattern        Regex to match against.
+     *    @param string $subject        Subject to search.
+     *    @access protected
+     */
+    function _describePatternMatch($pattern, $subject) {
+        preg_match($pattern, $subject, $matches);
+        $position = strpos($subject, $matches[0]);
+        $dumper = $this->_getDumper();
+        return "Pattern [$pattern] detected at character [$position] in [" .
+                $dumper->describeValue($subject) . "] as [" .
+                $matches[0] . "] in region [" .
+                $dumper->clipString($subject, 100, $position) . "]";
+    }
+}
+
+/**
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ *    @deprecated
+ */
+class WantedPatternExpectation extends PatternExpectation {
+}
+
+/**
+ *    Fail if a pattern is detected within the
+ *    comparison.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class NoPatternExpectation extends PatternExpectation {
+
+    /**
+     *    Sets the reject pattern
+     *    @param string $pattern    Pattern to search for.
+     *    @param string $message    Customised message on failure.
+     *    @access public
+     */
+    function NoPatternExpectation($pattern, $message = '%s') {
+        $this->PatternExpectation($pattern, $message);
+    }
+
+    /**
+     *    Tests the expectation. False if the Perl regex
+     *    matches the comparison value.
+     *    @param string $compare        Comparison value.
+     *    @return boolean               True if correct.
+     *    @access public
+     */
+    function test($compare) {
+        return ! parent::test($compare);
+    }
+
+    /**
+     *    Returns a human readable test message.
+     *    @param string $compare      Comparison value.
+     *    @return string              Description of success
+     *                                or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        if ($this->test($compare)) {
             $dumper = &$this->_getDumper();
-            return "Value [" . $dumper->describeValue($compare) .
-                    "] should not be type [" . $this->_getType() . "]";
+            return "Pattern [" . $this->_getPattern() .
+                    "] not detected in [" .
+                    $dumper->describeValue($compare) . "]";
+        } else {
+            return $this->_describePatternMatch($this->_getPattern(), $compare);
         }
     }
+}
+
+/**
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ *      @deprecated
+ */
+class UnwantedPatternExpectation extends NoPatternExpectation {
+}
+
+/**
+ *    Tests either type or class name if it's an object.
+ *      @package SimpleTest
+ *      @subpackage UnitTester
+ */
+class IsAExpectation extends SimpleExpectation {
+    var $_type;
 
     /**
-     *    Tests for existance of a method in an object
-     *      @package SimpleTest
-     *      @subpackage UnitTester
-     */
-    class MethodExistsExpectation extends SimpleExpectation {
-        var $_method;
-        
-        /**
-         *    Sets the value to compare against.
-         *    @param string $method     Method to check.
-         *    @param string $message    Customised message on failure.
-         *    @access public
-         *    @return void
-         */
-        function MethodExistsExpectation($method, $message = '%s') {
-            $this->SimpleExpectation($message);
-            $this->_method = &$method;
-        }
-        
-        /**
-         *    Tests the expectation. True if the method exists in the test object.
-         *    @param string $compare        Comparison method name.
-         *    @return boolean               True if correct.
-         *    @access public
-         */
-        function test($compare) {
-            return (boolean)(is_object($compare) && method_exists($compare, $this->_method));
-        }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            $dumper = &$this->_getDumper();
-            if (! is_object($compare)) {
-                return 'No method on non-object [' . $dumper->describeValue($compare) . ']';
-            }
-            $method = $this->_method;
-            return "Object [" . $dumper->describeValue($compare) .
-                    "] should contain method [$method]";
+     *    Sets the type to compare with.
+     *    @param string $type       Type or class name.
+     *    @param string $message    Customised message on failure.
+     *    @access public
+     */
+    function IsAExpectation($type, $message = '%s') {
+        $this->SimpleExpectation($message);
+        $this->_type = $type;
+    }
+
+    /**
+     *    Accessor for type to check against.
+     *    @return string    Type or class name.
+     *    @access protected
+     */
+    function _getType() {
+        return $this->_type;
+    }
+
+    /**
+     *    Tests the expectation. True if the type or
+     *    class matches the string value.
+     *    @param string $compare        Comparison value.
+     *    @return boolean               True if correct.
+     *    @access public
+     */
+    function test($compare) {
+        if (is_object($compare)) {
+            return SimpleTestCompatibility::isA($compare, $this->_type);
+        } else {
+            return (strtolower(gettype($compare)) == $this->_canonicalType($this->_type));
         }
     }
+
+    /**
+     *    Coerces type name into a gettype() match.
+     *    @param string $type        User type.
+     *    @return string             Simpler type.
+     *    @access private
+     */
+    function _canonicalType($type) {
+        $type = strtolower($type);
+        $map = array(
+                'bool' => 'boolean',
+                'float' => 'double',
+                'real' => 'double',
+                'int' => 'integer');
+        if (isset($map[$type])) {
+            $type = $map[$type];
+        }
+        return $type;
+    }
+
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        $dumper = &$this->_getDumper();
+        return "Value [" . $dumper->describeValue($compare) .
+                "] should be type [" . $this->_type . "]";
+    }
+}
+
+/**
+ *    Tests either type or class name if it's an object.
+ *    Will succeed if the type does not match.
+ *      @package SimpleTest
+ *      @subpackage UnitTester
+ */
+class NotAExpectation extends IsAExpectation {
+    var $_type;
+
+    /**
+     *    Sets the type to compare with.
+     *    @param string $type       Type or class name.
+     *    @param string $message    Customised message on failure.
+     *    @access public
+     */
+    function NotAExpectation($type, $message = '%s') {
+        $this->IsAExpectation($type, $message);
+    }
+
+    /**
+     *    Tests the expectation. False if the type or
+     *    class matches the string value.
+     *    @param string $compare        Comparison value.
+     *    @return boolean               True if different.
+     *    @access public
+     */
+    function test($compare) {
+        return ! parent::test($compare);
+    }
+
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        $dumper = &$this->_getDumper();
+        return "Value [" . $dumper->describeValue($compare) .
+                "] should not be type [" . $this->_getType() . "]";
+    }
+}
+
+/**
+ *    Tests for existance of a method in an object
+ *      @package SimpleTest
+ *      @subpackage UnitTester
+ */
+class MethodExistsExpectation extends SimpleExpectation {
+    var $_method;
+
+    /**
+     *    Sets the value to compare against.
+     *    @param string $method     Method to check.
+     *    @param string $message    Customised message on failure.
+     *    @access public
+     *    @return void
+     */
+    function MethodExistsExpectation($method, $message = '%s') {
+        $this->SimpleExpectation($message);
+        $this->_method = &$method;
+    }
+
+    /**
+     *    Tests the expectation. True if the method exists in the test object.
+     *    @param string $compare        Comparison method name.
+     *    @return boolean               True if correct.
+     *    @access public
+     */
+    function test($compare) {
+        return (boolean)(is_object($compare) && method_exists($compare, $this->_method));
+    }
+
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        $dumper = &$this->_getDumper();
+        if (! is_object($compare)) {
+            return 'No method on non-object [' . $dumper->describeValue($compare) . ']';
+        }
+        $method = $this->_method;
+        return "Object [" . $dumper->describeValue($compare) .
+                "] should contain method [$method]";
+    }
+}
 ?>
\ No newline at end of file
index 1628ed7d231a798cb32b586a7c6e1ec2e2108604..2dd3cd0f2fa917366158fc27ba6390473b716f2b 100644 (file)
@@ -26,7 +26,7 @@
          *    @param $label        Test name to display.
          *    @public
          */
-        function TestCase($label) {
+        function TestCase($label = false) {
             $this->SimpleTestCase($label);
         }
         
diff --git a/lib/simpletestlib/extensions/testdox.php b/lib/simpletestlib/extensions/testdox.php
new file mode 100644 (file)
index 0000000..7db7c87
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+
+class TestDoxReporter extends SimpleReporter
+{
+    var $_test_case_pattern = '/^TestOf(.*)$/';
+
+    function TestDoxReporter($test_case_pattern = '/^TestOf(.*)$/') {
+        parent::SimpleScorer();
+        $this->_test_case_pattern = empty($test_case_pattern) ? '/^(.*)$/' : $test_case_pattern;
+    }
+
+    function paintCaseStart($test_name) {
+        preg_match($this->_test_case_pattern, $test_name, $matches);
+        if (!empty($matches[1])) {
+            echo $matches[1] . "\n";
+        } else {
+            echo $test_name . "\n";
+        }
+    }
+
+    function paintCaseEnd() {
+        echo "\n";
+    }
+
+    function paintMethodStart($test_name) {
+        if (!preg_match('/^test(.*)$/i', $test_name, $matches)) {
+            return;
+        }
+        $test_name = $matches[1];
+
+        $test_name = preg_replace('/([A-Z])([A-Z])/', '$1 $2', $test_name);
+        echo '- ' . strtolower(preg_replace('/([a-zA-Z])([A-Z0-9])/', '$1 $2', $test_name));
+    }
+
+    function paintMethodEnd() {
+        echo "\n";
+    }
+
+    function paintFail() {
+        echo " [FAILED]";
+    }
+}
diff --git a/lib/simpletestlib/extensions/testdox/test.php b/lib/simpletestlib/extensions/testdox/test.php
new file mode 100644 (file)
index 0000000..3527e76
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+// $Id$
+require_once dirname(__FILE__) . '/../../autorun.php';
+require_once dirname(__FILE__) . '/../testdox.php';
+
+// uncomment to see test dox in action
+//SimpleTest::prefer(new TestDoxReporter());
+
+class TestOfTestDoxReporter extends UnitTestCase
+{
+    function testIsAnInstanceOfSimpleScorerAndReporter() {
+        $dox = new TestDoxReporter();
+        $this->assertIsA($dox, 'SimpleScorer');
+        $this->assertIsA($dox, 'SimpleReporter');
+    }
+
+    function testOutputsNameOfTestCase() {
+        $dox = new TestDoxReporter();
+        ob_start();
+        $dox->paintCaseStart('TestOfTestDoxReporter');
+        $buffer = ob_get_clean();
+        $this->assertWantedPattern('/^TestDoxReporter/', $buffer);
+    }
+
+    function testOutputOfTestCaseNameFilteredByConstructParameter() {
+        $dox = new TestDoxReporter('/^(.*)Test$/');
+        ob_start();
+        $dox->paintCaseStart('SomeGreatWidgetTest');
+        $buffer = ob_get_clean();
+        $this->assertWantedPattern('/^SomeGreatWidget/', $buffer);
+    }
+
+    function testIfTest_case_patternIsEmptyAssumeEverythingMatches() {
+        $dox = new TestDoxReporter('');
+        ob_start();
+        $dox->paintCaseStart('TestOfTestDoxReporter');
+        $buffer = ob_get_clean();
+        $this->assertWantedPattern('/^TestOfTestDoxReporter/', $buffer);
+    }
+
+    function testEmptyLineInsertedWhenCaseEnds() {
+        $dox = new TestDoxReporter();
+        ob_start();
+        $dox->paintCaseEnd('TestOfTestDoxReporter');
+        $buffer = ob_get_clean();
+        $this->assertEqual("\n", $buffer);
+    }
+
+    function testPaintsTestMethodInTestDoxFormat() {
+        $dox = new TestDoxReporter();
+        ob_start();
+        $dox->paintMethodStart('testSomeGreatTestCase');
+        $buffer = ob_get_clean();
+        $this->assertEqual("- some great test case", $buffer);
+        unset($buffer);
+
+        $random = rand(100, 200);
+        ob_start();
+        $dox->paintMethodStart("testRandomNumberIs{$random}");
+        $buffer = ob_get_clean();
+        $this->assertEqual("- random number is {$random}", $buffer);
+    }
+
+    function testDoesNotOutputAnythingOnNoneTestMethods() {
+        $dox = new TestDoxReporter();
+        ob_start();
+        $dox->paintMethodStart('nonMatchingMethod');
+        $buffer = ob_get_clean();
+        $this->assertEqual('', $buffer);
+    }
+
+    function testPaintMethodAddLineBreak() {
+        $dox = new TestDoxReporter();
+        ob_start();
+        $dox->paintMethodEnd('someMethod');
+        $buffer = ob_get_clean();
+        $this->assertEqual("\n", $buffer);
+        $this->assertNoErrors();
+    }
+
+    function testProperlySpacesSingleLettersInMethodName() {
+        $dox = new TestDoxReporter();
+        ob_start();
+        $dox->paintMethodStart('testAVerySimpleAgainAVerySimpleMethod');
+        $buffer = ob_get_clean();
+        $this->assertEqual('- a very simple again a very simple method', $buffer);
+    }
+
+    function testOnFailureThisPrintsFailureNotice() {
+        $dox = new TestDoxReporter();
+        ob_start();
+        $dox->paintFail();
+        $buffer = ob_get_clean();
+        $this->assertEqual(' [FAILED]', $buffer);
+    }
+
+    function testWhenMatchingMethodNamesTestPrefixIsCaseInsensitive() {
+        $dox = new TestDoxReporter();
+        ob_start();
+        $dox->paintMethodStart('TESTSupportsAllUppercaseTestPrefixEvenThoughIDoNotKnowWhyYouWouldDoThat');
+        $buffer = ob_get_clean();
+        $this->assertEqual(
+            '- supports all uppercase test prefix even though i do not know why you would do that',
+            $buffer
+        );
+    }
+}
+
index f05a7f75b2431b20e8dba170cc8f5610c342d54e..8a73e5a81b3e55b4ee72c43d7033fc065562c7a2 100644 (file)
 <?php
+/**
+ *  Base include file for SimpleTest.
+ *  @package    SimpleTest
+ *  @subpackage WebTester
+ *  @version    $Id$
+ */
+    
+/**#@+
+ * include SimpleTest files
+ */
+require_once(dirname(__FILE__) . '/tag.php');
+require_once(dirname(__FILE__) . '/encoding.php');
+require_once(dirname(__FILE__) . '/selector.php');
+/**#@-*/
+
+/**
+ *    Form tag class to hold widget values.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleForm {
+    var $_method;
+    var $_action;
+    var $_encoding;
+    var $_default_target;
+    var $_id;
+    var $_buttons;
+    var $_images;
+    var $_widgets;
+    var $_radios;
+    var $_checkboxes;
+    
     /**
-     * Base include file for SimpleTest.
-     * @package        SimpleTest
-     * @subpackage     WebTester
-     * @version        $Id$
+     *    Starts with no held controls/widgets.
+     *    @param SimpleTag $tag        Form tag to read.
+     *    @param SimplePage $page      Holding page.
      */
-     
-    /**#@+
-     * include SimpleTest files
-     */
-    require_once(dirname(__FILE__) . '/tag.php');
-    require_once(dirname(__FILE__) . '/encoding.php');
-    require_once(dirname(__FILE__) . '/selector.php');
-    /**#@-*/
+    function SimpleForm($tag, &$page) {
+        $this->_method = $tag->getAttribute('method');
+        $this->_action = $this->_createAction($tag->getAttribute('action'), $page);
+        $this->_encoding = $this->_setEncodingClass($tag);
+        $this->_default_target = false;
+        $this->_id = $tag->getAttribute('id');
+        $this->_buttons = array();
+        $this->_images = array();
+        $this->_widgets = array();
+        $this->_radios = array();
+        $this->_checkboxes = array();
+    }
     
     /**
-     *    Form tag class to hold widget values.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Creates the request packet to be sent by the form.
+     *    @param SimpleTag $tag        Form tag to read.
+     *    @return string               Packet class.
+     *    @access private
      */
-    class SimpleForm {
-        var $_method;
-        var $_action;
-        var $_encoding;
-        var $_default_target;
-        var $_id;
-        var $_buttons;
-        var $_images;
-        var $_widgets;
-        var $_radios;
-        var $_checkboxes;
-        
-        /**
-         *    Starts with no held controls/widgets.
-         *    @param SimpleTag $tag        Form tag to read.
-         *    @param SimpleUrl $url        Location of holding page.
-         */
-        function SimpleForm($tag, $url) {
-            $this->_method = $tag->getAttribute('method');
-            $this->_action = $this->_createAction($tag->getAttribute('action'), $url);
-            $this->_encoding = $this->_setEncodingClass($tag);
-            $this->_default_target = false;
-            $this->_id = $tag->getAttribute('id');
-            $this->_buttons = array();
-            $this->_images = array();
-            $this->_widgets = array();
-            $this->_radios = array();
-            $this->_checkboxes = array();
-        }
-        
-        /**
-         *    Creates the request packet to be sent by the form.
-         *    @param SimpleTag $tag        Form tag to read.
-         *    @return string               Packet class.
-         *    @access private
-         */
-        function _setEncodingClass($tag) {
-            if (strtolower($tag->getAttribute('method')) == 'post') {
-                if (strtolower($tag->getAttribute('enctype')) == 'multipart/form-data') {
-                    return 'SimpleMultipartEncoding';
-                }
-                return 'SimplePostEncoding';
+    function _setEncodingClass($tag) {
+        if (strtolower($tag->getAttribute('method')) == 'post') {
+            if (strtolower($tag->getAttribute('enctype')) == 'multipart/form-data') {
+                return 'SimpleMultipartEncoding';
             }
-            return 'SimpleGetEncoding';
-        }
-        
-        /**
-         *    Sets the frame target within a frameset.
-         *    @param string $frame        Name of frame.
-         *    @access public
-         */
-        function setDefaultTarget($frame) {
-            $this->_default_target = $frame;
+            return 'SimplePostEncoding';
         }
-        
-        /**
-         *    Accessor for method of form submission.
-         *    @return string           Either get or post.
-         *    @access public
-         */
-        function getMethod() {
-            return ($this->_method ? strtolower($this->_method) : 'get');
-        }
-        
-        /**
-         *    Combined action attribute with current location
-         *    to get an absolute form target.
-         *    @param string $action    Action attribute from form tag.
-         *    @param SimpleUrl $base   Page location.
-         *    @return SimpleUrl        Absolute form target.
-         */
-        function _createAction($action, $base) {
-            if (($action === '') || ($action === false)) {
-                return $base;
-            }
-            $url = new SimpleUrl($action);
-            return $url->makeAbsolute($base);
+        return 'SimpleGetEncoding';
+    }
+    
+    /**
+     *    Sets the frame target within a frameset.
+     *    @param string $frame        Name of frame.
+     *    @access public
+     */
+    function setDefaultTarget($frame) {
+        $this->_default_target = $frame;
+    }
+    
+    /**
+     *    Accessor for method of form submission.
+     *    @return string           Either get or post.
+     *    @access public
+     */
+    function getMethod() {
+        return ($this->_method ? strtolower($this->_method) : 'get');
+    }
+    
+    /**
+     *    Combined action attribute with current location
+     *    to get an absolute form target.
+     *    @param string $action    Action attribute from form tag.
+     *    @param SimpleUrl $base   Page location.
+     *    @return SimpleUrl        Absolute form target.
+     */
+    function _createAction($action, &$page) {
+        if (($action === '') || ($action === false)) {
+            return $page->expandUrl($page->getUrl());
         }
-        
-        /**
-         *    Absolute URL of the target.
-         *    @return SimpleUrl           URL target.
-         *    @access public
-         */
-        function getAction() {
-            $url = $this->_action;
-            if ($this->_default_target && ! $url->getTarget()) {
-                $url->setTarget($this->_default_target);
-            }
-            return $url;
+        return $page->expandUrl(new SimpleUrl($action));;
+    }
+    
+    /**
+     *    Absolute URL of the target.
+     *    @return SimpleUrl           URL target.
+     *    @access public
+     */
+    function getAction() {
+        $url = $this->_action;
+        if ($this->_default_target && ! $url->getTarget()) {
+            $url->setTarget($this->_default_target);
         }
-       
-        /**
-         *    Creates the encoding for the current values in the
-         *    form.
-         *    @return SimpleFormEncoding    Request to submit.
-         *    @access private
-         */
-        function _encode() {
-            $class = $this->_encoding;
-            $encoding = new $class();
-            for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
-                $this->_widgets[$i]->write($encoding);
-            }
-            return $encoding;
+        return $url;
+    }
+    
+    /**
+     *    Creates the encoding for the current values in the
+     *    form.
+     *    @return SimpleFormEncoding    Request to submit.
+     *    @access private
+     */
+    function _encode() {
+        $class = $this->_encoding;
+        $encoding = new $class();
+        for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
+            $this->_widgets[$i]->write($encoding);
         }
-                
-        /**
-         *    ID field of form for unique identification.
-         *    @return string           Unique tag ID.
-         *    @access public
-         */
-        function getId() {
-            return $this->_id;
+        return $encoding;
+    }
+            
+    /**
+     *    ID field of form for unique identification.
+     *    @return string           Unique tag ID.
+     *    @access public
+     */
+    function getId() {
+        return $this->_id;
+    }
+    
+    /**
+     *    Adds a tag contents to the form.
+     *    @param SimpleWidget $tag        Input tag to add.
+     *    @access public
+     */
+    function addWidget(&$tag) {
+        if (strtolower($tag->getAttribute('type')) == 'submit') {
+            $this->_buttons[] = &$tag;
+        } elseif (strtolower($tag->getAttribute('type')) == 'image') {
+            $this->_images[] = &$tag;
+        } elseif ($tag->getName()) {
+            $this->_setWidget($tag);
         }
-        
-        /**
-         *    Adds a tag contents to the form.
-         *    @param SimpleWidget $tag        Input tag to add.
-         *    @access public
-         */
-        function addWidget(&$tag) {
-            if (strtolower($tag->getAttribute('type')) == 'submit') {
-                $this->_buttons[] = &$tag;
-            } elseif (strtolower($tag->getAttribute('type')) == 'image') {
-                $this->_images[] = &$tag;
-            } elseif ($tag->getName()) {
-                $this->_setWidget($tag);
-            }
+    }
+    
+    /**
+     *    Sets the widget into the form, grouping radio
+     *    buttons if any.
+     *    @param SimpleWidget $tag   Incoming form control.
+     *    @access private
+     */
+    function _setWidget(&$tag) {
+        if (strtolower($tag->getAttribute('type')) == 'radio') {
+            $this->_addRadioButton($tag);
+        } elseif (strtolower($tag->getAttribute('type')) == 'checkbox') {
+            $this->_addCheckbox($tag);
+        } else {
+            $this->_widgets[] = &$tag;
         }
-        
-        /**
-         *    Sets the widget into the form, grouping radio
-         *    buttons if any.
-         *    @param SimpleWidget $tag   Incoming form control.
-         *    @access private
-         */
-        function _setWidget(&$tag) {
-            if (strtolower($tag->getAttribute('type')) == 'radio') {
-                $this->_addRadioButton($tag);
-            } elseif (strtolower($tag->getAttribute('type')) == 'checkbox') {
-                $this->_addCheckbox($tag);
-            } else {
-                $this->_widgets[] = &$tag;
-            }
+    }
+    
+    /**
+     *    Adds a radio button, building a group if necessary.
+     *    @param SimpleRadioButtonTag $tag   Incoming form control.
+     *    @access private
+     */
+    function _addRadioButton(&$tag) {
+        if (! isset($this->_radios[$tag->getName()])) {
+            $this->_widgets[] = &new SimpleRadioGroup();
+            $this->_radios[$tag->getName()] = count($this->_widgets) - 1;
         }
-        
-        /**
-         *    Adds a radio button, building a group if necessary.
-         *    @param SimpleRadioButtonTag $tag   Incoming form control.
-         *    @access private
-         */
-        function _addRadioButton(&$tag) {
-            if (! isset($this->_radios[$tag->getName()])) {
-                $this->_widgets[] = &new SimpleRadioGroup();
-                $this->_radios[$tag->getName()] = count($this->_widgets) - 1;
+        $this->_widgets[$this->_radios[$tag->getName()]]->addWidget($tag);
+    }
+    
+    /**
+     *    Adds a checkbox, making it a group on a repeated name.
+     *    @param SimpleCheckboxTag $tag   Incoming form control.
+     *    @access private
+     */
+    function _addCheckbox(&$tag) {
+        if (! isset($this->_checkboxes[$tag->getName()])) {
+            $this->_widgets[] = &$tag;
+            $this->_checkboxes[$tag->getName()] = count($this->_widgets) - 1;
+        } else {
+            $index = $this->_checkboxes[$tag->getName()];
+            if (! SimpleTestCompatibility::isA($this->_widgets[$index], 'SimpleCheckboxGroup')) {
+                $previous = &$this->_widgets[$index];
+                $this->_widgets[$index] = &new SimpleCheckboxGroup();
+                $this->_widgets[$index]->addWidget($previous);
             }
-            $this->_widgets[$this->_radios[$tag->getName()]]->addWidget($tag);
+            $this->_widgets[$index]->addWidget($tag);
         }
-        
-        /**
-         *    Adds a checkbox, making it a group on a repeated name.
-         *    @param SimpleCheckboxTag $tag   Incoming form control.
-         *    @access private
-         */
-        function _addCheckbox(&$tag) {
-            if (! isset($this->_checkboxes[$tag->getName()])) {
-                $this->_widgets[] = &$tag;
-                $this->_checkboxes[$tag->getName()] = count($this->_widgets) - 1;
-            } else {
-                $index = $this->_checkboxes[$tag->getName()];
-                if (! SimpleTestCompatibility::isA($this->_widgets[$index], 'SimpleCheckboxGroup')) {
-                    $previous = &$this->_widgets[$index];
-                    $this->_widgets[$index] = &new SimpleCheckboxGroup();
-                    $this->_widgets[$index]->addWidget($previous);
-                }
-                $this->_widgets[$index]->addWidget($tag);
+    }
+    
+    /**
+     *    Extracts current value from form.
+     *    @param SimpleSelector $selector   Criteria to apply.
+     *    @return string/array              Value(s) as string or null
+     *                                      if not set.
+     *    @access public
+     */
+    function getValue($selector) {
+        for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
+            if ($selector->isMatch($this->_widgets[$i])) {
+                return $this->_widgets[$i]->getValue();
             }
         }
-        
-        /**
-         *    Extracts current value from form.
-         *    @param SimpleSelector $selector   Criteria to apply.
-         *    @return string/array              Value(s) as string or null
-         *                                      if not set.
-         *    @access public
-         */
-        function getValue($selector) {
-            for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
-                if ($selector->isMatch($this->_widgets[$i])) {
-                    return $this->_widgets[$i]->getValue();
-                }
+        foreach ($this->_buttons as $button) {
+            if ($selector->isMatch($button)) {
+                return $button->getValue();
             }
-            foreach ($this->_buttons as $button) {
-                if ($selector->isMatch($button)) {
-                    return $button->getValue();
-                }
-            }
-            return null;
         }
-        
-        /**
-         *    Sets a widget value within the form.
-         *    @param SimpleSelector $selector   Criteria to apply.
-         *    @param string $value              Value to input into the widget.
-         *    @return boolean                   True if value is legal, false
-         *                                      otherwise. If the field is not
-         *                                      present, nothing will be set.
-         *    @access public
-         */
-        function setField($selector, $value) {
-            $success = false;
-            for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
-                if ($selector->isMatch($this->_widgets[$i])) {
+        return null;
+    }
+    
+    /**
+     *    Sets a widget value within the form.
+     *    @param SimpleSelector $selector   Criteria to apply.
+     *    @param string $value              Value to input into the widget.
+     *    @return boolean                   True if value is legal, false
+     *                                      otherwise. If the field is not
+     *                                      present, nothing will be set.
+     *    @access public
+     */
+    function setField($selector, $value, $position=false) {
+        $success = false;
+        $_position = 0;
+        for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
+            if ($selector->isMatch($this->_widgets[$i])) {
+                $_position++;
+                if ($position === false or $_position === (int)$position) {
                     if ($this->_widgets[$i]->setValue($value)) {
                         $success = true;
                     }
                 }
             }
-            return $success;
         }
-        
-        /**
-         *    Used by the page object to set widgets labels to
-         *    external label tags.
-         *    @param SimpleSelector $selector   Criteria to apply.
-         *    @access public
-         */
-        function attachLabelBySelector($selector, $label) {
-            for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
-                if ($selector->isMatch($this->_widgets[$i])) {
-                    if (method_exists($this->_widgets[$i], 'setLabel')) {
-                        $this->_widgets[$i]->setLabel($label);
-                        return;
-                    }
+        return $success;
+    }
+    
+    /**
+     *    Used by the page object to set widgets labels to
+     *    external label tags.
+     *    @param SimpleSelector $selector   Criteria to apply.
+     *    @access public
+     */
+    function attachLabelBySelector($selector, $label) {
+        for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
+            if ($selector->isMatch($this->_widgets[$i])) {
+                if (method_exists($this->_widgets[$i], 'setLabel')) {
+                    $this->_widgets[$i]->setLabel($label);
+                    return;
                 }
             }
         }
-        
-        /**
-         *    Test to see if a form has a submit button.
-         *    @param SimpleSelector $selector   Criteria to apply.
-         *    @return boolean                   True if present.
-         *    @access public
-         */
-        function hasSubmit($selector) {
-            foreach ($this->_buttons as $button) {
-                if ($selector->isMatch($button)) {
-                    return true;
-                }
+    }
+    
+    /**
+     *    Test to see if a form has a submit button.
+     *    @param SimpleSelector $selector   Criteria to apply.
+     *    @return boolean                   True if present.
+     *    @access public
+     */
+    function hasSubmit($selector) {
+        foreach ($this->_buttons as $button) {
+            if ($selector->isMatch($button)) {
+                return true;
             }
-            return false;
         }
-        
-        /**
-         *    Test to see if a form has an image control.
-         *    @param SimpleSelector $selector   Criteria to apply.
-         *    @return boolean                   True if present.
-         *    @access public
-         */
-        function hasImage($selector) {
-            foreach ($this->_images as $image) {
-                if ($selector->isMatch($image)) {
-                    return true;
-                }
+        return false;
+    }
+    
+    /**
+     *    Test to see if a form has an image control.
+     *    @param SimpleSelector $selector   Criteria to apply.
+     *    @return boolean                   True if present.
+     *    @access public
+     */
+    function hasImage($selector) {
+        foreach ($this->_images as $image) {
+            if ($selector->isMatch($image)) {
+                return true;
             }
-            return false;
         }
-       
-        /**
-         *    Gets the submit values for a selected button.
-         *    @param SimpleSelector $selector   Criteria to apply.
-         *    @param hash $additional           Additional data for the form.
-         *    @return SimpleEncoding            Submitted values or false
-         *                                      if there is no such button
-         *                                      in the form.
-         *    @access public
-         */
-        function submitButton($selector, $additional = false) {
-            $additional = $additional ? $additional : array();
-            foreach ($this->_buttons as $button) {
-                if ($selector->isMatch($button)) {
-                    $encoding = $this->_encode();
-                    $button->write($encoding);
-                    if ($additional) {
-                        $encoding->merge($additional);
-                    }
-                    return $encoding;           
+        return false;
+    }
+    
+    /**
+     *    Gets the submit values for a selected button.
+     *    @param SimpleSelector $selector   Criteria to apply.
+     *    @param hash $additional           Additional data for the form.
+     *    @return SimpleEncoding            Submitted values or false
+     *                                      if there is no such button
+     *                                      in the form.
+     *    @access public
+     */
+    function submitButton($selector, $additional = false) {
+        $additional = $additional ? $additional : array();
+        foreach ($this->_buttons as $button) {
+            if ($selector->isMatch($button)) {
+                $encoding = $this->_encode();
+                $button->write($encoding);
+                if ($additional) {
+                    $encoding->merge($additional);
                 }
+                return $encoding;           
             }
-            return false;
         }
-         
-        /**
-         *    Gets the submit values for an image.
-         *    @param SimpleSelector $selector   Criteria to apply.
-         *    @param integer $x                 X-coordinate of click.
-         *    @param integer $y                 Y-coordinate of click.
-         *    @param hash $additional           Additional data for the form.
-         *    @return SimpleEncoding            Submitted values or false
-         *                                      if there is no such button in the
-         *                                      form.
-         *    @access public
-         */
-        function submitImage($selector, $x, $y, $additional = false) {
-            $additional = $additional ? $additional : array();
-            foreach ($this->_images as $image) {
-                if ($selector->isMatch($image)) {
-                    $encoding = $this->_encode();
-                    $image->write($encoding, $x, $y);
-                    if ($additional) {
-                        $encoding->merge($additional);
-                    }
-                    return $encoding;           
+        return false;
+    }
+        
+    /**
+     *    Gets the submit values for an image.
+     *    @param SimpleSelector $selector   Criteria to apply.
+     *    @param integer $x                 X-coordinate of click.
+     *    @param integer $y                 Y-coordinate of click.
+     *    @param hash $additional           Additional data for the form.
+     *    @return SimpleEncoding            Submitted values or false
+     *                                      if there is no such button in the
+     *                                      form.
+     *    @access public
+     */
+    function submitImage($selector, $x, $y, $additional = false) {
+        $additional = $additional ? $additional : array();
+        foreach ($this->_images as $image) {
+            if ($selector->isMatch($image)) {
+                $encoding = $this->_encode();
+                $image->write($encoding, $x, $y);
+                if ($additional) {
+                    $encoding->merge($additional);
                 }
+                return $encoding;           
             }
-            return false;
-        }
-      
-        /**
-         *    Simply submits the form without the submit button
-         *    value. Used when there is only one button or it
-         *    is unimportant.
-         *    @return hash           Submitted values.
-         *    @access public
-         */
-        function submit() {
-            return $this->_encode();
         }
+        return false;
+    }
+    
+    /**
+     *    Simply submits the form without the submit button
+     *    value. Used when there is only one button or it
+     *    is unimportant.
+     *    @return hash           Submitted values.
+     *    @access public
+     */
+    function submit() {
+        return $this->_encode();
     }
-?>
+}
+?>
\ No newline at end of file
index ce669eaab116a96eff1f0815fc3849b2dba17d44..091750c2b21458787c8621d520acdace0469e183 100644 (file)
 <?php
+/**
+ *  Base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage WebTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ *  include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/page.php');
+require_once(dirname(__FILE__) . '/user_agent.php');
+/**#@-*/
+
+/**
+ *    A composite page. Wraps a frameset page and
+ *    adds subframes. The original page will be
+ *    mostly ignored. Implements the SimplePage
+ *    interface so as to be interchangeable.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleFrameset {
+    var $_frameset;
+    var $_frames;
+    var $_focus;
+    var $_names;
+
     /**
-     * Base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     WebTester
-     * @version        $Id$
-     */
-
-    /**#@+
-     * include other SimpleTest class files
-     */
-    require_once(dirname(__FILE__) . '/page.php');
-    require_once(dirname(__FILE__) . '/user_agent.php');
-    /**#@-*/
-
-    /**
-     *    A composite page. Wraps a frameset page and
-     *    adds subframes. The original page will be
-     *    mostly ignored. Implements the SimplePage
-     *    interface so as to be interchangeable.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleFrameset {
-        var $_frameset;
-        var $_frames;
-        var $_focus;
-        var $_names;
-
-        /**
-         *    Stashes the frameset page. Will make use of the
-         *    browser to fetch the sub frames recursively.
-         *    @param SimplePage $page        Frameset page.
-         */
-        function SimpleFrameset(&$page) {
-            $this->_frameset = &$page;
-            $this->_frames = array();
-            $this->_focus = false;
-            $this->_names = array();
-        }
-
-        /**
-         *    Adds a parsed page to the frameset.
-         *    @param SimplePage $page    Frame page.
-         *    @param string $name        Name of frame in frameset.
-         *    @access public
-         */
-        function addFrame(&$page, $name = false) {
-            $this->_frames[] = &$page;
-            if ($name) {
-                $this->_names[$name] = count($this->_frames) - 1;
-            }
+     *    Stashes the frameset page. Will make use of the
+     *    browser to fetch the sub frames recursively.
+     *    @param SimplePage $page        Frameset page.
+     */
+    function SimpleFrameset(&$page) {
+        $this->_frameset = &$page;
+        $this->_frames = array();
+        $this->_focus = false;
+        $this->_names = array();
+    }
+
+    /**
+     *    Adds a parsed page to the frameset.
+     *    @param SimplePage $page    Frame page.
+     *    @param string $name        Name of frame in frameset.
+     *    @access public
+     */
+    function addFrame(&$page, $name = false) {
+        $this->_frames[] = &$page;
+        if ($name) {
+            $this->_names[$name] = count($this->_frames) - 1;
         }
+    }
 
-        /**
-         *    Replaces existing frame with another. If the
-         *    frame is nested, then the call is passed down
-         *    one level.
-         *    @param array $path        Path of frame in frameset.
-         *    @param SimplePage $page   Frame source.
-         *    @access public
-         */
-        function setFrame($path, &$page) {
-            $name = array_shift($path);
-            if (isset($this->_names[$name])) {
-                $index = $this->_names[$name];
-            } else {
-                $index = $name - 1;
-            }
-            if (count($path) == 0) {
-                $this->_frames[$index] = &$page;
-                return;
-            }
-            $this->_frames[$index]->setFrame($path, $page);
-        }
-
-        /**
-         *    Accessor for current frame focus. Will be
-         *    false if no frame has focus. Will have the nested
-         *    frame focus if any.
-         *    @return array     Labels or indexes of nested frames.
-         *    @access public
-         */
-        function getFrameFocus() {
-            if ($this->_focus === false) {
-                return array();
-            }
-            return array_merge(
-                    array($this->_getPublicNameFromIndex($this->_focus)),
-                    $this->_frames[$this->_focus]->getFrameFocus());
-        }
-
-        /**
-         *    Turns an internal array index into the frames list
-         *    into a public name, or if none, then a one offset
-         *    index.
-         *    @param integer $subject    Internal index.
-         *    @return integer/string     Public name.
-         *    @access private
-         */
-        function _getPublicNameFromIndex($subject) {
-            foreach ($this->_names as $name => $index) {
-                if ($subject == $index) {
-                    return $name;
-                }
+    /**
+     *    Replaces existing frame with another. If the
+     *    frame is nested, then the call is passed down
+     *    one level.
+     *    @param array $path        Path of frame in frameset.
+     *    @param SimplePage $page   Frame source.
+     *    @access public
+     */
+    function setFrame($path, &$page) {
+        $name = array_shift($path);
+        if (isset($this->_names[$name])) {
+            $index = $this->_names[$name];
+        } else {
+            $index = $name - 1;
+        }
+        if (count($path) == 0) {
+            $this->_frames[$index] = &$page;
+            return;
+        }
+        $this->_frames[$index]->setFrame($path, $page);
+    }
+
+    /**
+     *    Accessor for current frame focus. Will be
+     *    false if no frame has focus. Will have the nested
+     *    frame focus if any.
+     *    @return array     Labels or indexes of nested frames.
+     *    @access public
+     */
+    function getFrameFocus() {
+        if ($this->_focus === false) {
+            return array();
+        }
+        return array_merge(
+                array($this->_getPublicNameFromIndex($this->_focus)),
+                $this->_frames[$this->_focus]->getFrameFocus());
+    }
+
+    /**
+     *    Turns an internal array index into the frames list
+     *    into a public name, or if none, then a one offset
+     *    index.
+     *    @param integer $subject    Internal index.
+     *    @return integer/string     Public name.
+     *    @access private
+     */
+    function _getPublicNameFromIndex($subject) {
+        foreach ($this->_names as $name => $index) {
+            if ($subject == $index) {
+                return $name;
             }
-            return $subject + 1;
-        }
-
-        /**
-         *    Sets the focus by index. The integer index starts from 1.
-         *    If already focused and the target frame also has frames,
-         *    then the nested frame will be focused.
-         *    @param integer $choice    Chosen frame.
-         *    @return boolean           True if frame exists.
-         *    @access public
-         */
-        function setFrameFocusByIndex($choice) {
-            if (is_integer($this->_focus)) {
-                if ($this->_frames[$this->_focus]->hasFrames()) {
-                    return $this->_frames[$this->_focus]->setFrameFocusByIndex($choice);
-                }
+        }
+        return $subject + 1;
+    }
+
+    /**
+     *    Sets the focus by index. The integer index starts from 1.
+     *    If already focused and the target frame also has frames,
+     *    then the nested frame will be focused.
+     *    @param integer $choice    Chosen frame.
+     *    @return boolean           True if frame exists.
+     *    @access public
+     */
+    function setFrameFocusByIndex($choice) {
+        if (is_integer($this->_focus)) {
+            if ($this->_frames[$this->_focus]->hasFrames()) {
+                return $this->_frames[$this->_focus]->setFrameFocusByIndex($choice);
             }
-            if (($choice < 1) || ($choice > count($this->_frames))) {
-                return false;
+        }
+        if (($choice < 1) || ($choice > count($this->_frames))) {
+            return false;
+        }
+        $this->_focus = $choice - 1;
+        return true;
+    }
+
+    /**
+     *    Sets the focus by name. If already focused and the
+     *    target frame also has frames, then the nested frame
+     *    will be focused.
+     *    @param string $name    Chosen frame.
+     *    @return boolean        True if frame exists.
+     *    @access public
+     */
+    function setFrameFocus($name) {
+        if (is_integer($this->_focus)) {
+            if ($this->_frames[$this->_focus]->hasFrames()) {
+                return $this->_frames[$this->_focus]->setFrameFocus($name);
             }
-            $this->_focus = $choice - 1;
+        }
+        if (in_array($name, array_keys($this->_names))) {
+            $this->_focus = $this->_names[$name];
             return true;
         }
+        return false;
+    }
 
-        /**
-         *    Sets the focus by name. If already focused and the
-         *    target frame also has frames, then the nested frame
-         *    will be focused.
-         *    @param string $name    Chosen frame.
-         *    @return boolean        True if frame exists.
-         *    @access public
-         */
-        function setFrameFocus($name) {
-            if (is_integer($this->_focus)) {
-                if ($this->_frames[$this->_focus]->hasFrames()) {
-                    return $this->_frames[$this->_focus]->setFrameFocus($name);
-                }
-            }
-            if (in_array($name, array_keys($this->_names))) {
-                $this->_focus = $this->_names[$name];
-                return true;
-            }
-            return false;
+    /**
+     *    Clears the frame focus.
+     *    @access public
+     */
+    function clearFrameFocus() {
+        $this->_focus = false;
+        $this->_clearNestedFramesFocus();
+    }
+
+    /**
+     *    Clears the frame focus for any nested frames.
+     *    @access private
+     */
+    function _clearNestedFramesFocus() {
+        for ($i = 0; $i < count($this->_frames); $i++) {
+            $this->_frames[$i]->clearFrameFocus();
         }
+    }
+
+    /**
+     *    Test for the presence of a frameset.
+     *    @return boolean        Always true.
+     *    @access public
+     */
+    function hasFrames() {
+        return true;
+    }
 
-        /**
-         *    Clears the frame focus.
-         *    @access public
-         */
-        function clearFrameFocus() {
-            $this->_focus = false;
-            $this->_clearNestedFramesFocus();
+    /**
+     *    Accessor for frames information.
+     *    @return array/string      Recursive hash of frame URL strings.
+     *                              The key is either a numerical
+     *                              index or the name attribute.
+     *    @access public
+     */
+    function getFrames() {
+        $report = array();
+        for ($i = 0; $i < count($this->_frames); $i++) {
+            $report[$this->_getPublicNameFromIndex($i)] =
+                    $this->_frames[$i]->getFrames();
         }
+        return $report;
+    }
 
-        /**
-         *    Clears the frame focus for any nested frames.
-         *    @access private
-         */
-        function _clearNestedFramesFocus() {
-            for ($i = 0; $i < count($this->_frames); $i++) {
-                $this->_frames[$i]->clearFrameFocus();
-            }
+    /**
+     *    Accessor for raw text of either all the pages or
+     *    the frame in focus.
+     *    @return string        Raw unparsed content.
+     *    @access public
+     */
+    function getRaw() {
+        if (is_integer($this->_focus)) {
+            return $this->_frames[$this->_focus]->getRaw();
         }
+        $raw = '';
+        for ($i = 0; $i < count($this->_frames); $i++) {
+            $raw .= $this->_frames[$i]->getRaw();
+        }
+        return $raw;
+    }
 
-        /**
-         *    Test for the presence of a frameset.
-         *    @return boolean        Always true.
-         *    @access public
-         */
-        function hasFrames() {
-            return true;
+    /**
+     *    Accessor for plain text of either all the pages or
+     *    the frame in focus.
+     *    @return string        Plain text content.
+     *    @access public
+     */
+    function getText() {
+        if (is_integer($this->_focus)) {
+            return $this->_frames[$this->_focus]->getText();
+        }
+        $raw = '';
+        for ($i = 0; $i < count($this->_frames); $i++) {
+            $raw .= ' ' . $this->_frames[$i]->getText();
         }
+        return trim($raw);
+    }
 
-        /**
-         *    Accessor for frames information.
-         *    @return array/string      Recursive hash of frame URL strings.
-         *                              The key is either a numerical
-         *                              index or the name attribute.
-         *    @access public
-         */
-        function getFrames() {
-            $report = array();
-            for ($i = 0; $i < count($this->_frames); $i++) {
-                $report[$this->_getPublicNameFromIndex($i)] =
-                        $this->_frames[$i]->getFrames();
-            }
-            return $report;
-        }
-
-        /**
-         *    Accessor for raw text of either all the pages or
-         *    the frame in focus.
-         *    @return string        Raw unparsed content.
-         *    @access public
-         */
-        function getRaw() {
-            if (is_integer($this->_focus)) {
-                return $this->_frames[$this->_focus]->getRaw();
-            }
-            $raw = '';
-            for ($i = 0; $i < count($this->_frames); $i++) {
-                $raw .= $this->_frames[$i]->getRaw();
-            }
-            return $raw;
-        }
-
-        /**
-         *    Accessor for plain text of either all the pages or
-         *    the frame in focus.
-         *    @return string        Plain text content.
-         *    @access public
-         */
-        function getText() {
-            if (is_integer($this->_focus)) {
-                return $this->_frames[$this->_focus]->getText();
-            }
-            $raw = '';
-            for ($i = 0; $i < count($this->_frames); $i++) {
-                $raw .= ' ' . $this->_frames[$i]->getText();
-            }
-            return trim($raw);
+    /**
+     *    Accessor for last error.
+     *    @return string        Error from last response.
+     *    @access public
+     */
+    function getTransportError() {
+        if (is_integer($this->_focus)) {
+            return $this->_frames[$this->_focus]->getTransportError();
         }
+        return $this->_frameset->getTransportError();
+    }
 
-        /**
-         *    Accessor for last error.
-         *    @return string        Error from last response.
-         *    @access public
-         */
-        function getTransportError() {
-            if (is_integer($this->_focus)) {
-                return $this->_frames[$this->_focus]->getTransportError();
-            }
-            return $this->_frameset->getTransportError();
+    /**
+     *    Request method used to fetch this frame.
+     *    @return string      GET, POST or HEAD.
+     *    @access public
+     */
+    function getMethod() {
+        if (is_integer($this->_focus)) {
+            return $this->_frames[$this->_focus]->getMethod();
         }
+        return $this->_frameset->getMethod();
+    }
 
-        /**
-         *    Request method used to fetch this frame.
-         *    @return string      GET, POST or HEAD.
-         *    @access public
-         */
-        function getMethod() {
-            if (is_integer($this->_focus)) {
-                return $this->_frames[$this->_focus]->getMethod();
-            }
-            return $this->_frameset->getMethod();
-        }
-
-        /**
-         *    Original resource name.
-         *    @return SimpleUrl        Current url.
-         *    @access public
-         */
-        function getUrl() {
-            if (is_integer($this->_focus)) {
-                $url = $this->_frames[$this->_focus]->getUrl();
-                $url->setTarget($this->_getPublicNameFromIndex($this->_focus));
-            } else {
-                $url = $this->_frameset->getUrl();
-            }
-            return $url;
+    /**
+     *    Original resource name.
+     *    @return SimpleUrl        Current url.
+     *    @access public
+     */
+    function getUrl() {
+        if (is_integer($this->_focus)) {
+            $url = $this->_frames[$this->_focus]->getUrl();
+            $url->setTarget($this->_getPublicNameFromIndex($this->_focus));
+        } else {
+            $url = $this->_frameset->getUrl();
+        }
+        return $url;
+    }
+
+    /**
+     *    Page base URL.
+     *    @return SimpleUrl        Current url.
+     *    @access public
+     */
+    function getBaseUrl() {
+        if (is_integer($this->_focus)) {
+            $url = $this->_frames[$this->_focus]->getBaseUrl();
+        } else {
+            $url = $this->_frameset->getBaseUrl();
         }
+        return $url;
+    }
 
-        /**
-         *    Original request data.
-         *    @return mixed              Sent content.
-         *    @access public
-         */
-        function getRequestData() {
-            if (is_integer($this->_focus)) {
-                return $this->_frames[$this->_focus]->getRequestData();
-            }
-            return $this->_frameset->getRequestData();
+    /**
+     *    Expands expandomatic URLs into fully qualified
+     *    URLs for the frameset page.
+     *    @param SimpleUrl $url        Relative URL.
+     *    @return SimpleUrl            Absolute URL.
+     *    @access public
+     */
+    function expandUrl($url) {
+        return $this->_frameset->expandUrl($url);
+    }
+
+    /**
+     *    Original request data.
+     *    @return mixed              Sent content.
+     *    @access public
+     */
+    function getRequestData() {
+        if (is_integer($this->_focus)) {
+            return $this->_frames[$this->_focus]->getRequestData();
         }
+        return $this->_frameset->getRequestData();
+    }
 
-        /**
-         *    Accessor for current MIME type.
-         *    @return string    MIME type as string; e.g. 'text/html'
-         *    @access public
-         */
-        function getMimeType() {
-            if (is_integer($this->_focus)) {
-                return $this->_frames[$this->_focus]->getMimeType();
-            }
-            return $this->_frameset->getMimeType();
+    /**
+     *    Accessor for current MIME type.
+     *    @return string    MIME type as string; e.g. 'text/html'
+     *    @access public
+     */
+    function getMimeType() {
+        if (is_integer($this->_focus)) {
+            return $this->_frames[$this->_focus]->getMimeType();
         }
+        return $this->_frameset->getMimeType();
+    }
 
-        /**
-         *    Accessor for last response code.
-         *    @return integer    Last HTTP response code received.
-         *    @access public
-         */
-        function getResponseCode() {
-            if (is_integer($this->_focus)) {
-                return $this->_frames[$this->_focus]->getResponseCode();
-            }
-            return $this->_frameset->getResponseCode();
-        }
-
-        /**
-         *    Accessor for last Authentication type. Only valid
-         *    straight after a challenge (401).
-         *    @return string    Description of challenge type.
-         *    @access public
-         */
-        function getAuthentication() {
-            if (is_integer($this->_focus)) {
-                return $this->_frames[$this->_focus]->getAuthentication();
-            }
-            return $this->_frameset->getAuthentication();
-        }
-
-        /**
-         *    Accessor for last Authentication realm. Only valid
-         *    straight after a challenge (401).
-         *    @return string    Name of security realm.
-         *    @access public
-         */
-        function getRealm() {
-            if (is_integer($this->_focus)) {
-                return $this->_frames[$this->_focus]->getRealm();
-            }
-            return $this->_frameset->getRealm();
+    /**
+     *    Accessor for last response code.
+     *    @return integer    Last HTTP response code received.
+     *    @access public
+     */
+    function getResponseCode() {
+        if (is_integer($this->_focus)) {
+            return $this->_frames[$this->_focus]->getResponseCode();
         }
+        return $this->_frameset->getResponseCode();
+    }
 
-        /**
-         *    Accessor for outgoing header information.
-         *    @return string      Header block.
-         *    @access public
-         */
-        function getRequest() {
-            if (is_integer($this->_focus)) {
-                return $this->_frames[$this->_focus]->getRequest();
-            }
-            return $this->_frameset->getRequest();
+    /**
+     *    Accessor for last Authentication type. Only valid
+     *    straight after a challenge (401).
+     *    @return string    Description of challenge type.
+     *    @access public
+     */
+    function getAuthentication() {
+        if (is_integer($this->_focus)) {
+            return $this->_frames[$this->_focus]->getAuthentication();
         }
+        return $this->_frameset->getAuthentication();
+    }
 
-        /**
-         *    Accessor for raw header information.
-         *    @return string      Header block.
-         *    @access public
-         */
-        function getHeaders() {
-            if (is_integer($this->_focus)) {
-                return $this->_frames[$this->_focus]->getHeaders();
-            }
-            return $this->_frameset->getHeaders();
-        }
-
-        /**
-         *    Accessor for parsed title.
-         *    @return string     Title or false if no title is present.
-         *    @access public
-         */
-        function getTitle() {
-            return $this->_frameset->getTitle();
-        }
-
-        /**
-         *    Accessor for a list of all fixed links.
-         *    @return array   List of urls with scheme of
-         *                    http or https and hostname.
-         *    @access public
-         */
-        function getAbsoluteUrls() {
-            if (is_integer($this->_focus)) {
-                return $this->_frames[$this->_focus]->getAbsoluteUrls();
-            }
-            $urls = array();
-            foreach ($this->_frames as $frame) {
-                $urls = array_merge($urls, $frame->getAbsoluteUrls());
-            }
-            return array_values(array_unique($urls));
+    /**
+     *    Accessor for last Authentication realm. Only valid
+     *    straight after a challenge (401).
+     *    @return string    Name of security realm.
+     *    @access public
+     */
+    function getRealm() {
+        if (is_integer($this->_focus)) {
+            return $this->_frames[$this->_focus]->getRealm();
         }
+        return $this->_frameset->getRealm();
+    }
 
-        /**
-         *    Accessor for a list of all relative links.
-         *    @return array      List of urls without hostname.
-         *    @access public
-         */
-        function getRelativeUrls() {
-            if (is_integer($this->_focus)) {
-                return $this->_frames[$this->_focus]->getRelativeUrls();
-            }
-            $urls = array();
-            foreach ($this->_frames as $frame) {
-                $urls = array_merge($urls, $frame->getRelativeUrls());
-            }
-            return array_values(array_unique($urls));
-        }
-
-        /**
-         *    Accessor for URLs by the link label. Label will match
-         *    regardess of whitespace issues and case.
-         *    @param string $label    Text of link.
-         *    @return array           List of links with that label.
-         *    @access public
-         */
-        function getUrlsByLabel($label) {
-            if (is_integer($this->_focus)) {
-                return $this->_tagUrlsWithFrame(
-                        $this->_frames[$this->_focus]->getUrlsByLabel($label),
-                        $this->_focus);
-            }
-            $urls = array();
-            foreach ($this->_frames as $index => $frame) {
-                $urls = array_merge(
-                        $urls,
-                        $this->_tagUrlsWithFrame(
-                                    $frame->getUrlsByLabel($label),
-                                    $index));
-            }
-            return $urls;
-        }
-
-        /**
-         *    Accessor for a URL by the id attribute. If in a frameset
-         *    then the first link found with that ID attribute is
-         *    returned only. Focus on a frame if you want one from
-         *    a specific part of the frameset.
-         *    @param string $id       Id attribute of link.
-         *    @return string          URL with that id.
-         *    @access public
-         */
-        function getUrlById($id) {
-            foreach ($this->_frames as $index => $frame) {
-                if ($url = $frame->getUrlById($id)) {
-                    if (! $url->gettarget()) {
-                        $url->setTarget($this->_getPublicNameFromIndex($index));
-                    }
-                    return $url;
-                }
-            }
-            return false;
+    /**
+     *    Accessor for outgoing header information.
+     *    @return string      Header block.
+     *    @access public
+     */
+    function getRequest() {
+        if (is_integer($this->_focus)) {
+            return $this->_frames[$this->_focus]->getRequest();
+        }
+        return $this->_frameset->getRequest();
+    }
+
+    /**
+     *    Accessor for raw header information.
+     *    @return string      Header block.
+     *    @access public
+     */
+    function getHeaders() {
+        if (is_integer($this->_focus)) {
+            return $this->_frames[$this->_focus]->getHeaders();
+        }
+        return $this->_frameset->getHeaders();
+    }
+
+    /**
+     *    Accessor for parsed title.
+     *    @return string     Title or false if no title is present.
+     *    @access public
+     */
+    function getTitle() {
+        return $this->_frameset->getTitle();
+    }
+
+    /**
+     *    Accessor for a list of all fixed links.
+     *    @return array   List of urls as strings.
+     *    @access public
+     */
+    function getUrls() {
+        if (is_integer($this->_focus)) {
+            return $this->_frames[$this->_focus]->getUrls();
+        }
+        $urls = array();
+        foreach ($this->_frames as $frame) {
+            $urls = array_merge($urls, $frame->getUrls());
         }
+        return array_values(array_unique($urls));
+    }
 
-        /**
-         *    Attaches the intended frame index to a list of URLs.
-         *    @param array $urls        List of SimpleUrls.
-         *    @param string $frame      Name of frame or index.
-         *    @return array             List of tagged URLs.
-         *    @access private
-         */
-        function _tagUrlsWithFrame($urls, $frame) {
-            $tagged = array();
-            foreach ($urls as $url) {
-                if (! $url->getTarget()) {
-                    $url->setTarget($this->_getPublicNameFromIndex($frame));
+    /**
+     *    Accessor for URLs by the link label. Label will match
+     *    regardess of whitespace issues and case.
+     *    @param string $label    Text of link.
+     *    @return array           List of links with that label.
+     *    @access public
+     */
+    function getUrlsByLabel($label) {
+        if (is_integer($this->_focus)) {
+            return $this->_tagUrlsWithFrame(
+                    $this->_frames[$this->_focus]->getUrlsByLabel($label),
+                    $this->_focus);
+        }
+        $urls = array();
+        foreach ($this->_frames as $index => $frame) {
+            $urls = array_merge(
+                    $urls,
+                    $this->_tagUrlsWithFrame(
+                                $frame->getUrlsByLabel($label),
+                                $index));
+        }
+        return $urls;
+    }
+
+    /**
+     *    Accessor for a URL by the id attribute. If in a frameset
+     *    then the first link found with that ID attribute is
+     *    returned only. Focus on a frame if you want one from
+     *    a specific part of the frameset.
+     *    @param string $id       Id attribute of link.
+     *    @return string          URL with that id.
+     *    @access public
+     */
+    function getUrlById($id) {
+        foreach ($this->_frames as $index => $frame) {
+            if ($url = $frame->getUrlById($id)) {
+                if (! $url->gettarget()) {
+                    $url->setTarget($this->_getPublicNameFromIndex($index));
                 }
-                $tagged[] = $url;
+                return $url;
             }
-            return $tagged;
-        }
-
-        /**
-         *    Finds a held form by button label. Will only
-         *    search correctly built forms.
-         *    @param SimpleSelector $selector       Button finder.
-         *    @return SimpleForm                    Form object containing
-         *                                          the button.
-         *    @access public
-         */
-        function &getFormBySubmit($selector) {
-            $form = &$this->_findForm('getFormBySubmit', $selector);
-            return $form;
         }
+        return false;
+    }
 
-        /**
-         *    Finds a held form by image using a selector.
-         *    Will only search correctly built forms. The first
-         *    form found either within the focused frame, or
-         *    across frames, will be the one returned.
-         *    @param SimpleSelector $selector  Image finder.
-         *    @return SimpleForm               Form object containing
-         *                                     the image.
-         *    @access public
-         */
-        function &getFormByImage($selector) {
-            $form = &$this->_findForm('getFormByImage', $selector);
-            return $form;
+    /**
+     *    Attaches the intended frame index to a list of URLs.
+     *    @param array $urls        List of SimpleUrls.
+     *    @param string $frame      Name of frame or index.
+     *    @return array             List of tagged URLs.
+     *    @access private
+     */
+    function _tagUrlsWithFrame($urls, $frame) {
+        $tagged = array();
+        foreach ($urls as $url) {
+            if (! $url->getTarget()) {
+                $url->setTarget($this->_getPublicNameFromIndex($frame));
+            }
+            $tagged[] = $url;
         }
+        return $tagged;
+    }
+
+    /**
+     *    Finds a held form by button label. Will only
+     *    search correctly built forms.
+     *    @param SimpleSelector $selector       Button finder.
+     *    @return SimpleForm                    Form object containing
+     *                                          the button.
+     *    @access public
+     */
+    function &getFormBySubmit($selector) {
+        $form = &$this->_findForm('getFormBySubmit', $selector);
+        return $form;
+    }
+
+    /**
+     *    Finds a held form by image using a selector.
+     *    Will only search correctly built forms. The first
+     *    form found either within the focused frame, or
+     *    across frames, will be the one returned.
+     *    @param SimpleSelector $selector  Image finder.
+     *    @return SimpleForm               Form object containing
+     *                                     the image.
+     *    @access public
+     */
+    function &getFormByImage($selector) {
+        $form = &$this->_findForm('getFormByImage', $selector);
+        return $form;
+    }
 
-        /**
-         *    Finds a held form by the form ID. A way of
-         *    identifying a specific form when we have control
-         *    of the HTML code. The first form found
-         *    either within the focused frame, or across frames,
-         *    will be the one returned.
-         *    @param string $id     Form label.
-         *    @return SimpleForm    Form object containing the matching ID.
-         *    @access public
-         */
-        function &getFormById($id) {
-            $form = &$this->_findForm('getFormById', $id);
+    /**
+     *    Finds a held form by the form ID. A way of
+     *    identifying a specific form when we have control
+     *    of the HTML code. The first form found
+     *    either within the focused frame, or across frames,
+     *    will be the one returned.
+     *    @param string $id     Form label.
+     *    @return SimpleForm    Form object containing the matching ID.
+     *    @access public
+     */
+    function &getFormById($id) {
+        $form = &$this->_findForm('getFormById', $id);
+        return $form;
+    }
+
+    /**
+        *    General form finder. Will search all the frames or
+        *    just the one in focus.
+        *    @param string $method    Method to use to find in a page.
+        *    @param string $attribute Label, name or ID.
+        *    @return SimpleForm    Form object containing the matching ID.
+        *    @access private
+        */
+    function &_findForm($method, $attribute) {
+        if (is_integer($this->_focus)) {
+            $form = &$this->_findFormInFrame(
+                    $this->_frames[$this->_focus],
+                    $this->_focus,
+                    $method,
+                    $attribute);
             return $form;
         }
-
-        /**
-         *    General form finder. Will search all the frames or
-         *    just the one in focus.
-         *    @param string $method    Method to use to find in a page.
-         *    @param string $attribute Label, name or ID.
-         *    @return SimpleForm    Form object containing the matching ID.
-         *    @access private
-         */
-        function &_findForm($method, $attribute) {
-            if (is_integer($this->_focus)) {
-                $form = &$this->_findFormInFrame(
-                        $this->_frames[$this->_focus],
-                        $this->_focus,
-                        $method,
-                        $attribute);
+        for ($i = 0; $i < count($this->_frames); $i++) {
+            $form = &$this->_findFormInFrame(
+                    $this->_frames[$i],
+                    $i,
+                    $method,
+                    $attribute);
+            if ($form) {
                 return $form;
             }
-            for ($i = 0; $i < count($this->_frames); $i++) {
-                $form = &$this->_findFormInFrame(
-                        $this->_frames[$i],
-                        $i,
-                        $method,
-                        $attribute);
-                if ($form) {
-                    return $form;
-                }
-            }
-            $null = null;
-            return $null;
-        }
-
-        /**
-         *    Finds a form in a page using a form finding method. Will
-         *    also tag the form with the frame name it belongs in.
-         *    @param SimplePage $page  Page content of frame.
-         *    @param integer $index    Internal frame representation.
-         *    @param string $method    Method to use to find in a page.
-         *    @param string $attribute Label, name or ID.
-         *    @return SimpleForm       Form object containing the matching ID.
-         *    @access private
-         */
-        function &_findFormInFrame(&$page, $index, $method, $attribute) {
-            $form = &$this->_frames[$index]->$method($attribute);
-            if (isset($form)) {
-                $form->setDefaultTarget($this->_getPublicNameFromIndex($index));
-            }
-            return $form;
         }
+        $null = null;
+        return $null;
+    }
 
-        /**
-         *    Sets a field on each form in which the field is
-         *    available.
-         *    @param SimpleSelector $selector    Field finder.
-         *    @param string $value               Value to set field to.
-         *    @return boolean                    True if value is valid.
-         *    @access public
-         */
-        function setField($selector, $value) {
-            if (is_integer($this->_focus)) {
-                $this->_frames[$this->_focus]->setField($selector, $value);
-            } else {
-                for ($i = 0; $i < count($this->_frames); $i++) {
-                    $this->_frames[$i]->setField($selector, $value);
-                }
-            }
+    /**
+     *    Finds a form in a page using a form finding method. Will
+     *    also tag the form with the frame name it belongs in.
+     *    @param SimplePage $page  Page content of frame.
+     *    @param integer $index    Internal frame representation.
+     *    @param string $method    Method to use to find in a page.
+     *    @param string $attribute Label, name or ID.
+     *    @return SimpleForm       Form object containing the matching ID.
+     *    @access private
+     */
+    function &_findFormInFrame(&$page, $index, $method, $attribute) {
+        $form = &$this->_frames[$index]->$method($attribute);
+        if (isset($form)) {
+            $form->setDefaultTarget($this->_getPublicNameFromIndex($index));
         }
+        return $form;
+    }
 
-        /**
-         *    Accessor for a form element value within a page.
-         *    @param SimpleSelector $selector    Field finder.
-         *    @return string/boolean             A string if the field is
-         *                                       present, false if unchecked
-         *                                       and null if missing.
-         *    @access public
-         */
-        function getField($selector) {
+    /**
+     *    Sets a field on each form in which the field is
+     *    available.
+     *    @param SimpleSelector $selector    Field finder.
+     *    @param string $value               Value to set field to.
+     *    @return boolean                    True if value is valid.
+     *    @access public
+     */
+    function setField($selector, $value) {
+        if (is_integer($this->_focus)) {
+            $this->_frames[$this->_focus]->setField($selector, $value);
+        } else {
             for ($i = 0; $i < count($this->_frames); $i++) {
-                $value = $this->_frames[$i]->getField($selector);
-                if (isset($value)) {
-                    return $value;
-                }
+                $this->_frames[$i]->setField($selector, $value);
+            }
+        }
+    }
+
+    /**
+     *    Accessor for a form element value within a page.
+     *    @param SimpleSelector $selector    Field finder.
+     *    @return string/boolean             A string if the field is
+     *                                       present, false if unchecked
+     *                                       and null if missing.
+     *    @access public
+     */
+    function getField($selector) {
+        for ($i = 0; $i < count($this->_frames); $i++) {
+            $value = $this->_frames[$i]->getField($selector);
+            if (isset($value)) {
+                return $value;
             }
-            return null;
         }
+        return null;
     }
+}
 ?>
\ No newline at end of file
index 120d74c276cb3f18c1070ee85d2e68255fe1b1f7..e309b0f5301f9d9702e73a3027ff202a845a5bae 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage WebTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ *  include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/socket.php');
+require_once(dirname(__FILE__) . '/cookies.php');
+require_once(dirname(__FILE__) . '/url.php');
+/**#@-*/
+
+/**
+ *    Creates HTTP headers for the end point of
+ *    a HTTP request.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleRoute {
+    var $_url;
+    
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     WebTester
-     * @version        $Id$
+     *    Sets the target URL.
+     *    @param SimpleUrl $url   URL as object.
+     *    @access public
      */
-
-    /**#@+
-     * include other SimpleTest class files
-     */
-    require_once(dirname(__FILE__) . '/socket.php');
-    require_once(dirname(__FILE__) . '/cookies.php');
-    require_once(dirname(__FILE__) . '/url.php');
-    /**#@-*/
-    
-    /**
-     *    Creates HTTP headers for the end point of
-     *    a HTTP request.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleRoute {
-        var $_url;
-        
-        /**
-         *    Sets the target URL.
-         *    @param SimpleUrl $url   URL as object.
-         *    @access public
-         */
-        function SimpleRoute($url) {
-            $this->_url = $url;
-        }
-        
-        /**
-         *    Resource name.
-         *    @return SimpleUrl        Current url.
-         *    @access protected
-         */
-        function getUrl() {
-            return $this->_url;
-        }
-        
-        /**
-         *    Creates the first line which is the actual request.
-         *    @param string $method   HTTP request method, usually GET.
-         *    @return string          Request line content.
-         *    @access protected
-         */
-        function _getRequestLine($method) {
-            return $method . ' ' . $this->_url->getPath() .
-                    $this->_url->getEncodedRequest() . ' HTTP/1.0';
-        }
-        
-        /**
-         *    Creates the host part of the request.
-         *    @return string          Host line content.
-         *    @access protected
-         */
-        function _getHostLine() {
-            $line = 'Host: ' . $this->_url->getHost();
-            if ($this->_url->getPort()) {
-                $line .= ':' . $this->_url->getPort();
-            }
-            return $line;
-        }
-        
-        /**
-         *    Opens a socket to the route.
-         *    @param string $method      HTTP request method, usually GET.
-         *    @param integer $timeout    Connection timeout.
-         *    @return SimpleSocket       New socket.
-         *    @access public
-         */
-        function &createConnection($method, $timeout) {
-            $default_port = ('https' == $this->_url->getScheme()) ? 443 : 80;
-            $socket = &$this->_createSocket(
-                    $this->_url->getScheme() ? $this->_url->getScheme() : 'http',
-                    $this->_url->getHost(),
-                    $this->_url->getPort() ? $this->_url->getPort() : $default_port,
-                    $timeout);
-            if (! $socket->isError()) {
-                $socket->write($this->_getRequestLine($method) . "\r\n");
-                $socket->write($this->_getHostLine() . "\r\n");
-                $socket->write("Connection: close\r\n");
-            }
-            return $socket;
-        }
-        
-        /**
-         *    Factory for socket.
-         *    @param string $scheme                   Protocol to use.
-         *    @param string $host                     Hostname to connect to.
-         *    @param integer $port                    Remote port.
-         *    @param integer $timeout                 Connection timeout.
-         *    @return SimpleSocket/SimpleSecureSocket New socket.
-         *    @access protected
-         */
-        function &_createSocket($scheme, $host, $port, $timeout) {
-            if (in_array($scheme, array('https'))) {
-                $socket = &new SimpleSecureSocket($host, $port, $timeout);
-            } else {
-                $socket = &new SimpleSocket($host, $port, $timeout);
-            }
-            return $socket;
-        }
+    function SimpleRoute($url) {
+        $this->_url = $url;
     }
     
     /**
-     *    Creates HTTP headers for the end point of
-     *    a HTTP request via a proxy server.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleProxyRoute extends SimpleRoute {
-        var $_proxy;
-        var $_username;
-        var $_password;
-        
-        /**
-         *    Stashes the proxy address.
-         *    @param SimpleUrl $url     URL as object.
-         *    @param string $proxy      Proxy URL.
-         *    @param string $username   Username for autentication.
-         *    @param string $password   Password for autentication.
-         *    @access public
-         */
-        function SimpleProxyRoute($url, $proxy, $username = false, $password = false) {
-            $this->SimpleRoute($url);
-            $this->_proxy = $proxy;
-            $this->_username = $username;
-            $this->_password = $password;
-        }
-        
-        /**
-         *    Creates the first line which is the actual request.
-         *    @param string $method   HTTP request method, usually GET.
-         *    @param SimpleUrl $url   URL as object.
-         *    @return string          Request line content.
-         *    @access protected
-         */
-        function _getRequestLine($method) {
-            $url = $this->getUrl();
-            $scheme = $url->getScheme() ? $url->getScheme() : 'http';
-            $port = $url->getPort() ? ':' . $url->getPort() : '';
-            return $method . ' ' . $scheme . '://' . $url->getHost() . $port .
-                    $url->getPath() . $url->getEncodedRequest() . ' HTTP/1.0';
-        }
-        
-        /**
-         *    Creates the host part of the request.
-         *    @param SimpleUrl $url   URL as object.
-         *    @return string          Host line content.
-         *    @access protected
-         */
-        function _getHostLine() {
-            $host = 'Host: ' . $this->_proxy->getHost();
-            $port = $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080;
-            return "$host:$port";
+     *    Resource name.
+     *    @return SimpleUrl        Current url.
+     *    @access protected
+     */
+    function getUrl() {
+        return $this->_url;
+    }
+    
+    /**
+     *    Creates the first line which is the actual request.
+     *    @param string $method   HTTP request method, usually GET.
+     *    @return string          Request line content.
+     *    @access protected
+     */
+    function _getRequestLine($method) {
+        return $method . ' ' . $this->_url->getPath() .
+                $this->_url->getEncodedRequest() . ' HTTP/1.0';
+    }
+    
+    /**
+     *    Creates the host part of the request.
+     *    @return string          Host line content.
+     *    @access protected
+     */
+    function _getHostLine() {
+        $line = 'Host: ' . $this->_url->getHost();
+        if ($this->_url->getPort()) {
+            $line .= ':' . $this->_url->getPort();
         }
-        
-        /**
-         *    Opens a socket to the route.
-         *    @param string $method       HTTP request method, usually GET.
-         *    @param integer $timeout     Connection timeout.
-         *    @return SimpleSocket        New socket.
-         *    @access public
-         */
-        function &createConnection($method, $timeout) {
-            $socket = &$this->_createSocket(
-                    $this->_proxy->getScheme() ? $this->_proxy->getScheme() : 'http',
-                    $this->_proxy->getHost(),
-                    $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080,
-                    $timeout);
-            if ($socket->isError()) {
-                return $socket;
-            }
+        return $line;
+    }
+    
+    /**
+     *    Opens a socket to the route.
+     *    @param string $method      HTTP request method, usually GET.
+     *    @param integer $timeout    Connection timeout.
+     *    @return SimpleSocket       New socket.
+     *    @access public
+     */
+    function &createConnection($method, $timeout) {
+        $default_port = ('https' == $this->_url->getScheme()) ? 443 : 80;
+        $socket = &$this->_createSocket(
+                $this->_url->getScheme() ? $this->_url->getScheme() : 'http',
+                $this->_url->getHost(),
+                $this->_url->getPort() ? $this->_url->getPort() : $default_port,
+                $timeout);
+        if (! $socket->isError()) {
             $socket->write($this->_getRequestLine($method) . "\r\n");
             $socket->write($this->_getHostLine() . "\r\n");
-            if ($this->_username && $this->_password) {
-                $socket->write('Proxy-Authorization: Basic ' .
-                        base64_encode($this->_username . ':' . $this->_password) .
-                        "\r\n");
-            }
             $socket->write("Connection: close\r\n");
-            return $socket;
         }
+        return $socket;
     }
-
+    
     /**
-     *    HTTP request for a web page. Factory for
-     *    HttpResponse object.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleHttpRequest {
-        var $_route;
-        var $_encoding;
-        var $_headers;
-        var $_cookies;
-        
-        /**
-         *    Builds the socket request from the different pieces.
-         *    These include proxy information, URL, cookies, headers,
-         *    request method and choice of encoding.
-         *    @param SimpleRoute $route              Request route.
-         *    @param SimpleFormEncoding $encoding    Content to send with
-         *                                           request.
-         *    @access public
-         */
-        function SimpleHttpRequest(&$route, $encoding) {
-            $this->_route = &$route;
-            $this->_encoding = $encoding;
-            $this->_headers = array();
-            $this->_cookies = array();
-        }
-        
-        /**
-         *    Dispatches the content to the route's socket.
-         *    @param integer $timeout      Connection timeout.
-         *    @return SimpleHttpResponse   A response which may only have
-         *                                 an error, but hopefully has a
-         *                                 complete web page.
-         *    @access public
-         */
-        function &fetch($timeout) {
-            $socket = &$this->_route->createConnection($this->_encoding->getMethod(), $timeout);
-            if (! $socket->isError()) {
-                $this->_dispatchRequest($socket, $this->_encoding);
-            }
-            $response = &$this->_createResponse($socket);
-            return $response;
-        }
-        
-        /**
-         *    Sends the headers.
-         *    @param SimpleSocket $socket           Open socket.
-         *    @param string $method                 HTTP request method,
-         *                                          usually GET.
-         *    @param SimpleFormEncoding $encoding   Content to send with request.
-         *    @access private
-         */
-        function _dispatchRequest(&$socket, $encoding) {
-            foreach ($this->_headers as $header_line) {
-                $socket->write($header_line . "\r\n");
-            }
-            if (count($this->_cookies) > 0) {
-                $socket->write("Cookie: " . implode(";", $this->_cookies) . "\r\n");
-            }
-            $encoding->writeHeadersTo($socket);
-            $socket->write("\r\n");
-            $encoding->writeTo($socket);
-        }
-        
-        /**
-         *    Adds a header line to the request.
-         *    @param string $header_line    Text of full header line.
-         *    @access public
-         */
-        function addHeaderLine($header_line) {
-            $this->_headers[] = $header_line;
+     *    Factory for socket.
+     *    @param string $scheme                   Protocol to use.
+     *    @param string $host                     Hostname to connect to.
+     *    @param integer $port                    Remote port.
+     *    @param integer $timeout                 Connection timeout.
+     *    @return SimpleSocket/SimpleSecureSocket New socket.
+     *    @access protected
+     */
+    function &_createSocket($scheme, $host, $port, $timeout) {
+        if (in_array($scheme, array('https'))) {
+            $socket = &new SimpleSecureSocket($host, $port, $timeout);
+        } else {
+            $socket = &new SimpleSocket($host, $port, $timeout);
         }
-        
-        /**
-         *    Reads all the relevant cookies from the
-         *    cookie jar.
-         *    @param SimpleCookieJar $jar     Jar to read
-         *    @param SimpleUrl $url           Url to use for scope.
-         *    @access public
-         */
-        function readCookiesFromJar($jar, $url) {
-            $this->_cookies = $jar->selectAsPairs($url);
+        return $socket;
+    }
+}
+
+/**
+ *    Creates HTTP headers for the end point of
+ *    a HTTP request via a proxy server.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleProxyRoute extends SimpleRoute {
+    var $_proxy;
+    var $_username;
+    var $_password;
+    
+    /**
+     *    Stashes the proxy address.
+     *    @param SimpleUrl $url     URL as object.
+     *    @param string $proxy      Proxy URL.
+     *    @param string $username   Username for autentication.
+     *    @param string $password   Password for autentication.
+     *    @access public
+     */
+    function SimpleProxyRoute($url, $proxy, $username = false, $password = false) {
+        $this->SimpleRoute($url);
+        $this->_proxy = $proxy;
+        $this->_username = $username;
+        $this->_password = $password;
+    }
+    
+    /**
+     *    Creates the first line which is the actual request.
+     *    @param string $method   HTTP request method, usually GET.
+     *    @param SimpleUrl $url   URL as object.
+     *    @return string          Request line content.
+     *    @access protected
+     */
+    function _getRequestLine($method) {
+        $url = $this->getUrl();
+        $scheme = $url->getScheme() ? $url->getScheme() : 'http';
+        $port = $url->getPort() ? ':' . $url->getPort() : '';
+        return $method . ' ' . $scheme . '://' . $url->getHost() . $port .
+                $url->getPath() . $url->getEncodedRequest() . ' HTTP/1.0';
+    }
+    
+    /**
+     *    Creates the host part of the request.
+     *    @param SimpleUrl $url   URL as object.
+     *    @return string          Host line content.
+     *    @access protected
+     */
+    function _getHostLine() {
+        $host = 'Host: ' . $this->_proxy->getHost();
+        $port = $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080;
+        return "$host:$port";
+    }
+    
+    /**
+     *    Opens a socket to the route.
+     *    @param string $method       HTTP request method, usually GET.
+     *    @param integer $timeout     Connection timeout.
+     *    @return SimpleSocket        New socket.
+     *    @access public
+     */
+    function &createConnection($method, $timeout) {
+        $socket = &$this->_createSocket(
+                $this->_proxy->getScheme() ? $this->_proxy->getScheme() : 'http',
+                $this->_proxy->getHost(),
+                $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080,
+                $timeout);
+        if ($socket->isError()) {
+            return $socket;
         }
-        
-        /**
-         *    Wraps the socket in a response parser.
-         *    @param SimpleSocket $socket   Responding socket.
-         *    @return SimpleHttpResponse    Parsed response object.
-         *    @access protected
-         */
-        function &_createResponse(&$socket) {
-            $response = &new SimpleHttpResponse(
-                    $socket,
-                    $this->_route->getUrl(),
-                    $this->_encoding);
-            return $response;
+        $socket->write($this->_getRequestLine($method) . "\r\n");
+        $socket->write($this->_getHostLine() . "\r\n");
+        if ($this->_username && $this->_password) {
+            $socket->write('Proxy-Authorization: Basic ' .
+                    base64_encode($this->_username . ':' . $this->_password) .
+                    "\r\n");
         }
+        $socket->write("Connection: close\r\n");
+        return $socket;
     }
+}
+
+/**
+ *    HTTP request for a web page. Factory for
+ *    HttpResponse object.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleHttpRequest {
+    var $_route;
+    var $_encoding;
+    var $_headers;
+    var $_cookies;
     
     /**
-     *    Collection of header lines in the response.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleHttpHeaders {
-        var $_raw_headers;
-        var $_response_code;
-        var $_http_version;
-        var $_mime_type;
-        var $_location;
-        var $_cookies;
-        var $_authentication;
-        var $_realm;
-        
-        /**
-         *    Parses the incoming header block.
-         *    @param string $headers     Header block.
-         *    @access public
-         */
-        function SimpleHttpHeaders($headers) {
-            $this->_raw_headers = $headers;
-            $this->_response_code = false;
-            $this->_http_version = false;
-            $this->_mime_type = '';
-            $this->_location = false;
-            $this->_cookies = array();
-            $this->_authentication = false;
-            $this->_realm = false;
-            foreach (split("\r\n", $headers) as $header_line) {
-                $this->_parseHeaderLine($header_line);
-            }
+     *    Builds the socket request from the different pieces.
+     *    These include proxy information, URL, cookies, headers,
+     *    request method and choice of encoding.
+     *    @param SimpleRoute $route              Request route.
+     *    @param SimpleFormEncoding $encoding    Content to send with
+     *                                           request.
+     *    @access public
+     */
+    function SimpleHttpRequest(&$route, $encoding) {
+        $this->_route = &$route;
+        $this->_encoding = $encoding;
+        $this->_headers = array();
+        $this->_cookies = array();
+    }
+    
+    /**
+     *    Dispatches the content to the route's socket.
+     *    @param integer $timeout      Connection timeout.
+     *    @return SimpleHttpResponse   A response which may only have
+     *                                 an error, but hopefully has a
+     *                                 complete web page.
+     *    @access public
+     */
+    function &fetch($timeout) {
+        $socket = &$this->_route->createConnection($this->_encoding->getMethod(), $timeout);
+        if (! $socket->isError()) {
+            $this->_dispatchRequest($socket, $this->_encoding);
         }
-        
-        /**
-         *    Accessor for parsed HTTP protocol version.
-         *    @return integer           HTTP error code.
-         *    @access public
-         */
-        function getHttpVersion() {
-            return $this->_http_version;
+        $response = &$this->_createResponse($socket);
+        return $response;
+    }
+    
+    /**
+     *    Sends the headers.
+     *    @param SimpleSocket $socket           Open socket.
+     *    @param string $method                 HTTP request method,
+     *                                          usually GET.
+     *    @param SimpleFormEncoding $encoding   Content to send with request.
+     *    @access private
+     */
+    function _dispatchRequest(&$socket, $encoding) {
+        foreach ($this->_headers as $header_line) {
+            $socket->write($header_line . "\r\n");
         }
-        
-        /**
-         *    Accessor for raw header block.
-         *    @return string        All headers as raw string.
-         *    @access public
-         */
-        function getRaw() {
-            return $this->_raw_headers;
+        if (count($this->_cookies) > 0) {
+            $socket->write("Cookie: " . implode(";", $this->_cookies) . "\r\n");
         }
-        
-        /**
-         *    Accessor for parsed HTTP error code.
-         *    @return integer           HTTP error code.
-         *    @access public
-         */
-        function getResponseCode() {
-            return (integer)$this->_response_code;
+        $encoding->writeHeadersTo($socket);
+        $socket->write("\r\n");
+        $encoding->writeTo($socket);
+    }
+    
+    /**
+     *    Adds a header line to the request.
+     *    @param string $header_line    Text of full header line.
+     *    @access public
+     */
+    function addHeaderLine($header_line) {
+        $this->_headers[] = $header_line;
+    }
+    
+    /**
+     *    Reads all the relevant cookies from the
+     *    cookie jar.
+     *    @param SimpleCookieJar $jar     Jar to read
+     *    @param SimpleUrl $url           Url to use for scope.
+     *    @access public
+     */
+    function readCookiesFromJar($jar, $url) {
+        $this->_cookies = $jar->selectAsPairs($url);
+    }
+    
+    /**
+     *    Wraps the socket in a response parser.
+     *    @param SimpleSocket $socket   Responding socket.
+     *    @return SimpleHttpResponse    Parsed response object.
+     *    @access protected
+     */
+    function &_createResponse(&$socket) {
+        $response = &new SimpleHttpResponse(
+                $socket,
+                $this->_route->getUrl(),
+                $this->_encoding);
+        return $response;
+    }
+}
+
+/**
+ *    Collection of header lines in the response.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleHttpHeaders {
+    var $_raw_headers;
+    var $_response_code;
+    var $_http_version;
+    var $_mime_type;
+    var $_location;
+    var $_cookies;
+    var $_authentication;
+    var $_realm;
+    
+    /**
+     *    Parses the incoming header block.
+     *    @param string $headers     Header block.
+     *    @access public
+     */
+    function SimpleHttpHeaders($headers) {
+        $this->_raw_headers = $headers;
+        $this->_response_code = false;
+        $this->_http_version = false;
+        $this->_mime_type = '';
+        $this->_location = false;
+        $this->_cookies = array();
+        $this->_authentication = false;
+        $this->_realm = false;
+        foreach (split("\r\n", $headers) as $header_line) {
+            $this->_parseHeaderLine($header_line);
         }
-        
-        /**
-         *    Returns the redirected URL or false if
-         *    no redirection.
-         *    @return string      URL or false for none.
-         *    @access public
-         */
-        function getLocation() {
-            return $this->_location;
+    }
+    
+    /**
+     *    Accessor for parsed HTTP protocol version.
+     *    @return integer           HTTP error code.
+     *    @access public
+     */
+    function getHttpVersion() {
+        return $this->_http_version;
+    }
+    
+    /**
+     *    Accessor for raw header block.
+     *    @return string        All headers as raw string.
+     *    @access public
+     */
+    function getRaw() {
+        return $this->_raw_headers;
+    }
+    
+    /**
+     *    Accessor for parsed HTTP error code.
+     *    @return integer           HTTP error code.
+     *    @access public
+     */
+    function getResponseCode() {
+        return (integer)$this->_response_code;
+    }
+    
+    /**
+     *    Returns the redirected URL or false if
+     *    no redirection.
+     *    @return string      URL or false for none.
+     *    @access public
+     */
+    function getLocation() {
+        return $this->_location;
+    }
+    
+    /**
+     *    Test to see if the response is a valid redirect.
+     *    @return boolean       True if valid redirect.
+     *    @access public
+     */
+    function isRedirect() {
+        return in_array($this->_response_code, array(301, 302, 303, 307)) &&
+                (boolean)$this->getLocation();
+    }
+    
+    /**
+     *    Test to see if the response is an authentication
+     *    challenge.
+     *    @return boolean       True if challenge.
+     *    @access public
+     */
+    function isChallenge() {
+        return ($this->_response_code == 401) &&
+                (boolean)$this->_authentication &&
+                (boolean)$this->_realm;
+    }
+    
+    /**
+     *    Accessor for MIME type header information.
+     *    @return string           MIME type.
+     *    @access public
+     */
+    function getMimeType() {
+        return $this->_mime_type;
+    }
+    
+    /**
+     *    Accessor for authentication type.
+     *    @return string        Type.
+     *    @access public
+     */
+    function getAuthentication() {
+        return $this->_authentication;
+    }
+    
+    /**
+     *    Accessor for security realm.
+     *    @return string        Realm.
+     *    @access public
+     */
+    function getRealm() {
+        return $this->_realm;
+    }
+    
+    /**
+     *    Writes new cookies to the cookie jar.
+     *    @param SimpleCookieJar $jar   Jar to write to.
+     *    @param SimpleUrl $url         Host and path to write under.
+     *    @access public
+     */
+    function writeCookiesToJar(&$jar, $url) {
+        foreach ($this->_cookies as $cookie) {
+            $jar->setCookie(
+                    $cookie->getName(),
+                    $cookie->getValue(),
+                    $url->getHost(),
+                    $cookie->getPath(),
+                    $cookie->getExpiry());
         }
-        
-        /**
-         *    Test to see if the response is a valid redirect.
-         *    @return boolean       True if valid redirect.
-         *    @access public
-         */
-        function isRedirect() {
-            return in_array($this->_response_code, array(301, 302, 303, 307)) &&
-                    (boolean)$this->getLocation();
+    }
+
+    /**
+     *    Called on each header line to accumulate the held
+     *    data within the class.
+     *    @param string $header_line        One line of header.
+     *    @access protected
+     */
+    function _parseHeaderLine($header_line) {
+        if (preg_match('/HTTP\/(\d+\.\d+)\s+(\d+)/i', $header_line, $matches)) {
+            $this->_http_version = $matches[1];
+            $this->_response_code = $matches[2];
         }
-        
-        /**
-         *    Test to see if the response is an authentication
-         *    challenge.
-         *    @return boolean       True if challenge.
-         *    @access public
-         */
-        function isChallenge() {
-            return ($this->_response_code == 401) &&
-                    (boolean)$this->_authentication &&
-                    (boolean)$this->_realm;
+        if (preg_match('/Content-type:\s*(.*)/i', $header_line, $matches)) {
+            $this->_mime_type = trim($matches[1]);
         }
-        
-        /**
-         *    Accessor for MIME type header information.
-         *    @return string           MIME type.
-         *    @access public
-         */
-        function getMimeType() {
-            return $this->_mime_type;
+        if (preg_match('/Location:\s*(.*)/i', $header_line, $matches)) {
+            $this->_location = trim($matches[1]);
         }
-        
-        /**
-         *    Accessor for authentication type.
-         *    @return string        Type.
-         *    @access public
-         */
-        function getAuthentication() {
-            return $this->_authentication;
+        if (preg_match('/Set-cookie:(.*)/i', $header_line, $matches)) {
+            $this->_cookies[] = $this->_parseCookie($matches[1]);
         }
-        
-        /**
-         *    Accessor for security realm.
-         *    @return string        Realm.
-         *    @access public
-         */
-        function getRealm() {
-            return $this->_realm;
+        if (preg_match('/WWW-Authenticate:\s+(\S+)\s+realm=\"(.*?)\"/i', $header_line, $matches)) {
+            $this->_authentication = $matches[1];
+            $this->_realm = trim($matches[2]);
         }
-        
-        /**
-         *    Writes new cookies to the cookie jar.
-         *    @param SimpleCookieJar $jar   Jar to write to.
-         *    @param SimpleUrl $url         Host and path to write under.
-         *    @access public
-         */
-        function writeCookiesToJar(&$jar, $url) {
-            foreach ($this->_cookies as $cookie) {
-                $jar->setCookie(
-                        $cookie->getName(),
-                        $cookie->getValue(),
-                        $url->getHost(),
-                        $cookie->getPath(),
-                        $cookie->getExpiry());
+    }
+    
+    /**
+     *    Parse the Set-cookie content.
+     *    @param string $cookie_line    Text after "Set-cookie:"
+     *    @return SimpleCookie          New cookie object.
+     *    @access private
+     */
+    function _parseCookie($cookie_line) {
+        $parts = split(";", $cookie_line);
+        $cookie = array();
+        preg_match('/\s*(.*?)\s*=(.*)/', array_shift($parts), $cookie);
+        foreach ($parts as $part) {
+            if (preg_match('/\s*(.*?)\s*=(.*)/', $part, $matches)) {
+                $cookie[$matches[1]] = trim($matches[2]);
             }
         }
+        return new SimpleCookie(
+                $cookie[1],
+                trim($cookie[2]),
+                isset($cookie["path"]) ? $cookie["path"] : "",
+                isset($cookie["expires"]) ? $cookie["expires"] : false);
+    }
+}
 
-        /**
-         *    Called on each header line to accumulate the held
-         *    data within the class.
-         *    @param string $header_line        One line of header.
-         *    @access protected
-         */
-        function _parseHeaderLine($header_line) {
-            if (preg_match('/HTTP\/(\d+\.\d+)\s+(\d+)/i', $header_line, $matches)) {
-                $this->_http_version = $matches[1];
-                $this->_response_code = $matches[2];
-            }
-            if (preg_match('/Content-type:\s*(.*)/i', $header_line, $matches)) {
-                $this->_mime_type = trim($matches[1]);
-            }
-            if (preg_match('/Location:\s*(.*)/i', $header_line, $matches)) {
-                $this->_location = trim($matches[1]);
-            }
-            if (preg_match('/Set-cookie:(.*)/i', $header_line, $matches)) {
-                $this->_cookies[] = $this->_parseCookie($matches[1]);
-            }
-            if (preg_match('/WWW-Authenticate:\s+(\S+)\s+realm=\"(.*?)\"/i', $header_line, $matches)) {
-                $this->_authentication = $matches[1];
-                $this->_realm = trim($matches[2]);
-            }
-        }
-        
-        /**
-         *    Parse the Set-cookie content.
-         *    @param string $cookie_line    Text after "Set-cookie:"
-         *    @return SimpleCookie          New cookie object.
-         *    @access private
-         */
-        function _parseCookie($cookie_line) {
-            $parts = split(";", $cookie_line);
-            $cookie = array();
-            preg_match('/\s*(.*?)\s*=(.*)/', array_shift($parts), $cookie);
-            foreach ($parts as $part) {
-                if (preg_match('/\s*(.*?)\s*=(.*)/', $part, $matches)) {
-                    $cookie[$matches[1]] = trim($matches[2]);
-                }
-            }
-            return new SimpleCookie(
-                    $cookie[1],
-                    trim($cookie[2]),
-                    isset($cookie["path"]) ? $cookie["path"] : "",
-                    isset($cookie["expires"]) ? $cookie["expires"] : false);
-        }
+/**
+ *    Basic HTTP response.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleHttpResponse extends SimpleStickyError {
+    var $_url;
+    var $_encoding;
+    var $_sent;
+    var $_content;
+    var $_headers;
+    
+    /**
+     *    Constructor. Reads and parses the incoming
+     *    content and headers.
+     *    @param SimpleSocket $socket   Network connection to fetch
+     *                                  response text from.
+     *    @param SimpleUrl $url         Resource name.
+     *    @param mixed $encoding        Record of content sent.
+     *    @access public
+     */
+    function SimpleHttpResponse(&$socket, $url, $encoding) {
+        $this->SimpleStickyError();
+        $this->_url = $url;
+        $this->_encoding = $encoding;
+        $this->_sent = $socket->getSent();
+        $this->_content = false;
+        $raw = $this->_readAll($socket);
+        if ($socket->isError()) {
+            $this->_setError('Error reading socket [' . $socket->getError() . ']');
+            return;
+        }
+        $this->_parse($raw);
     }
     
     /**
-     *    Basic HTTP response.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleHttpResponse extends SimpleStickyError {
-        var $_url;
-        var $_encoding;
-        var $_sent;
-        var $_content;
-        var $_headers;
-        
-        /**
-         *    Constructor. Reads and parses the incoming
-         *    content and headers.
-         *    @param SimpleSocket $socket   Network connection to fetch
-         *                                  response text from.
-         *    @param SimpleUrl $url         Resource name.
-         *    @param mixed $encoding        Record of content sent.
-         *    @access public
-         */
-        function SimpleHttpResponse(&$socket, $url, $encoding) {
-            $this->SimpleStickyError();
-            $this->_url = $url;
-            $this->_encoding = $encoding;
-            $this->_sent = $socket->getSent();
-            $this->_content = false;
-            $raw = $this->_readAll($socket);
-            if ($socket->isError()) {
-                $this->_setError('Error reading socket [' . $socket->getError() . ']');
-                return;
-            }
-            $this->_parse($raw);
-        }
-        
-        /**
-         *    Splits up the headers and the rest of the content.
-         *    @param string $raw    Content to parse.
-         *    @access private
-         */
-        function _parse($raw) {
-            if (! $raw) {
-                $this->_setError('Nothing fetched');
-                $this->_headers = &new SimpleHttpHeaders('');
-            } elseif (! strstr($raw, "\r\n\r\n")) {
-                $this->_setError('Could not split headers from content');
-                $this->_headers = &new SimpleHttpHeaders($raw);
-            } else {
-                list($headers, $this->_content) = split("\r\n\r\n", $raw, 2);
-                $this->_headers = &new SimpleHttpHeaders($headers);
-            }
-        }
-        
-        /**
-         *    Original request method.
-         *    @return string        GET, POST or HEAD.
-         *    @access public
-         */
-        function getMethod() {
-            return $this->_encoding->getMethod();
-        }
-        
-        /**
-         *    Resource name.
-         *    @return SimpleUrl        Current url.
-         *    @access public
-         */
-        function getUrl() {
-            return $this->_url;
-        }
-        
-        /**
-         *    Original request data.
-         *    @return mixed              Sent content.
-         *    @access public
-         */
-        function getRequestData() {
-            return $this->_encoding;
-        }
-        
-        /**
-         *    Raw request that was sent down the wire.
-         *    @return string        Bytes actually sent.
-         *    @access public
-         */
-        function getSent() {
-            return $this->_sent;
-        }
-        
-        /**
-         *    Accessor for the content after the last
-         *    header line.
-         *    @return string           All content.
-         *    @access public
-         */
-        function getContent() {
-            return $this->_content;
-        }
-        
-        /**
-         *    Accessor for header block. The response is the
-         *    combination of this and the content.
-         *    @return SimpleHeaders        Wrapped header block.
-         *    @access public
-         */
-        function getHeaders() {
-            return $this->_headers;
-        }
-        
-        /**
-         *    Accessor for any new cookies.
-         *    @return array       List of new cookies.
-         *    @access public
-         */
-        function getNewCookies() {
-            return $this->_headers->getNewCookies();
+     *    Splits up the headers and the rest of the content.
+     *    @param string $raw    Content to parse.
+     *    @access private
+     */
+    function _parse($raw) {
+        if (! $raw) {
+            $this->_setError('Nothing fetched');
+            $this->_headers = &new SimpleHttpHeaders('');
+        } elseif (! strstr($raw, "\r\n\r\n")) {
+            $this->_setError('Could not split headers from content');
+            $this->_headers = &new SimpleHttpHeaders($raw);
+        } else {
+            list($headers, $this->_content) = split("\r\n\r\n", $raw, 2);
+            $this->_headers = &new SimpleHttpHeaders($headers);
         }
-        
-        /**
-         *    Reads the whole of the socket output into a
-         *    single string.
-         *    @param SimpleSocket $socket  Unread socket.
-         *    @return string               Raw output if successful
-         *                                 else false.
-         *    @access private
-         */
-        function _readAll(&$socket) {
-            $all = '';
-            while (! $this->_isLastPacket($next = $socket->read())) {
-                $all .= $next;
-            }
-            return $all;
+    }
+    
+    /**
+     *    Original request method.
+     *    @return string        GET, POST or HEAD.
+     *    @access public
+     */
+    function getMethod() {
+        return $this->_encoding->getMethod();
+    }
+    
+    /**
+     *    Resource name.
+     *    @return SimpleUrl        Current url.
+     *    @access public
+     */
+    function getUrl() {
+        return $this->_url;
+    }
+    
+    /**
+     *    Original request data.
+     *    @return mixed              Sent content.
+     *    @access public
+     */
+    function getRequestData() {
+        return $this->_encoding;
+    }
+    
+    /**
+     *    Raw request that was sent down the wire.
+     *    @return string        Bytes actually sent.
+     *    @access public
+     */
+    function getSent() {
+        return $this->_sent;
+    }
+    
+    /**
+     *    Accessor for the content after the last
+     *    header line.
+     *    @return string           All content.
+     *    @access public
+     */
+    function getContent() {
+        return $this->_content;
+    }
+    
+    /**
+     *    Accessor for header block. The response is the
+     *    combination of this and the content.
+     *    @return SimpleHeaders        Wrapped header block.
+     *    @access public
+     */
+    function getHeaders() {
+        return $this->_headers;
+    }
+    
+    /**
+     *    Accessor for any new cookies.
+     *    @return array       List of new cookies.
+     *    @access public
+     */
+    function getNewCookies() {
+        return $this->_headers->getNewCookies();
+    }
+    
+    /**
+     *    Reads the whole of the socket output into a
+     *    single string.
+     *    @param SimpleSocket $socket  Unread socket.
+     *    @return string               Raw output if successful
+     *                                 else false.
+     *    @access private
+     */
+    function _readAll(&$socket) {
+        $all = '';
+        while (! $this->_isLastPacket($next = $socket->read())) {
+            $all .= $next;
         }
-        
-        /**
-         *    Test to see if the packet from the socket is the
-         *    last one.
-         *    @param string $packet    Chunk to interpret.
-         *    @return boolean          True if empty or EOF.
-         *    @access private
-         */
-        function _isLastPacket($packet) {
-            if (is_string($packet)) {
-                return $packet === '';
-            }
-            return ! $packet;
+        return $all;
+    }
+    
+    /**
+     *    Test to see if the packet from the socket is the
+     *    last one.
+     *    @param string $packet    Chunk to interpret.
+     *    @return boolean          True if empty or EOF.
+     *    @access private
+     */
+    function _isLastPacket($packet) {
+        if (is_string($packet)) {
+            return $packet === '';
         }
+        return ! $packet;
     }
+}
 ?>
\ No newline at end of file
index 22867faad404b06aa83710ee5a05f56a49c8e580..49b393c0fb7843413a20b90b1367e09aa717e710 100644 (file)
 <?php
+/**
+ *  Base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ * Includes SimpleTest files and defined the root constant
+ * for dependent libraries.
+ */
+require_once(dirname(__FILE__) . '/errors.php');
+require_once(dirname(__FILE__) . '/compatibility.php');
+require_once(dirname(__FILE__) . '/scorer.php');
+require_once(dirname(__FILE__) . '/expectation.php');
+require_once(dirname(__FILE__) . '/dumper.php');
+if (! defined('SIMPLE_TEST')) {
+    define('SIMPLE_TEST', dirname(__FILE__) . '/');
+}
+/**#@-*/
+
+/**
+ *    This is called by the class runner to run a
+ *    single test method. Will also run the setUp()
+ *    and tearDown() methods.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class SimpleInvoker {
+    var $_test_case;
+
     /**
-     * Base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
+     *    Stashes the test case for later.
+     *    @param SimpleTestCase $test_case  Test case to run.
      */
+    function SimpleInvoker(&$test_case) {
+        $this->_test_case = &$test_case;
+    }
 
-    /**#@+
-     * Includes SimpleTest files and defined the root constant
-     * for dependent libraries.
+    /**
+     *    Accessor for test case being run.
+     *    @return SimpleTestCase    Test case.
+     *    @access public
      */
-    require_once(dirname(__FILE__) . '/errors.php');
-    require_once(dirname(__FILE__) . '/compatibility.php');
-    require_once(dirname(__FILE__) . '/scorer.php');
-    require_once(dirname(__FILE__) . '/expectation.php');
-    require_once(dirname(__FILE__) . '/dumper.php');
-    if (! defined('SIMPLE_TEST')) {
-        define('SIMPLE_TEST', dirname(__FILE__) . '/');
+    function &getTestCase() {
+        return $this->_test_case;
     }
-    /**#@-*/
 
     /**
-     *    This is called by the class runner to run a
-     *    single test method. Will also run the setUp()
-     *    and tearDown() methods.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Runs test level set up. Used for changing
+     *    the mechanics of base test cases.
+     *    @param string $method    Test method to call.
+     *    @access public
      */
-    class SimpleInvoker {
-        var $_test_case;
-
-        /**
-         *    Stashes the test case for later.
-         *    @param SimpleTestCase $test_case  Test case to run.
-         */
-        function SimpleInvoker(&$test_case) {
-            $this->_test_case = &$test_case;
-        }
+    function before($method) {
+        $this->_test_case->before($method);
+    }
 
-        /**
-         *    Accessor for test case being run.
-         *    @return SimpleTestCase    Test case.
-         *    @access public
-         */
-        function &getTestCase() {
-            return $this->_test_case;
-        }
+    /**
+     *    Invokes a test method and buffered with setUp()
+     *    and tearDown() calls.
+     *    @param string $method    Test method to call.
+     *    @access public
+     */
+    function invoke($method) {
+        $this->_test_case->setUp();
+        $this->_test_case->$method();
+        $this->_test_case->tearDown();
+    }
 
-        /**
-         *    Runs test level set up. Used for changing
-         *    the mechanics of base test cases.
-         *    @param string $method    Test method to call.
-         *    @access public
-         */
-        function before($method) {
-            $this->_test_case->before($method);
-        }
+    /**
+     *    Runs test level clean up. Used for changing
+     *    the mechanics of base test cases.
+     *    @param string $method    Test method to call.
+     *    @access public
+     */
+    function after($method) {
+        $this->_test_case->after($method);
+    }
+}
 
-        /**
-         *    Invokes a test method and buffered with setUp()
-         *    and tearDown() calls.
-         *    @param string $method    Test method to call.
-         *    @access public
-         */
-        function invoke($method) {
-            $this->_test_case->setUp();
-            $this->_test_case->$method();
-            $this->_test_case->tearDown();
-        }
+/**
+ *    Do nothing decorator. Just passes the invocation
+ *    straight through.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class SimpleInvokerDecorator {
+    var $_invoker;
 
-        /**
-         *    Runs test level clean up. Used for changing
-         *    the mechanics of base test cases.
-         *    @param string $method    Test method to call.
-         *    @access public
-         */
-        function after($method) {
-            $this->_test_case->after($method);
-        }
+    /**
+     *    Stores the invoker to wrap.
+     *    @param SimpleInvoker $invoker  Test method runner.
+     */
+    function SimpleInvokerDecorator(&$invoker) {
+        $this->_invoker = &$invoker;
     }
 
     /**
-     *    Do nothing decorator. Just passes the invocation
-     *    straight through.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Accessor for test case being run.
+     *    @return SimpleTestCase    Test case.
+     *    @access public
      */
-    class SimpleInvokerDecorator {
-        var $_invoker;
-
-        /**
-         *    Stores the invoker to wrap.
-         *    @param SimpleInvoker $invoker  Test method runner.
-         */
-        function SimpleInvokerDecorator(&$invoker) {
-            $this->_invoker = &$invoker;
-        }
-
-        /**
-         *    Accessor for test case being run.
-         *    @return SimpleTestCase    Test case.
-         *    @access public
-         */
-        function &getTestCase() {
-            return $this->_invoker->getTestCase();
-        }
+    function &getTestCase() {
+        return $this->_invoker->getTestCase();
+    }
 
-        /**
-         *    Runs test level set up. Used for changing
-         *    the mechanics of base test cases.
-         *    @param string $method    Test method to call.
-         *    @access public
-         */
-        function before($method) {
-            $this->_invoker->before($method);
-        }
+    /**
+     *    Runs test level set up. Used for changing
+     *    the mechanics of base test cases.
+     *    @param string $method    Test method to call.
+     *    @access public
+     */
+    function before($method) {
+        $this->_invoker->before($method);
+    }
 
-        /**
-         *    Invokes a test method and buffered with setUp()
-         *    and tearDown() calls.
-         *    @param string $method    Test method to call.
-         *    @access public
-         */
-        function invoke($method) {
-            $this->_invoker->invoke($method);
-        }
+    /**
+     *    Invokes a test method and buffered with setUp()
+     *    and tearDown() calls.
+     *    @param string $method    Test method to call.
+     *    @access public
+     */
+    function invoke($method) {
+        $this->_invoker->invoke($method);
+    }
 
-        /**
-         *    Runs test level clean up. Used for changing
-         *    the mechanics of base test cases.
-         *    @param string $method    Test method to call.
-         *    @access public
-         */
-        function after($method) {
-            $this->_invoker->after($method);
-        }
+    /**
+     *    Runs test level clean up. Used for changing
+     *    the mechanics of base test cases.
+     *    @param string $method    Test method to call.
+     *    @access public
+     */
+    function after($method) {
+        $this->_invoker->after($method);
     }
-?>
+}
+?>
\ No newline at end of file
index 047685fe362a332c07d0a62a00bcc5d553f5788c..e007ae14f4ef9ad59794e3ab537d8732b1a10614 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage MockObjects
+ *  @version    $Id$
+ */
+
+/**#@+
+ * include SimpleTest files
+ */
+require_once(dirname(__FILE__) . '/expectation.php');
+require_once(dirname(__FILE__) . '/simpletest.php');
+require_once(dirname(__FILE__) . '/dumper.php');
+if (version_compare(phpversion(), '5') >= 0) {
+    require_once(dirname(__FILE__) . '/reflection_php5.php');
+} else {
+    require_once(dirname(__FILE__) . '/reflection_php4.php');
+}
+/**#@-*/
+
+/**
+ * Default character simpletest will substitute for any value
+ */
+if (! defined('MOCK_ANYTHING')) {
+    define('MOCK_ANYTHING', '*');
+}
+
+/**
+ *    Parameter comparison assertion.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class ParametersExpectation extends SimpleExpectation {
+    var $_expected;
+
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     MockObjects
-     * @version        $Id$
+     *    Sets the expected parameter list.
+     *    @param array $parameters  Array of parameters including
+     *                              those that are wildcarded.
+     *                              If the value is not an array
+     *                              then it is considered to match any.
+     *    @param string $message    Customised message on failure.
+     *    @access public
      */
+    function ParametersExpectation($expected = false, $message = '%s') {
+        $this->SimpleExpectation($message);
+        $this->_expected = $expected;
+    }
 
-    /**#@+
-     * include SimpleTest files
+    /**
+     *    Tests the assertion. True if correct.
+     *    @param array $parameters     Comparison values.
+     *    @return boolean              True if correct.
+     *    @access public
      */
-    require_once(dirname(__FILE__) . '/expectation.php');
-    require_once(dirname(__FILE__) . '/simpletest.php');
-    require_once(dirname(__FILE__) . '/dumper.php');
-    if (version_compare(phpversion(), '5') >= 0) {
-        require_once(dirname(__FILE__) . '/reflection_php5.php');
-    } else {
-        require_once(dirname(__FILE__) . '/reflection_php4.php');
+    function test($parameters) {
+        if (! is_array($this->_expected)) {
+            return true;
+        }
+        if (count($this->_expected) != count($parameters)) {
+            return false;
+        }
+        for ($i = 0; $i < count($this->_expected); $i++) {
+            if (! $this->_testParameter($parameters[$i], $this->_expected[$i])) {
+                return false;
+            }
+        }
+        return true;
     }
-    /**#@-*/
 
     /**
-     * Default character simpletest will substitute for any value
+     *    Tests an individual parameter.
+     *    @param mixed $parameter    Value to test.
+     *    @param mixed $expected     Comparison value.
+     *    @return boolean            True if expectation
+     *                               fulfilled.
+     *    @access private
      */
-    if (! defined('MOCK_ANYTHING')) {
-        define('MOCK_ANYTHING', '*');
+    function _testParameter($parameter, $expected) {
+        $comparison = $this->_coerceToExpectation($expected);
+        return $comparison->test($parameter);
     }
 
     /**
-     *    Parameter comparison assertion.
-        *    @package SimpleTest
-        *    @subpackage MockObjects
+     *    Returns a human readable test message.
+     *    @param array $comparison   Incoming parameter list.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
      */
-    class ParametersExpectation extends SimpleExpectation {
-        var $_expected;
-
-        /**
-         *    Sets the expected parameter list.
-         *    @param array $parameters  Array of parameters including
-         *                              those that are wildcarded.
-         *                              If the value is not an array
-         *                              then it is considered to match any.
-         *    @param string $message    Customised message on failure.
-         *    @access public
-         */
-        function ParametersExpectation($expected = false, $message = '%s') {
-            $this->SimpleExpectation($message);
-            $this->_expected = $expected;
+    function testMessage($parameters) {
+        if ($this->test($parameters)) {
+            return "Expectation of " . count($this->_expected) .
+                    " arguments of [" . $this->_renderArguments($this->_expected) .
+                    "] is correct";
+        } else {
+            return $this->_describeDifference($this->_expected, $parameters);
         }
+    }
 
-        /**
-         *    Tests the assertion. True if correct.
-         *    @param array $parameters     Comparison values.
-         *    @return boolean              True if correct.
-         *    @access public
-         */
-        function test($parameters) {
-            if (! is_array($this->_expected)) {
-                return true;
-            }
-            if (count($this->_expected) != count($parameters)) {
-                return false;
-            }
-            for ($i = 0; $i < count($this->_expected); $i++) {
-                if (! $this->_testParameter($parameters[$i], $this->_expected[$i])) {
-                    return false;
-                }
+    /**
+     *    Message to display if expectation differs from
+     *    the parameters actually received.
+     *    @param array $expected      Expected parameters as list.
+     *    @param array $parameters    Actual parameters received.
+     *    @return string              Description of difference.
+     *    @access private
+     */
+    function _describeDifference($expected, $parameters) {
+        if (count($expected) != count($parameters)) {
+            return "Expected " . count($expected) .
+                    " arguments of [" . $this->_renderArguments($expected) .
+                    "] but got " . count($parameters) .
+                    " arguments of [" . $this->_renderArguments($parameters) . "]";
+        }
+        $messages = array();
+        for ($i = 0; $i < count($expected); $i++) {
+            $comparison = $this->_coerceToExpectation($expected[$i]);
+            if (! $comparison->test($parameters[$i])) {
+                $messages[] = "parameter " . ($i + 1) . " with [" .
+                        $comparison->overlayMessage($parameters[$i], $this->_getDumper()) . "]";
             }
-            return true;
         }
+        return "Parameter expectation differs at " . implode(" and ", $messages);
+    }
 
-        /**
-         *    Tests an individual parameter.
-         *    @param mixed $parameter    Value to test.
-         *    @param mixed $expected     Comparison value.
-         *    @return boolean            True if expectation
-         *                               fulfilled.
-         *    @access private
-         */
-        function _testParameter($parameter, $expected) {
-            $comparison = $this->_coerceToExpectation($expected);
-            return $comparison->test($parameter);
+    /**
+     *    Creates an identical expectation if the
+     *    object/value is not already some type
+     *    of expectation.
+     *    @param mixed $expected      Expected value.
+     *    @return SimpleExpectation   Expectation object.
+     *    @access private
+     */
+    function _coerceToExpectation($expected) {
+        if (SimpleExpectation::isExpectation($expected)) {
+            return $expected;
         }
+        return new IdenticalExpectation($expected);
+    }
 
-        /**
-         *    Returns a human readable test message.
-         *    @param array $comparison   Incoming parameter list.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($parameters) {
-            if ($this->test($parameters)) {
-                return "Expectation of " . count($this->_expected) .
-                        " arguments of [" . $this->_renderArguments($this->_expected) .
-                        "] is correct";
-            } else {
-                return $this->_describeDifference($this->_expected, $parameters);
+    /**
+     *    Renders the argument list as a string for
+     *    messages.
+     *    @param array $args    Incoming arguments.
+     *    @return string        Simple description of type and value.
+     *    @access private
+     */
+    function _renderArguments($args) {
+        $descriptions = array();
+        if (is_array($args)) {
+            foreach ($args as $arg) {
+                $dumper = &new SimpleDumper();
+                $descriptions[] = $dumper->describeValue($arg);
             }
         }
+        return implode(', ', $descriptions);
+    }
+}
 
-        /**
-         *    Message to display if expectation differs from
-         *    the parameters actually received.
-         *    @param array $expected      Expected parameters as list.
-         *    @param array $parameters    Actual parameters received.
-         *    @return string              Description of difference.
-         *    @access private
-         */
-        function _describeDifference($expected, $parameters) {
-            if (count($expected) != count($parameters)) {
-                return "Expected " . count($expected) .
-                        " arguments of [" . $this->_renderArguments($expected) .
-                        "] but got " . count($parameters) .
-                        " arguments of [" . $this->_renderArguments($parameters) . "]";
-            }
-            $messages = array();
-            for ($i = 0; $i < count($expected); $i++) {
-                $comparison = $this->_coerceToExpectation($expected[$i]);
-                if (! $comparison->test($parameters[$i])) {
-                    $messages[] = "parameter " . ($i + 1) . " with [" .
-                            $comparison->overlayMessage($parameters[$i], $this->_getDumper()) . "]";
-                }
-            }
-            return "Parameter expectation differs at " . implode(" and ", $messages);
-        }
+/**
+ *    Confirms that the number of calls on a method is as expected.
+ *  @package    SimpleTest
+ *  @subpackage MockObjects
+ */
+class CallCountExpectation extends SimpleExpectation {
+    var $_method;
+    var $_count;
 
-        /**
-         *    Creates an identical expectation if the
-         *    object/value is not already some type
-         *    of expectation.
-         *    @param mixed $expected      Expected value.
-         *    @return SimpleExpectation   Expectation object.
-         *    @access private
-         */
-        function _coerceToExpectation($expected) {
-            if (SimpleExpectation::isExpectation($expected)) {
-                return $expected;
-            }
-            return new IdenticalExpectation($expected);
-        }
+    /**
+     *    Stashes the method and expected count for later
+     *    reporting.
+     *    @param string $method    Name of method to confirm against.
+     *    @param integer $count    Expected number of calls.
+     *    @param string $message   Custom error message.
+     */
+    function CallCountExpectation($method, $count, $message = '%s') {
+        $this->_method = $method;
+        $this->_count = $count;
+        $this->SimpleExpectation($message);
+    }
 
-        /**
-         *    Renders the argument list as a string for
-         *    messages.
-         *    @param array $args    Incoming arguments.
-         *    @return string        Simple description of type and value.
-         *    @access private
-         */
-        function _renderArguments($args) {
-            $descriptions = array();
-            if (is_array($args)) {
-                foreach ($args as $arg) {
-                    $dumper = &new SimpleDumper();
-                    $descriptions[] = $dumper->describeValue($arg);
-                }
-            }
-            return implode(', ', $descriptions);
-        }
+    /**
+     *    Tests the assertion. True if correct.
+     *    @param integer $compare     Measured call count.
+     *    @return boolean             True if expected.
+     *    @access public
+     */
+    function test($compare) {
+        return ($this->_count == $compare);
     }
 
     /**
-     *    Confirms that the number of calls on a method is as expected.
+     *    Reports the comparison.
+     *    @param integer $compare     Measured call count.
+     *    @return string              Message to show.
+     *    @access public
      */
-    class CallCountExpectation extends SimpleExpectation {
-        var $_method;
-        var $_count;
+    function testMessage($compare) {
+        return 'Expected call count for [' . $this->_method .
+                '] was [' . $this->_count .
+                '] got [' . $compare . ']';
+    }
+}
 
-        /**
-         *    Stashes the method and expected count for later
-         *    reporting.
-         *    @param string $method    Name of method to confirm against.
-         *    @param integer $count    Expected number of calls.
-         *    @param string $message   Custom error message.
-         */
-        function CallCountExpectation($method, $count, $message = '%s') {
-            $this->_method = $method;
-            $this->_count = $count;
-            $this->SimpleExpectation($message);
-        }
+/**
+ *    Confirms that the number of calls on a method is as expected.
+ *  @package    SimpleTest
+ *  @subpackage MockObjects
+ */
+class MinimumCallCountExpectation extends SimpleExpectation {
+    var $_method;
+    var $_count;
 
-        /**
-         *    Tests the assertion. True if correct.
-         *    @param integer $compare     Measured call count.
-         *    @return boolean             True if expected.
-         *    @access public
-         */
-        function test($compare) {
-            return ($this->_count == $compare);
-        }
+    /**
+     *    Stashes the method and expected count for later
+     *    reporting.
+     *    @param string $method    Name of method to confirm against.
+     *    @param integer $count    Minimum number of calls.
+     *    @param string $message   Custom error message.
+     */
+    function MinimumCallCountExpectation($method, $count, $message = '%s') {
+        $this->_method = $method;
+        $this->_count = $count;
+        $this->SimpleExpectation($message);
+    }
 
-        /**
-         *    Reports the comparison.
-         *    @param integer $compare     Measured call count.
-         *    @return string              Message to show.
-         *    @access public
-         */
-        function testMessage($compare) {
-            return 'Expected call count for [' . $this->_method .
-                    '] was [' . $this->_count .
-                    '] got [' . $compare . ']';
-        }
+    /**
+     *    Tests the assertion. True if correct.
+     *    @param integer $compare     Measured call count.
+     *    @return boolean             True if enough.
+     *    @access public
+     */
+    function test($compare) {
+        return ($this->_count <= $compare);
     }
 
     /**
-     *    Confirms that the number of calls on a method is as expected.
+     *    Reports the comparison.
+     *    @param integer $compare     Measured call count.
+     *    @return string              Message to show.
+     *    @access public
      */
-    class MinimumCallCountExpectation extends SimpleExpectation {
-        var $_method;
-        var $_count;
+    function testMessage($compare) {
+        return 'Minimum call count for [' . $this->_method .
+                '] was [' . $this->_count .
+                '] got [' . $compare . ']';
+    }
+}
 
-        /**
-         *    Stashes the method and expected count for later
-         *    reporting.
-         *    @param string $method    Name of method to confirm against.
-         *    @param integer $count    Minimum number of calls.
-         *    @param string $message   Custom error message.
-         */
-        function MinimumCallCountExpectation($method, $count, $message = '%s') {
-            $this->_method = $method;
-            $this->_count = $count;
-            $this->SimpleExpectation($message);
-        }
+/**
+ *    Confirms that the number of calls on a method is as expected.
+ *    @package      SimpleTest
+ *    @subpackage   MockObjects
+ */
+class MaximumCallCountExpectation extends SimpleExpectation {
+    var $_method;
+    var $_count;
 
-        /**
-         *    Tests the assertion. True if correct.
-         *    @param integer $compare     Measured call count.
-         *    @return boolean             True if enough.
-         *    @access public
-         */
-        function test($compare) {
-            return ($this->_count <= $compare);
-        }
+    /**
+     *    Stashes the method and expected count for later
+     *    reporting.
+     *    @param string $method    Name of method to confirm against.
+     *    @param integer $count    Minimum number of calls.
+     *    @param string $message   Custom error message.
+     */
+    function MaximumCallCountExpectation($method, $count, $message = '%s') {
+        $this->_method = $method;
+        $this->_count = $count;
+        $this->SimpleExpectation($message);
+    }
 
-        /**
-         *    Reports the comparison.
-         *    @param integer $compare     Measured call count.
-         *    @return string              Message to show.
-         *    @access public
-         */
-        function testMessage($compare) {
-            return 'Minimum call count for [' . $this->_method .
-                    '] was [' . $this->_count .
-                    '] got [' . $compare . ']';
-        }
+    /**
+     *    Tests the assertion. True if correct.
+     *    @param integer $compare     Measured call count.
+     *    @return boolean             True if not over.
+     *    @access public
+     */
+    function test($compare) {
+        return ($this->_count >= $compare);
     }
 
     /**
-     *    Confirms that the number of calls on a method is as expected.
+     *    Reports the comparison.
+     *    @param integer $compare     Measured call count.
+     *    @return string              Message to show.
+     *    @access public
      */
-    class MaximumCallCountExpectation extends SimpleExpectation {
-        var $_method;
-        var $_count;
+    function testMessage($compare) {
+        return 'Maximum call count for [' . $this->_method .
+                '] was [' . $this->_count .
+                '] got [' . $compare . ']';
+    }
+}
 
-        /**
-         *    Stashes the method and expected count for later
-         *    reporting.
-         *    @param string $method    Name of method to confirm against.
-         *    @param integer $count    Minimum number of calls.
-         *    @param string $message   Custom error message.
-         */
-        function MaximumCallCountExpectation($method, $count, $message = '%s') {
-            $this->_method = $method;
-            $this->_count = $count;
-            $this->SimpleExpectation($message);
-        }
+/**
+ *    Retrieves method actions by searching the
+ *    parameter lists until an expected match is found.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class SimpleSignatureMap {
+    var $_map;
 
-        /**
-         *    Tests the assertion. True if correct.
-         *    @param integer $compare     Measured call count.
-         *    @return boolean             True if not over.
-         *    @access public
-         */
-        function test($compare) {
-            return ($this->_count >= $compare);
-        }
+    /**
+     *    Creates an empty call map.
+     *    @access public
+     */
+    function SimpleSignatureMap() {
+        $this->_map = array();
+    }
 
-        /**
-         *    Reports the comparison.
-         *    @param integer $compare     Measured call count.
-         *    @return string              Message to show.
-         *    @access public
-         */
-        function testMessage($compare) {
-            return 'Maximum call count for [' . $this->_method .
-                    '] was [' . $this->_count .
-                    '] got [' . $compare . ']';
+    /**
+     *    Stashes a reference against a method call.
+     *    @param array $parameters    Array of arguments (including wildcards).
+     *    @param mixed $action        Reference placed in the map.
+     *    @access public
+     */
+    function add($parameters, &$action) {
+        $place = count($this->_map);
+        $this->_map[$place] = array();
+        $this->_map[$place]['params'] = new ParametersExpectation($parameters);
+        $this->_map[$place]['content'] = &$action;
+    }
+
+    /**
+     *    Searches the call list for a matching parameter
+     *    set. Returned by reference.
+     *    @param array $parameters    Parameters to search by
+     *                                without wildcards.
+     *    @return object              Object held in the first matching
+     *                                slot, otherwise null.
+     *    @access public
+     */
+    function &findFirstAction($parameters) {
+        $slot = $this->_findFirstSlot($parameters);
+        if (isset($slot) && isset($slot['content'])) {
+            return $slot['content'];
         }
+        $null = null;
+        return $null;
     }
 
     /**
-     *    Retrieves values and references by searching the
-     *    parameter lists until a match is found.
-        *    @package SimpleTest
-        *    @subpackage MockObjects
+     *    Searches the call list for a matching parameter
+     *    set. True if successful.
+     *    @param array $parameters    Parameters to search by
+     *                                without wildcards.
+     *    @return boolean             True if a match is present.
+     *    @access public
      */
-    class CallMap {
-        var $_map;
+    function isMatch($parameters) {
+        return ($this->_findFirstSlot($parameters) != null);
+    }
+    
+    /**
+     *    Compares the incoming parameters with the
+     *    internal expectation. Uses the incoming $test
+     *    to dispatch the test message.
+     *    @param SimpleTestCase $test   Test to dispatch to.
+     *    @param array $parameters      The actual calling arguments.
+     *    @param string $message        The message to overlay.
+     *    @access public
+     */
+    function test(&$test, $parameters, $message) {
+    }
 
-        /**
-         *    Creates an empty call map.
-         *    @access public
-         */
-        function CallMap() {
-            $this->_map = array();
+    /**
+     *    Searches the map for a matching item.
+     *    @param array $parameters    Parameters to search by
+     *                                without wildcards.
+     *    @return array               Reference to slot or null.
+     *    @access private
+     */
+    function &_findFirstSlot($parameters) {
+        $count = count($this->_map);
+        for ($i = 0; $i < $count; $i++) {
+            if ($this->_map[$i]["params"]->test($parameters)) {
+                return $this->_map[$i];
+            }
         }
-
-        /**
-         *    Stashes a value against a method call.
-         *    @param array $parameters    Arguments including wildcards.
-         *    @param mixed $value         Value copied into the map.
-         *    @access public
-         */
-        function addValue($parameters, $value) {
-            $this->addReference($parameters, $value);
+        $null = null;
+        return $null;
+    }
+}
+
+/**
+ *    Allows setting of actions against call signatures either
+ *    at a specific time, or always. Specific time settings
+ *    trump lasting ones, otherwise the most recently added
+ *    will mask an earlier match.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class SimpleCallSchedule {
+    var $_wildcard = MOCK_ANYTHING;
+    var $_always;
+    var $_at;
+    
+    /**
+     *    Sets up an empty response schedule.
+     *    Creates an empty call map.
+     */
+    function SimpleCallSchedule() {
+        $this->_always = array();
+        $this->_at = array();
+    }
+    
+    /**
+     *    Stores an action against a signature that
+     *    will always fire unless masked by a time
+     *    specific one.
+     *    @param string $method        Method name.
+     *    @param array $args           Calling parameters.
+     *    @param SimpleAction $action  Actually simpleByValue, etc.
+     *    @access public
+     */
+    function register($method, $args, &$action) {
+        $args = $this->_replaceWildcards($args);
+        $method = strtolower($method);
+        if (! isset($this->_always[$method])) {
+            $this->_always[$method] = new SimpleSignatureMap();
         }
-
-        /**
-         *    Stashes a reference against a method call.
-         *    @param array $parameters    Array of arguments (including wildcards).
-         *    @param mixed $reference     Array reference placed in the map.
-         *    @access public
-         */
-        function addReference($parameters, &$reference) {
-            $place = count($this->_map);
-            $this->_map[$place] = array();
-            $this->_map[$place]["params"] = new ParametersExpectation($parameters);
-            $this->_map[$place]["content"] = &$reference;
+        $this->_always[$method]->add($args, $action);
+    }
+    
+    /**
+     *    Stores an action against a signature that
+     *    will fire at a specific time in the future.
+     *    @param integer $step         delay of calls to this method,
+     *                                 0 is next.
+     *    @param string $method        Method name.
+     *    @param array $args           Calling parameters.
+     *    @param SimpleAction $action  Actually SimpleByValue, etc.
+     *    @access public
+     */
+    function registerAt($step, $method, $args, &$action) {
+        $args = $this->_replaceWildcards($args);
+        $method = strtolower($method);
+        if (! isset($this->_at[$method])) {
+            $this->_at[$method] = array();
         }
+        if (! isset($this->_at[$method][$step])) {
+            $this->_at[$method][$step] = new SimpleSignatureMap();
+        }
+        $this->_at[$method][$step]->add($args, $action);
+    }
+    
+    function expectArguments($method, $args, $message) {
+        $args = $this->_replaceWildcards($args);
+        $message .= Mock::getExpectationLine();
+        $this->_expected_args[strtolower($method)] =
+                new ParametersExpectation($args, $message);
 
-        /**
-         *    Searches the call list for a matching parameter
-         *    set. Returned by reference.
-         *    @param array $parameters    Parameters to search by
-         *                                without wildcards.
-         *    @return object              Object held in the first matching
-         *                                slot, otherwise null.
-         *    @access public
-         */
-        function &findFirstMatch($parameters) {
-            $slot = $this->_findFirstSlot($parameters);
-            if (!isset($slot)) {
-                $null = null;
-                return $null;
+    }
+    
+    /**
+     *    Actually carry out the action stored previously,
+     *    if the parameters match.
+     *    @param integer $step      Time of call.
+     *    @param string $method     Method name.
+     *    @param array $args        The parameters making up the
+     *                              rest of the call.
+     *    @return mixed             The result of the action.
+     *    @access public.
+     */
+    function &respond($step, $method, $args) {
+        $method = strtolower($method);
+        if (isset($this->_at[$method][$step])) {
+            if ($this->_at[$method][$step]->isMatch($args)) {
+                $action = &$this->_at[$method][$step]->findFirstAction($args);
+                if (isset($action)) {
+                    return $action->act();
+                }
             }
-            return $slot["content"];
         }
-
-        /**
-         *    Searches the call list for a matching parameter
-         *    set. True if successful.
-         *    @param array $parameters    Parameters to search by
-         *                                without wildcards.
-         *    @return boolean             True if a match is present.
-         *    @access public
-         */
-        function isMatch($parameters) {
-            return ($this->_findFirstSlot($parameters) != null);
+        if (isset($this->_always[$method])) {
+            $action = &$this->_always[$method]->findFirstAction($args);
+            if (isset($action)) {
+                return $action->act();
+            }
         }
-
-        /**
-         *    Searches the map for a matching item.
-         *    @param array $parameters    Parameters to search by
-         *                                without wildcards.
-         *    @return array               Reference to slot or null.
-         *    @access private
-         */
-        function &_findFirstSlot($parameters) {
-            $count = count($this->_map);
-            for ($i = 0; $i < $count; $i++) {
-                if ($this->_map[$i]["params"]->test($parameters)) {
-                    return $this->_map[$i];
-                }
+        $null = null;
+        return $null;
+    }
+    
+    /**
+     *    Replaces wildcard matches with wildcard
+     *    expectations in the argument list.
+     *    @param array $args      Raw argument list.
+     *    @return array           Argument list with
+     *                            expectations.
+     *    @access private
+     */
+    function _replaceWildcards($args) {
+        if ($args === false) {
+            return false;
+        }
+        for ($i = 0; $i < count($args); $i++) {
+            if ($args[$i] === $this->_wildcard) {
+                $args[$i] = new AnythingExpectation();
             }
-            $null = null;
-            return $null;
         }
+        return $args;
+    }
+}
+
+/**
+ *    A type of SimpleMethodAction.
+ *    Stashes a reference for returning later.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class SimpleByReference {
+    var $_reference;
+    
+    /**
+     *    Stashes it for later.
+     *    @param mixed $reference     Actual PHP4 style reference.
+     *    @access public
+     */
+    function SimpleByReference(&$reference) {
+        $this->_reference = &$reference;
+    }
+    
+    /**
+     *    Returns the reference stored earlier.
+     *    @return mixed    Whatever was stashed.
+     *    @access public
+     */
+    function &act() {
+        return $this->_reference;
+    }
+}
+
+/**
+ *    A type of SimpleMethodAction.
+ *    Stashes a value for returning later.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class SimpleByValue {
+    var $_value;
+    
+    /**
+     *    Stashes it for later.
+     *    @param mixed $value     You need to clone objects
+     *                            if you want copy semantics
+     *                            for these.
+     *    @access public
+     */
+    function SimpleByValue($value) {
+        $this->_value = $value;
+    }
+    
+    /**
+     *    Returns the value stored earlier.
+     *    @return mixed    Whatever was stashed.
+     *    @access public
+     */
+    function &act() {
+        $dummy = $this->_value;
+        return $dummy;
+    }
+}
+
+/**
+ *    A type of SimpleMethodAction.
+ *    Stashes an exception for throwing later.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class SimpleThrower {
+    var $_exception;
+    
+    /**
+     *    Stashes it for later.
+     *    @param Exception $exception    The exception object to throw.
+     *    @access public
+     */
+    function SimpleThrower($exception) {
+        $this->_exception = $exception;
+    }
+    
+    /**
+     *    Throws the exceptins stashed earlier.
+     *    @access public
+     */
+    function act() {
+        eval('throw $this->_exception;');
+    }
+}
+
+/**
+ *    A type of SimpleMethodAction.
+ *    Stashes an error for emitting later.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class SimpleErrorThrower {
+    var $_error;
+    var $_severity;
+    
+    /**
+     *    Stashes an error to throw later.
+     *    @param string $error      Error message.
+     *    @param integer $severity  PHP error constant, e.g E_USER_ERROR.
+     *    @access public
+     */
+    function SimpleErrorThrower($error, $severity) {
+        $this->_error = $error;
+        $this->_severity = $severity;
+    }
+    
+    /**
+     *    Triggers the stashed error.
+     *    @return null        The usual PHP4.4 shenanigans are needed here.
+     *    @access public
+     */
+    function &act() {
+        trigger_error($this->_error, $this->_severity);
+        $null = null;
+        return $null;
     }
+}
+
+/**
+ *    A base class or delegate that extends an
+ *    empty collection of methods that can have their
+ *    return values set and expectations made of the
+ *    calls upon them. The mock will assert the
+ *    expectations against it's attached test case in
+ *    addition to the server stub behaviour or returning
+ *    preprogrammed responses.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class SimpleMock {
+    var $_actions;
+    var $_wildcard = MOCK_ANYTHING;
+    var $_is_strict = true;
+    var $_call_counts;
+    var $_expected_counts;
+    var $_max_counts;
+    var $_expected_args;
+    var $_expected_args_at;
 
     /**
-     *    An empty collection of methods that can have their
-     *    return values set and expectations made of the
-     *    calls upon them. The mock will assert the
-     *    expectations against it's attached test case in
-     *    addition to the server stub behaviour.
-        *    @package SimpleTest
-        *    @subpackage MockObjects
-     */
-    class SimpleMock {
-        var $_wildcard = MOCK_ANYTHING;
-        var $_is_strict = true;
-        var $_returns;
-        var $_return_sequence;
-        var $_call_counts;
-        var $_expected_counts;
-        var $_max_counts;
-        var $_expected_args;
-        var $_expected_args_at;
-
-        /**
-         *    Creates an empty return list and expectation list.
-         *    All call counts are set to zero.
-         *    @param SimpleTestCase $test    Test case to test expectations in.
-         *    @param mixed $wildcard         Parameter matching wildcard.
-         *    @param boolean $is_strict      Enables method name checks on
-         *                                   expectations.
-         */
-        function SimpleMock() {
-            $this->_returns = array();
-            $this->_return_sequence = array();
-            $this->_call_counts = array();
-            $test = &$this->_getCurrentTestCase();
-            $test->tell($this);
-            $this->_expected_counts = array();
-            $this->_max_counts = array();
-            $this->_expected_args = array();
-            $this->_expected_args_at = array();
-        }
+     *    Creates an empty action list and expectation list.
+     *    All call counts are set to zero.
+     *    @access public
+     */
+    function SimpleMock() {
+        $this->_actions = &new SimpleCallSchedule();
+        $this->_expectations = &new SimpleCallSchedule();
+        $this->_call_counts = array();
+        $this->_expected_counts = array();
+        $this->_max_counts = array();
+        $this->_expected_args = array();
+        $this->_expected_args_at = array();
+        $test = &$this->_getCurrentTestCase();
+        $test->tell($this);
+    }
+    
+    /**
+     *    Disables a name check when setting expectations.
+     *    This hack is needed for the partial mocks.
+     *    @access public
+     */
+    function disableExpectationNameChecks() {
+        $this->_is_strict = false;
+    }
 
-        /**
-         *    Disables a name check when setting expectations.
-         *    This hack is needed for the partial mocks.
-         *    @access public
-         */
-        function disableExpectationNameChecks() {
-            $this->_is_strict = false;
-        }
+    /**
+     *    Finds currently running test.
+     *    @return SimpeTestCase    Current test case.
+     *    @access protected
+     */
+    function &_getCurrentTestCase() {
+        $context = &SimpleTest::getContext();
+        return $context->getTest();
+    }
 
-        /**
-         *    Changes the default wildcard object.
-         *    @param mixed $wildcard         Parameter matching wildcard.
-         *    @access public
-         */
-        function setWildcard($wildcard) {
-            $this->_wildcard = $wildcard;
+    /**
+     *    Die if bad arguments array is passed.
+     *    @param mixed $args     The arguments value to be checked.
+     *    @param string $task    Description of task attempt.
+     *    @return boolean        Valid arguments
+     *    @access private
+     */
+    function _checkArgumentsIsArray($args, $task) {
+        if (! is_array($args)) {
+            trigger_error(
+                "Cannot $task as \$args parameter is not an array",
+                E_USER_ERROR);
         }
+    }
 
-        /**
-         *    Finds currently running test.
-         *    @return SimpeTestCase    Current test case.
-         *    @access protected
-         */
-        function &_getCurrentTestCase() {
-            $context = &SimpleTest::getContext();
-            return $context->getTest();
+    /**
+     *    Triggers a PHP error if the method is not part
+     *    of this object.
+     *    @param string $method        Name of method.
+     *    @param string $task          Description of task attempt.
+     *    @access protected
+     */
+    function _dieOnNoMethod($method, $task) {
+        if ($this->_is_strict && ! method_exists($this, $method)) {
+            trigger_error(
+                    "Cannot $task as no ${method}() in class " . get_class($this),
+                    E_USER_ERROR);
         }
+    }
 
-        /**
-         *    Die if bad arguments array is passed
-         *    @param mixed $args     The arguments value to be checked.
-         *    @param string $task    Description of task attempt.
-         *    @return boolean        Valid arguments
-         *    @access private
-         */
-        function _checkArgumentsIsArray($args, $task) {
-               if (! is_array($args)) {
-                       trigger_error(
-                               "Cannot $task as \$args parameter is not an array",
-                               E_USER_ERROR);
-               }
+    /**
+     *    Replaces wildcard matches with wildcard
+     *    expectations in the argument list.
+     *    @param array $args      Raw argument list.
+     *    @return array           Argument list with
+     *                            expectations.
+     *    @access private
+     */
+    function _replaceWildcards($args) {
+        if ($args === false) {
+            return false;
         }
-
-        /**
-         *    Triggers a PHP error if the method is not part
-         *    of this object.
-         *    @param string $method        Name of method.
-         *    @param string $task          Description of task attempt.
-         *    @access protected
-         */
-        function _dieOnNoMethod($method, $task) {
-            if ($this->_is_strict && ! method_exists($this, $method)) {
-                trigger_error(
-                        "Cannot $task as no ${method}() in class " . get_class($this),
-                        E_USER_ERROR);
+        for ($i = 0; $i < count($args); $i++) {
+            if ($args[$i] === $this->_wildcard) {
+                $args[$i] = new AnythingExpectation();
             }
         }
+        return $args;
+    }
 
-        /**
-         *    Replaces wildcard matches with wildcard
-         *    expectations in the argument list.
-         *    @param array $args      Raw argument list.
-         *    @return array           Argument list with
-         *                            expectations.
-         *    @access private
-         */
-        function _replaceWildcards($args) {
-            if ($args === false) {
-                return false;
-            }
-            for ($i = 0; $i < count($args); $i++) {
-                if ($args[$i] === $this->_wildcard) {
-                    $args[$i] = new AnythingExpectation();
-                }
-            }
-            return $args;
+    /**
+     *    Adds one to the call count of a method.
+     *    @param string $method        Method called.
+     *    @param array $args           Arguments as an array.
+     *    @access protected
+     */
+    function _addCall($method, $args) {
+        if (! isset($this->_call_counts[$method])) {
+            $this->_call_counts[$method] = 0;
         }
+        $this->_call_counts[$method]++;
+    }
 
-        /**
-         *    Adds one to the call count of a method.
-         *    @param string $method        Method called.
-         *    @param array $args           Arguments as an array.
-         *    @access protected
-         */
-        function _addCall($method, $args) {
-            if (!isset($this->_call_counts[$method])) {
-                $this->_call_counts[$method] = 0;
-            }
-            $this->_call_counts[$method]++;
+    /**
+     *    Fetches the call count of a method so far.
+     *    @param string $method        Method name called.
+     *    @return integer              Number of calls so far.
+     *    @access public
+     */
+    function getCallCount($method) {
+        $this->_dieOnNoMethod($method, "get call count");
+        $method = strtolower($method);
+        if (! isset($this->_call_counts[$method])) {
+            return 0;
         }
+        return $this->_call_counts[$method];
+    }
 
-        /**
-         *    Fetches the call count of a method so far.
-         *    @param string $method        Method name called.
-         *    @return                      Number of calls so far.
-         *    @access public
-         */
-        function getCallCount($method) {
-            $this->_dieOnNoMethod($method, "get call count");
-            $method = strtolower($method);
-            if (! isset($this->_call_counts[$method])) {
-                return 0;
-            }
-            return $this->_call_counts[$method];
-        }
+    /**
+     *    Sets a return for a parameter list that will
+     *    be passed by value for all calls to this method.
+     *    @param string $method       Method name.
+     *    @param mixed $value         Result of call passed by value.
+     *    @param array $args          List of parameters to match
+     *                                including wildcards.
+     *    @access public
+     */
+    function setReturnValue($method, $value, $args = false) {
+        $this->_dieOnNoMethod($method, "set return value");
+        $this->_actions->register($method, $args, new SimpleByValue($value));
+    }
 
-        /**
-         *    Sets a return for a parameter list that will
-         *    be passed by value for all calls to this method.
-         *    @param string $method       Method name.
-         *    @param mixed $value         Result of call passed by value.
-         *    @param array $args          List of parameters to match
-         *                                including wildcards.
-         *    @access public
-         */
-        function setReturnValue($method, $value, $args = false) {
-            $this->_dieOnNoMethod($method, "set return value");
-            $args = $this->_replaceWildcards($args);
-            $method = strtolower($method);
-            if (! isset($this->_returns[$method])) {
-                $this->_returns[$method] = new CallMap();
-            }
-            $this->_returns[$method]->addValue($args, $value);
-        }
+    /**
+     *    Sets a return for a parameter list that will
+     *    be passed by value only when the required call count
+     *    is reached.
+     *    @param integer $timing   Number of calls in the future
+     *                             to which the result applies. If
+     *                             not set then all calls will return
+     *                             the value.
+     *    @param string $method    Method name.
+     *    @param mixed $value      Result of call passed by value.
+     *    @param array $args       List of parameters to match
+     *                             including wildcards.
+     *    @access public
+     */
+    function setReturnValueAt($timing, $method, $value, $args = false) {
+        $this->_dieOnNoMethod($method, "set return value sequence");
+        $this->_actions->registerAt($timing, $method, $args, new SimpleByValue($value));
+    }
 
-        /**
-         *    Sets a return for a parameter list that will
-         *    be passed by value only when the required call count
-         *    is reached.
-         *    @param integer $timing   Number of calls in the future
-         *                             to which the result applies. If
-         *                             not set then all calls will return
-         *                             the value.
-         *    @param string $method    Method name.
-         *    @param mixed $value      Result of call passed by value.
-         *    @param array $args       List of parameters to match
-         *                             including wildcards.
-         *    @access public
-         */
-        function setReturnValueAt($timing, $method, $value, $args = false) {
-            $this->_dieOnNoMethod($method, "set return value sequence");
-            $args = $this->_replaceWildcards($args);
-            $method = strtolower($method);
-            if (! isset($this->_return_sequence[$method])) {
-                $this->_return_sequence[$method] = array();
-            }
-            if (! isset($this->_return_sequence[$method][$timing])) {
-                $this->_return_sequence[$method][$timing] = new CallMap();
-            }
-            $this->_return_sequence[$method][$timing]->addValue($args, $value);
-        }
+    /**
+     *    Sets a return for a parameter list that will
+     *    be passed by reference for all calls.
+     *    @param string $method       Method name.
+     *    @param mixed $reference     Result of the call will be this object.
+     *    @param array $args          List of parameters to match
+     *                                including wildcards.
+     *    @access public
+     */
+    function setReturnReference($method, &$reference, $args = false) {
+        $this->_dieOnNoMethod($method, "set return reference");
+        $this->_actions->register($method, $args, new SimpleByReference($reference));
+    }
 
-        /**
-         *    Sets a return for a parameter list that will
-         *    be passed by reference for all calls.
-         *    @param string $method       Method name.
-         *    @param mixed $reference     Result of the call will be this object.
-         *    @param array $args          List of parameters to match
-         *                                including wildcards.
-         *    @access public
-         */
-        function setReturnReference($method, &$reference, $args = false) {
-            $this->_dieOnNoMethod($method, "set return reference");
-            $args = $this->_replaceWildcards($args);
-            $method = strtolower($method);
-            if (! isset($this->_returns[$method])) {
-                $this->_returns[$method] = new CallMap();
-            }
-            $this->_returns[$method]->addReference($args, $reference);
-        }
+    /**
+     *    Sets a return for a parameter list that will
+     *    be passed by value only when the required call count
+     *    is reached.
+     *    @param integer $timing    Number of calls in the future
+     *                              to which the result applies. If
+     *                              not set then all calls will return
+     *                              the value.
+     *    @param string $method     Method name.
+     *    @param mixed $reference   Result of the call will be this object.
+     *    @param array $args        List of parameters to match
+     *                              including wildcards.
+     *    @access public
+     */
+    function setReturnReferenceAt($timing, $method, &$reference, $args = false) {
+        $this->_dieOnNoMethod($method, "set return reference sequence");
+        $this->_actions->registerAt($timing, $method, $args, new SimpleByReference($reference));
+    }
 
-        /**
-         *    Sets a return for a parameter list that will
-         *    be passed by value only when the required call count
-         *    is reached.
-         *    @param integer $timing    Number of calls in the future
-         *                              to which the result applies. If
-         *                              not set then all calls will return
-         *                              the value.
-         *    @param string $method     Method name.
-         *    @param mixed $reference   Result of the call will be this object.
-         *    @param array $args        List of parameters to match
-         *                              including wildcards.
-         *    @access public
-         */
-        function setReturnReferenceAt($timing, $method, &$reference, $args = false) {
-            $this->_dieOnNoMethod($method, "set return reference sequence");
-            $args = $this->_replaceWildcards($args);
-            $method = strtolower($method);
-            if (! isset($this->_return_sequence[$method])) {
-                $this->_return_sequence[$method] = array();
-            }
-            if (! isset($this->_return_sequence[$method][$timing])) {
-                $this->_return_sequence[$method][$timing] = new CallMap();
-            }
-            $this->_return_sequence[$method][$timing]->addReference($args, $reference);
-        }
+    /**
+     *    Sets up an expected call with a set of
+     *    expected parameters in that call. All
+     *    calls will be compared to these expectations
+     *    regardless of when the call is made.
+     *    @param string $method        Method call to test.
+     *    @param array $args           Expected parameters for the call
+     *                                 including wildcards.
+     *    @param string $message       Overridden message.
+     *    @access public
+     */
+    function expect($method, $args, $message = '%s') {
+        $this->_dieOnNoMethod($method, 'set expected arguments');
+        $this->_checkArgumentsIsArray($args, 'set expected arguments');
+        $this->_expectations->expectArguments($method, $args, $message);
+        $args = $this->_replaceWildcards($args);
+        $message .= Mock::getExpectationLine();
+        $this->_expected_args[strtolower($method)] =
+                new ParametersExpectation($args, $message);
+    }
 
-        /**
-         *    Sets up an expected call with a set of
-         *    expected parameters in that call. All
-         *    calls will be compared to these expectations
-         *    regardless of when the call is made.
-         *    @param string $method        Method call to test.
-         *    @param array $args           Expected parameters for the call
-         *                                 including wildcards.
-         *    @param string $message       Overridden message.
-         *    @access public
-         */
-        function expect($method, $args, $message = '%s') {
-            $this->_dieOnNoMethod($method, 'set expected arguments');
-            $this->_checkArgumentsIsArray($args, 'set expected arguments');
-            $args = $this->_replaceWildcards($args);
-            $message .= Mock::getExpectationLine();
-            $this->_expected_args[strtolower($method)] =
-                    new ParametersExpectation($args, $message);
-        }
+    /**
+     *    @deprecated
+     */
+    function expectArguments($method, $args, $message = '%s') {
+        return $this->expect($method, $args, $message);
+    }
 
-        /**
-         *    @deprecated
-         */
-        function expectArguments($method, $args, $message = '%s') {
-            return $this->expect($method, $args, $message);
-        }
+    /**
+     *    Sets up an expected call with a set of
+     *    expected parameters in that call. The
+     *    expected call count will be adjusted if it
+     *    is set too low to reach this call.
+     *    @param integer $timing    Number of calls in the future at
+     *                              which to test. Next call is 0.
+     *    @param string $method     Method call to test.
+     *    @param array $args        Expected parameters for the call
+     *                              including wildcards.
+     *    @param string $message    Overridden message.
+     *    @access public
+     */
+    function expectAt($timing, $method, $args, $message = '%s') {
+        $this->_dieOnNoMethod($method, 'set expected arguments at time');
+        $this->_checkArgumentsIsArray($args, 'set expected arguments at time');
+        $args = $this->_replaceWildcards($args);
+        if (! isset($this->_expected_args_at[$timing])) {
+            $this->_expected_args_at[$timing] = array();
+        }
+        $method = strtolower($method);
+        $message .= Mock::getExpectationLine();
+        $this->_expected_args_at[$timing][$method] =
+                new ParametersExpectation($args, $message);
+    }
 
-        /**
-         *    Sets up an expected call with a set of
-         *    expected parameters in that call. The
-         *    expected call count will be adjusted if it
-         *    is set too low to reach this call.
-         *    @param integer $timing    Number of calls in the future at
-         *                              which to test. Next call is 0.
-         *    @param string $method     Method call to test.
-         *    @param array $args        Expected parameters for the call
-         *                              including wildcards.
-         *    @param string $message    Overridden message.
-         *    @access public
-         */
-        function expectAt($timing, $method, $args, $message = '%s') {
-            $this->_dieOnNoMethod($method, 'set expected arguments at time');
-            $this->_checkArgumentsIsArray($args, 'set expected arguments at time');
-            $args = $this->_replaceWildcards($args);
-            if (! isset($this->_expected_args_at[$timing])) {
-                $this->_expected_args_at[$timing] = array();
-            }
-            $method = strtolower($method);
-            $message .= Mock::getExpectationLine();
-            $this->_expected_args_at[$timing][$method] =
-                    new ParametersExpectation($args, $message);
-        }
+    /**
+     *    @deprecated
+     */
+    function expectArgumentsAt($timing, $method, $args, $message = '%s') {
+        return $this->expectAt($timing, $method, $args, $message);
+    }
 
-        /**
-         *    @deprecated
-         */
-        function expectArgumentsAt($timing, $method, $args, $message = '%s') {
-            return $this->expectAt($timing, $method, $args, $message);
-        }
+    /**
+     *    Sets an expectation for the number of times
+     *    a method will be called. The tally method
+     *    is used to check this.
+     *    @param string $method        Method call to test.
+     *    @param integer $count        Number of times it should
+     *                                 have been called at tally.
+     *    @param string $message       Overridden message.
+     *    @access public
+     */
+    function expectCallCount($method, $count, $message = '%s') {
+        $this->_dieOnNoMethod($method, 'set expected call count');
+        $message .= Mock::getExpectationLine();
+        $this->_expected_counts[strtolower($method)] =
+                new CallCountExpectation($method, $count, $message);
+    }
 
-        /**
-         *    Sets an expectation for the number of times
-         *    a method will be called. The tally method
-         *    is used to check this.
-         *    @param string $method        Method call to test.
-         *    @param integer $count        Number of times it should
-         *                                 have been called at tally.
-         *    @param string $message       Overridden message.
-         *    @access public
-         */
-        function expectCallCount($method, $count, $message = '%s') {
-            $this->_dieOnNoMethod($method, 'set expected call count');
-            $message .= Mock::getExpectationLine();
-            $this->_expected_counts[strtolower($method)] =
-                    new CallCountExpectation($method, $count, $message);
-        }
+    /**
+     *    Sets the number of times a method may be called
+     *    before a test failure is triggered.
+     *    @param string $method        Method call to test.
+     *    @param integer $count        Most number of times it should
+     *                                 have been called.
+     *    @param string $message       Overridden message.
+     *    @access public
+     */
+    function expectMaximumCallCount($method, $count, $message = '%s') {
+        $this->_dieOnNoMethod($method, 'set maximum call count');
+        $message .= Mock::getExpectationLine();
+        $this->_max_counts[strtolower($method)] =
+                new MaximumCallCountExpectation($method, $count, $message);
+    }
 
-        /**
-         *    Sets the number of times a method may be called
-         *    before a test failure is triggered.
-         *    @param string $method        Method call to test.
-         *    @param integer $count        Most number of times it should
-         *                                 have been called.
-         *    @param string $message       Overridden message.
-         *    @access public
-         */
-        function expectMaximumCallCount($method, $count, $message = '%s') {
-            $this->_dieOnNoMethod($method, 'set maximum call count');
-            $message .= Mock::getExpectationLine();
-            $this->_max_counts[strtolower($method)] =
-                    new MaximumCallCountExpectation($method, $count, $message);
-        }
+    /**
+     *    Sets the number of times to call a method to prevent
+     *    a failure on the tally.
+     *    @param string $method      Method call to test.
+     *    @param integer $count      Least number of times it should
+     *                               have been called.
+     *    @param string $message     Overridden message.
+     *    @access public
+     */
+    function expectMinimumCallCount($method, $count, $message = '%s') {
+        $this->_dieOnNoMethod($method, 'set minimum call count');
+        $message .= Mock::getExpectationLine();
+        $this->_expected_counts[strtolower($method)] =
+                new MinimumCallCountExpectation($method, $count, $message);
+    }
 
-        /**
-         *    Sets the number of times to call a method to prevent
-         *    a failure on the tally.
-         *    @param string $method      Method call to test.
-         *    @param integer $count      Least number of times it should
-         *                               have been called.
-         *    @param string $message     Overridden message.
-         *    @access public
-         */
-        function expectMinimumCallCount($method, $count, $message = '%s') {
-            $this->_dieOnNoMethod($method, 'set minimum call count');
-            $message .= Mock::getExpectationLine();
-            $this->_expected_counts[strtolower($method)] =
-                    new MinimumCallCountExpectation($method, $count, $message);
-        }
+    /**
+     *    Convenience method for barring a method
+     *    call.
+     *    @param string $method        Method call to ban.
+     *    @param string $message       Overridden message.
+     *    @access public
+     */
+    function expectNever($method, $message = '%s') {
+        $this->expectMaximumCallCount($method, 0, $message);
+    }
 
-        /**
-         *    Convenience method for barring a method
-         *    call.
-         *    @param string $method        Method call to ban.
-         *    @param string $message       Overridden message.
-         *    @access public
-         */
-        function expectNever($method, $message = '%s') {
-            $this->expectMaximumCallCount($method, 0, $message);
+    /**
+     *    Convenience method for a single method
+     *    call.
+     *    @param string $method     Method call to track.
+     *    @param array $args        Expected argument list or
+     *                              false for any arguments.
+     *    @param string $message    Overridden message.
+     *    @access public
+     */
+    function expectOnce($method, $args = false, $message = '%s') {
+        $this->expectCallCount($method, 1, $message);
+        if ($args !== false) {
+            $this->expect($method, $args, $message);
         }
+    }
 
-        /**
-         *    Convenience method for a single method
-         *    call.
-         *    @param string $method     Method call to track.
-         *    @param array $args        Expected argument list or
-         *                              false for any arguments.
-         *    @param string $message    Overridden message.
-         *    @access public
-         */
-        function expectOnce($method, $args = false, $message = '%s') {
-            $this->expectCallCount($method, 1, $message);
-            if ($args !== false) {
-                $this->expectArguments($method, $args, $message);
-            }
+    /**
+     *    Convenience method for requiring a method
+     *    call.
+     *    @param string $method       Method call to track.
+     *    @param array $args          Expected argument list or
+     *                                false for any arguments.
+     *    @param string $message      Overridden message.
+     *    @access public
+     */
+    function expectAtLeastOnce($method, $args = false, $message = '%s') {
+        $this->expectMinimumCallCount($method, 1, $message);
+        if ($args !== false) {
+            $this->expect($method, $args, $message);
         }
+    }
+    
+    /**
+     *    Sets up a trigger to throw an exception upon the
+     *    method call.
+     *    @param string $method     Method name to throw on.
+     */
+    function throwOn($method, $exception = false, $args = false) {
+        $this->_dieOnNoMethod($method, "throw on");
+        $this->_actions->register($method, $args,
+                new SimpleThrower($exception ? $exception : new Exception()));
+    }
+    
+    /**
+     *    Sets up a trigger to throw an exception upon the
+     *    method call.
+     */
+    function throwAt($timing, $method, $exception = false, $args = false) {
+        $this->_dieOnNoMethod($method, "throw at");
+        $this->_actions->registerAt($timing, $method, $args,
+                new SimpleThrower($exception ? $exception : new Exception()));
+    }
+    
+    /**
+     *    Sets up a trigger to throw an error upon the
+     *    method call.
+     */
+    function errorOn($method, $error = 'A mock error', $args = false, $severity = E_USER_ERROR) {
+        $this->_dieOnNoMethod($method, "error on");
+        $this->_actions->register($method, $args, new SimpleErrorThrower($error, $severity));
+    }
+    
+    /**
+     *    Sets up a trigger to throw an error upon the
+     *    method call.
+     */
+    function errorAt($timing, $method, $error = 'A mock error', $args = false, $severity = E_USER_ERROR) {
+        $this->_dieOnNoMethod($method, "error at");
+        $this->_actions->registerAt($timing, $method, $args, new SimpleErrorThrower($error, $severity));
+    }
 
-        /**
-         *    Convenience method for requiring a method
-         *    call.
-         *    @param string $method       Method call to track.
-         *    @param array $args          Expected argument list or
-         *                                false for any arguments.
-         *    @param string $message      Overridden message.
-         *    @access public
-         */
-        function expectAtLeastOnce($method, $args = false, $message = '%s') {
-            $this->expectMinimumCallCount($method, 1, $message);
-            if ($args !== false) {
-                $this->expectArguments($method, $args, $message);
-            }
-        }
+    /**
+     *    @deprecated
+     */
+    function tally() {
+    }
 
-        /**
-         *    @deprecated
-         */
-        function tally() {
+    /**
+     *    Receives event from unit test that the current
+     *    test method has finished. Totals up the call
+     *    counts and triggers a test assertion if a test
+     *    is present for expected call counts.
+     *    @param string $test_method      Current method name.
+     *    @param SimpleTestCase $test     Test to send message to.
+     *    @access public
+     */
+    function atTestEnd($test_method, &$test) {
+        foreach ($this->_expected_counts as $method => $expectation) {
+            $test->assert($expectation, $this->getCallCount($method));
         }
-
-        /**
-         *    Receives event from unit test that the current
-         *    test method has finished. Totals up the call
-         *    counts and triggers a test assertion if a test
-         *    is present for expected call counts.
-         *    @param string $test_method    Current method name.
-         *    @param SimpleTestCase $test   Test to send message to.
-         *    @access public
-         */
-        function atTestEnd($test_method, &$test) {
-            foreach ($this->_expected_counts as $method => $expectation) {
+        foreach ($this->_max_counts as $method => $expectation) {
+            if ($expectation->test($this->getCallCount($method))) {
                 $test->assert($expectation, $this->getCallCount($method));
             }
-            foreach ($this->_max_counts as $method => $expectation) {
-                if ($expectation->test($this->getCallCount($method))) {
-                    $test->assert($expectation, $this->getCallCount($method));
-                }
-            }
         }
+    }
 
-        /**
-         *    Returns the expected value for the method name
-         *    and checks expectations. Will generate any
-         *    test assertions as a result of expectations
-         *    if there is a test present.
-         *    @param string $method       Name of method to simulate.
-         *    @param array $args          Arguments as an array.
-         *    @return mixed               Stored return.
-         *    @access private
-         */
-        function &_invoke($method, $args) {
-            $method = strtolower($method);
-            $step = $this->getCallCount($method);
-            $this->_addCall($method, $args);
-            $this->_checkExpectations($method, $args, $step);
-            $result = &$this->_getReturn($method, $args, $step);
-            return $result;
-        }
-        /**
-         *    Finds the return value matching the incoming
-         *    arguments. If there is no matching value found
-         *    then an error is triggered.
-         *    @param string $method      Method name.
-         *    @param array $args         Calling arguments.
-         *    @param integer $step       Current position in the
-         *                               call history.
-         *    @return mixed              Stored return.
-         *    @access protected
-         */
-        function &_getReturn($method, $args, $step) {
-            if (isset($this->_return_sequence[$method][$step])) {
-                if ($this->_return_sequence[$method][$step]->isMatch($args)) {
-                    $result = &$this->_return_sequence[$method][$step]->findFirstMatch($args);
-                    return $result;
-                }
-            }
-            if (isset($this->_returns[$method])) {
-                $result = &$this->_returns[$method]->findFirstMatch($args);
-                return $result;
-            }
-            $null = null;
-            return $null;
-        }
+    /**
+     *    Returns the expected value for the method name
+     *    and checks expectations. Will generate any
+     *    test assertions as a result of expectations
+     *    if there is a test present.
+     *    @param string $method       Name of method to simulate.
+     *    @param array $args          Arguments as an array.
+     *    @return mixed               Stored return.
+     *    @access private
+     */
+    function &_invoke($method, $args) {
+        $method = strtolower($method);
+        $step = $this->getCallCount($method);
+        $this->_addCall($method, $args);
+        $this->_checkExpectations($method, $args, $step);
+        $result = &$this->_emulateCall($method, $args, $step);
+        return $result;
+    }
+    
+    /**
+     *    Finds the return value matching the incoming
+     *    arguments. If there is no matching value found
+     *    then an error is triggered.
+     *    @param string $method      Method name.
+     *    @param array $args         Calling arguments.
+     *    @param integer $step       Current position in the
+     *                               call history.
+     *    @return mixed              Stored return or other action.
+     *    @access protected
+     */
+    function &_emulateCall($method, $args, $step) {
+        return $this->_actions->respond($step, $method, $args);
+    }
 
-        /**
-         *    Tests the arguments against expectations.
-         *    @param string $method        Method to check.
-         *    @param array $args           Argument list to match.
-         *    @param integer $timing       The position of this call
-         *                                 in the call history.
-         *    @access private
-         */
-        function _checkExpectations($method, $args, $timing) {
-            $test = &$this->_getCurrentTestCase();
-            if (isset($this->_max_counts[$method])) {
-                if (! $this->_max_counts[$method]->test($timing + 1)) {
-                    $test->assert($this->_max_counts[$method], $timing + 1);
-                }
-            }
-            if (isset($this->_expected_args_at[$timing][$method])) {
-                $test->assert(
-                        $this->_expected_args_at[$timing][$method],
-                        $args,
-                        "Mock method [$method] at [$timing] -> %s");
-            } elseif (isset($this->_expected_args[$method])) {
-                $test->assert(
-                        $this->_expected_args[$method],
-                        $args,
-                        "Mock method [$method] -> %s");
+    /**
+     *    Tests the arguments against expectations.
+     *    @param string $method        Method to check.
+     *    @param array $args           Argument list to match.
+     *    @param integer $timing       The position of this call
+     *                                 in the call history.
+     *    @access private
+     */
+    function _checkExpectations($method, $args, $timing) {
+        $test = &$this->_getCurrentTestCase();
+        if (isset($this->_max_counts[$method])) {
+            if (! $this->_max_counts[$method]->test($timing + 1)) {
+                $test->assert($this->_max_counts[$method], $timing + 1);
             }
         }
+        if (isset($this->_expected_args_at[$timing][$method])) {
+            $test->assert(
+                    $this->_expected_args_at[$timing][$method],
+                    $args,
+                    "Mock method [$method] at [$timing] -> %s");
+        } elseif (isset($this->_expected_args[$method])) {
+            $test->assert(
+                    $this->_expected_args[$method],
+                    $args,
+                    "Mock method [$method] -> %s");
+        }
     }
+}
+
+/**
+ *    Static methods only service class for code generation of
+ *    mock objects.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class Mock {
 
     /**
-     *    Static methods only service class for code generation of
-     *    mock objects.
-        *    @package SimpleTest
-        *    @subpackage MockObjects
+     *    Factory for mock object classes.
+     *    @access public
      */
-    class Mock {
-
-        /**
-         *    Factory for mock object classes.
-         *    @access public
-         */
-        function Mock() {
-            trigger_error('Mock factory methods are static.');
-        }
-
-        /**
-         *    Clones a class' interface and creates a mock version
-         *    that can have return values and expectations set.
-         *    @param string $class         Class to clone.
-         *    @param string $mock_class    New class name. Default is
-         *                                 the old name with "Mock"
-         *                                 prepended.
-         *    @param array $methods        Additional methods to add beyond
-         *                                 those in th cloned class. Use this
-         *                                 to emulate the dynamic addition of
-         *                                 methods in the cloned class or when
-         *                                 the class hasn't been written yet.
-         *    @static
-         *    @access public
-         */
-        function generate($class, $mock_class = false, $methods = false) {
-            $generator = new MockGenerator($class, $mock_class);
-            return $generator->generate($methods);
-        }
+    function Mock() {
+        trigger_error('Mock factory methods are static.');
+    }
 
-        /**
-         *    Generates a version of a class with selected
-         *    methods mocked only. Inherits the old class
-         *    and chains the mock methods of an aggregated
-         *    mock object.
-         *    @param string $class            Class to clone.
-         *    @param string $mock_class       New class name.
-         *    @param array $methods           Methods to be overridden
-         *                                    with mock versions.
-         *    @static
-         *    @access public
-         */
-        function generatePartial($class, $mock_class, $methods) {
-            $generator = new MockGenerator($class, $mock_class);
-            return $generator->generatePartial($methods);
-        }
+    /**
+     *    Clones a class' interface and creates a mock version
+     *    that can have return values and expectations set.
+     *    @param string $class         Class to clone.
+     *    @param string $mock_class    New class name. Default is
+     *                                 the old name with "Mock"
+     *                                 prepended.
+     *    @param array $methods        Additional methods to add beyond
+     *                                 those in the cloned class. Use this
+     *                                 to emulate the dynamic addition of
+     *                                 methods in the cloned class or when
+     *                                 the class hasn't been written yet.
+     *    @static
+     *    @access public
+     */
+    function generate($class, $mock_class = false, $methods = false) {
+        $generator = new MockGenerator($class, $mock_class);
+        return $generator->generateSubclass($methods);
+    }
 
-        /**
-         *    Uses a stack trace to find the line of an assertion.
-         *    @access public
-         *    @static
-         */
-        function getExpectationLine() {
-            $trace = new SimpleStackTrace(array('expect'));
-            return $trace->traceMethod();
-        }
+    /**
+     *    Generates a version of a class with selected
+     *    methods mocked only. Inherits the old class
+     *    and chains the mock methods of an aggregated
+     *    mock object.
+     *    @param string $class            Class to clone.
+     *    @param string $mock_class       New class name.
+     *    @param array $methods           Methods to be overridden
+     *                                    with mock versions.
+     *    @static
+     *    @access public
+     */
+    function generatePartial($class, $mock_class, $methods) {
+        $generator = new MockGenerator($class, $mock_class);
+        return $generator->generatePartial($methods);
     }
 
     /**
-     *    @deprecated
+     *    Uses a stack trace to find the line of an assertion.
+     *    @access public
+     *    @static
      */
-    class Stub extends Mock {
+    function getExpectationLine() {
+        $trace = new SimpleStackTrace(array('expect'));
+        return $trace->traceMethod();
     }
+}
+
+/**
+ *    @package  SimpleTest
+ *    @subpackage   MockObjects
+ *    @deprecated
+ */
+class Stub extends Mock {
+}
+
+/**
+ *    Service class for code generation of mock objects.
+ *    @package SimpleTest
+ *    @subpackage MockObjects
+ */
+class MockGenerator {
+    var $_class;
+    var $_mock_class;
+    var $_mock_base;
+    var $_reflection;
 
     /**
-     *    Service class for code generation of mock objects.
-        *    @package SimpleTest
-        *    @subpackage MockObjects
+     *    Builds initial reflection object.
+     *    @param string $class        Class to be mocked.
+     *    @param string $mock_class   New class with identical interface,
+     *                                but no behaviour.
      */
-    class MockGenerator {
-        var $_class;
-        var $_mock_class;
-        var $_mock_base;
-        var $_reflection;
+    function MockGenerator($class, $mock_class) {
+        $this->_class = $class;
+        $this->_mock_class = $mock_class;
+        if (! $this->_mock_class) {
+            $this->_mock_class = 'Mock' . $this->_class;
+        }
+        $this->_mock_base = SimpleTest::getMockBaseClass();
+        $this->_reflection = new SimpleReflection($this->_class);
+    }
 
-        function MockGenerator($class, $mock_class) {
-            $this->_class = $class;
-            $this->_mock_class = $mock_class;
-            $this->_mock_base = SimpleTest::getMockBaseClass();
-            $this->_reflection = new SimpleReflection($this->_class);
+    /**
+     *    Clones a class' interface and creates a mock version
+     *    that can have return values and expectations set.
+     *    @param array $methods        Additional methods to add beyond
+     *                                 those in th cloned class. Use this
+     *                                 to emulate the dynamic addition of
+     *                                 methods in the cloned class or when
+     *                                 the class hasn't been written yet.
+     *    @access public
+     */
+    function generate($methods) {
+        if (! $this->_reflection->classOrInterfaceExists()) {
+            return false;
         }
-
-        /**
-         *    Clones a class' interface and creates a mock version
-         *    that can have return values and expectations set.
-         *    @param array $methods        Additional methods to add beyond
-         *                                 those in th cloned class. Use this
-         *                                 to emulate the dynamic addition of
-         *                                 methods in the cloned class or when
-         *                                 the class hasn't been written yet.
-         *    @access public
-         */
-        function generate($methods) {
-            if (! $this->_reflection->classOrInterfaceExists()) {
-                return false;
-            }
-            if (! $this->_mock_class) {
-                $this->_mock_class = 'Mock' . $this->_class;
-            }
-            $mock_reflection = new SimpleReflection($this->_mock_class);
-            if ($mock_reflection->classExistsSansAutoload()) {
-                return false;
-            }
-            return eval(
-                    $this->_createClassCode($methods ? $methods : array()) .
-                    " return true;");
+        $mock_reflection = new SimpleReflection($this->_mock_class);
+        if ($mock_reflection->classExistsSansAutoload()) {
+            return false;
         }
+        $code = $this->_createClassCode($methods ? $methods : array());
+        return eval("$code return \$code;");
+    }
+    
+    /**
+     *    Subclasses a class and overrides every method with a mock one
+     *    that can have return values and expectations set. Chains
+     *    to an aggregated SimpleMock.
+     *    @param array $methods        Additional methods to add beyond
+     *                                 those in the cloned class. Use this
+     *                                 to emulate the dynamic addition of
+     *                                 methods in the cloned class or when
+     *                                 the class hasn't been written yet.
+     *    @access public
+     */
+    function generateSubclass($methods) {
+        if (! $this->_reflection->classOrInterfaceExists()) {
+            return false;
+        }
+        $mock_reflection = new SimpleReflection($this->_mock_class);
+        if ($mock_reflection->classExistsSansAutoload()) {
+            return false;
+        }
+        if ($this->_reflection->isInterface() || $this->_reflection->hasFinal()) {
+            $code = $this->_createClassCode($methods ? $methods : array());
+            return eval("$code return \$code;");
+        } else {
+            $code = $this->_createSubclassCode($methods ? $methods : array());
+            return eval("$code return \$code;");
+        }
+    }
 
-        /**
-         *    Generates a version of a class with selected
-         *    methods mocked only. Inherits the old class
-         *    and chains the mock methods of an aggregated
-         *    mock object.
-         *    @param array $methods           Methods to be overridden
-         *                                    with mock versions.
-         *    @access public
-         */
-        function generatePartial($methods) {
-            if (! $this->_reflection->classExists($this->_class)) {
-                return false;
-            }
-            $mock_reflection = new SimpleReflection($this->_mock_class);
-            if ($mock_reflection->classExistsSansAutoload()) {
-                trigger_error('Partial mock class [' . $this->_mock_class . '] already exists');
-                return false;
-            }
-            return eval($this->_extendClassCode($methods));
+    /**
+     *    Generates a version of a class with selected
+     *    methods mocked only. Inherits the old class
+     *    and chains the mock methods of an aggregated
+     *    mock object.
+     *    @param array $methods           Methods to be overridden
+     *                                    with mock versions.
+     *    @access public
+     */
+    function generatePartial($methods) {
+        if (! $this->_reflection->classExists($this->_class)) {
+            return false;
         }
+        $mock_reflection = new SimpleReflection($this->_mock_class);
+        if ($mock_reflection->classExistsSansAutoload()) {
+            trigger_error('Partial mock class [' . $this->_mock_class . '] already exists');
+            return false;
+        }
+        $code = $this->_extendClassCode($methods);
+        return eval("$code return \$code;");
+    }
 
-        /**
-         *    The new mock class code as a string.
-         *    @param array $methods          Additional methods.
-         *    @return string                 Code for new mock class.
-         *    @access private
-         */
-        function _createClassCode($methods) {
-            $implements = '';
-            $interfaces = $this->_reflection->getInterfaces();
-            if (function_exists('spl_classes')) {
-               $interfaces = array_diff($interfaces, array('Traversable'));
-            }
-            if (count($interfaces) > 0) {
-               $implements = 'implements ' . implode(', ', $interfaces);
-            }
-            $code = "class " . $this->_mock_class . " extends " . $this->_mock_base . " $implements {\n";
-            $code .= "    function " . $this->_mock_class . "() {\n";
+    /**
+     *    The new mock class code as a string.
+     *    @param array $methods          Additional methods.
+     *    @return string                 Code for new mock class.
+     *    @access private
+     */
+    function _createClassCode($methods) {
+        $implements = '';
+        $interfaces = $this->_reflection->getInterfaces();
+        if (function_exists('spl_classes')) {
+            $interfaces = array_diff($interfaces, array('Traversable'));
+        }
+        if (count($interfaces) > 0) {
+            $implements = 'implements ' . implode(', ', $interfaces);
+        }
+        $code = "class " . $this->_mock_class . " extends " . $this->_mock_base . " $implements {\n";
+        $code .= "    function " . $this->_mock_class . "() {\n";
+        $code .= "        \$this->" . $this->_mock_base . "();\n";
+        $code .= "    }\n";
+        if (in_array('__construct', $this->_reflection->getMethods())) {
+            $code .= "    " . $this->_reflection->getSignature('__construct') . " {\n";
             $code .= "        \$this->" . $this->_mock_base . "();\n";
             $code .= "    }\n";
-            $code .= $this->_createHandlerCode($methods);
-            $code .= "}\n";
-            return $code;
         }
+        $code .= $this->_createHandlerCode($methods);
+        $code .= "}\n";
+        return $code;
+    }
+
+    /**
+     *    The new mock class code as a string. The mock will
+     *    be a subclass of the original mocked class.
+     *    @param array $methods          Additional methods.
+     *    @return string                 Code for new mock class.
+     *    @access private
+     */
+    function _createSubclassCode($methods) {
+        $code  = "class " . $this->_mock_class . " extends " . $this->_class . " {\n";
+        $code .= "    var \$_mock;\n";
+        $code .= $this->_addMethodList(array_merge($methods, $this->_reflection->getMethods()));
+        $code .= "\n";
+        $code .= "    function " . $this->_mock_class . "() {\n";
+        $code .= "        \$this->_mock = &new " . $this->_mock_base . "();\n";
+        $code .= "        \$this->_mock->disableExpectationNameChecks();\n";
+        $code .= "    }\n";
+        $code .= $this->_chainMockReturns();
+        $code .= $this->_chainMockExpectations();
+        $code .= $this->_chainThrowMethods();
+        $code .= $this->_overrideMethods($this->_reflection->getMethods());
+        $code .= $this->_createNewMethodCode($methods);
+        $code .= "}\n";
+        return $code;
+    }
 
-        /**
-         *    The extension class code as a string. The class
-         *    composites a mock object and chains mocked methods
-         *    to it.
-         *    @param array  $methods       Mocked methods.
-         *    @return string               Code for a new class.
-         *    @access private
-         */
-        function _extendClassCode($methods) {
-            $code  = "class " . $this->_mock_class . " extends " . $this->_class . " {\n";
-            $code .= "    var \$_mock;\n";
-            $code .= $this->_addMethodList($methods);
-            $code .= "\n";
-            $code .= "    function " . $this->_mock_class . "() {\n";
-            $code .= "        \$this->_mock = &new " . $this->_mock_base . "();\n";
-            $code .= "        \$this->_mock->disableExpectationNameChecks();\n";
+    /**
+     *    The extension class code as a string. The class
+     *    composites a mock object and chains mocked methods
+     *    to it.
+     *    @param array  $methods       Mocked methods.
+     *    @return string               Code for a new class.
+     *    @access private
+     */
+    function _extendClassCode($methods) {
+        $code  = "class " . $this->_mock_class . " extends " . $this->_class . " {\n";
+        $code .= "    var \$_mock;\n";
+        $code .= $this->_addMethodList($methods);
+        $code .= "\n";
+        $code .= "    function " . $this->_mock_class . "() {\n";
+        $code .= "        \$this->_mock = &new " . $this->_mock_base . "();\n";
+        $code .= "        \$this->_mock->disableExpectationNameChecks();\n";
+        $code .= "    }\n";
+        $code .= $this->_chainMockReturns();
+        $code .= $this->_chainMockExpectations();
+        $code .= $this->_chainThrowMethods();
+        $code .= $this->_overrideMethods($methods);
+        $code .= "}\n";
+        return $code;
+    }
+
+    /**
+     *    Creates code within a class to generate replaced
+     *    methods. All methods call the _invoke() handler
+     *    with the method name and the arguments in an
+     *    array.
+     *    @param array $methods    Additional methods.
+     *    @access private
+     */
+    function _createHandlerCode($methods) {
+        $code = '';
+        $methods = array_merge($methods, $this->_reflection->getMethods());
+        foreach ($methods as $method) {
+            if ($this->_isConstructor($method)) {
+                continue;
+            }
+            $mock_reflection = new SimpleReflection($this->_mock_base);
+            if (in_array($method, $mock_reflection->getMethods())) {
+                continue;
+            }
+            $code .= "    " . $this->_reflection->getSignature($method) . " {\n";
+            $code .= "        \$args = func_get_args();\n";
+            $code .= "        \$result = &\$this->_invoke(\"$method\", \$args);\n";
+            $code .= "        return \$result;\n";
             $code .= "    }\n";
-            $code .= $this->_chainMockReturns();
-            $code .= $this->_chainMockExpectations();
-            $code .= $this->_overrideMethods($methods);
-            $code .= "}\n";
-            return $code;
         }
+        return $code;
+    }
 
-        /**
-         *    Creates code within a class to generate replaced
-         *    methods. All methods call the _invoke() handler
-         *    with the method name and the arguments in an
-         *    array.
-         *    @param array $methods    Additional methods.
-         *    @access private
-         */
-        function _createHandlerCode($methods) {
-               $code = '';
-            $methods = array_merge($methods, $this->_reflection->getMethods());
-            foreach ($methods as $method) {
-                if ($this->_isConstructor($method)) {
-                    continue;
-                }
-                $mock_reflection = new SimpleReflection($this->_mock_base);
-                if (in_array($method, $mock_reflection->getMethods())) {
-                    continue;
-                }
-                $code .= "    " . $this->_reflection->getSignature($method) . " {\n";
-                $code .= "        \$args = func_get_args();\n";
-                $code .= "        \$result = &\$this->_invoke(\"$method\", \$args);\n";
-                $code .= "        return \$result;\n";
-                $code .= "    }\n";
+    /**
+     *    Creates code within a class to generate a new
+     *    methods. All methods call the _invoke() handler
+     *    on the internal mock with the method name and
+     *    the arguments in an array.
+     *    @param array $methods    Additional methods.
+     *    @access private
+     */
+    function _createNewMethodCode($methods) {
+        $code = '';
+        foreach ($methods as $method) {
+            if ($this->_isConstructor($method)) {
+                continue;
+            }
+            $mock_reflection = new SimpleReflection($this->_mock_base);
+            if (in_array($method, $mock_reflection->getMethods())) {
+                continue;
             }
-            return $code;
+            $code .= "    " . $this->_reflection->getSignature($method) . " {\n";
+            $code .= "        \$args = func_get_args();\n";
+            $code .= "        \$result = &\$this->_mock->_invoke(\"$method\", \$args);\n";
+            $code .= "        return \$result;\n";
+            $code .= "    }\n";
         }
+        return $code;
+    }
 
-        /**
-         *    Tests to see if a special PHP method is about to
-         *    be stubbed by mistake.
-         *    @param string $method    Method name.
-         *    @return boolean          True if special.
-         *    @access private
-         */
-        function _isConstructor($method) {
-            return in_array(
-                    strtolower($method),
-                    array('__construct', '__destruct', '__clone'));
-        }
+    /**
+     *    Tests to see if a special PHP method is about to
+     *    be stubbed by mistake.
+     *    @param string $method    Method name.
+     *    @return boolean          True if special.
+     *    @access private
+     */
+    function _isConstructor($method) {
+        return in_array(
+                strtolower($method),
+                array('__construct', '__destruct'));
+    }
 
-        /**
-         *    Creates a list of mocked methods for error checking.
-         *    @param array $methods       Mocked methods.
-         *    @return string              Code for a method list.
-         *    @access private
-         */
-        function _addMethodList($methods) {
-            return "    var \$_mocked_methods = array('" . implode("', '", $methods) . "');\n";
-        }
+    /**
+     *    Creates a list of mocked methods for error checking.
+     *    @param array $methods       Mocked methods.
+     *    @return string              Code for a method list.
+     *    @access private
+     */
+    function _addMethodList($methods) {
+        return "    var \$_mocked_methods = array('" .
+                implode("', '", array_map('strtolower', $methods)) .
+                "');\n";
+    }
 
-        /**
-         *    Creates code to abandon the expectation if not mocked.
-         *    @param string $alias       Parameter name of method name.
-         *    @return string             Code for bail out.
-         *    @access private
-         */
-        function _bailOutIfNotMocked($alias) {
-            $code  = "        if (! in_array($alias, \$this->_mocked_methods)) {\n";
-            $code .= "            trigger_error(\"Method [$alias] is not mocked\");\n";
-            $code .= "            \$null = null;\n";
-            $code .= "            return \$null;\n";
-            $code .= "        }\n";
-            return $code;
-        }
+    /**
+     *    Creates code to abandon the expectation if not mocked.
+     *    @param string $alias       Parameter name of method name.
+     *    @return string             Code for bail out.
+     *    @access private
+     */
+    function _bailOutIfNotMocked($alias) {
+        $code  = "        if (! in_array(strtolower($alias), \$this->_mocked_methods)) {\n";
+        $code .= "            trigger_error(\"Method [$alias] is not mocked\");\n";
+        $code .= "            \$null = null;\n";
+        $code .= "            return \$null;\n";
+        $code .= "        }\n";
+        return $code;
+    }
 
-        /**
-         *    Creates source code for chaining to the composited
-         *    mock object.
-         *    @return string           Code for mock set up.
-         *    @access private
-         */
-        function _chainMockReturns() {
-            $code  = "    function setReturnValue(\$method, \$value, \$args = false) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->setReturnValue(\$method, \$value, \$args);\n";
-            $code .= "    }\n";
-            $code .= "    function setReturnValueAt(\$timing, \$method, \$value, \$args = false) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->setReturnValueAt(\$timing, \$method, \$value, \$args);\n";
-            $code .= "    }\n";
-            $code .= "    function setReturnReference(\$method, &\$ref, \$args = false) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->setReturnReference(\$method, \$ref, \$args);\n";
-            $code .= "    }\n";
-            $code .= "    function setReturnReferenceAt(\$timing, \$method, &\$ref, \$args = false) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->setReturnReferenceAt(\$timing, \$method, \$ref, \$args);\n";
-            $code .= "    }\n";
-            return $code;
-        }
+    /**
+     *    Creates source code for chaining to the composited
+     *    mock object.
+     *    @return string           Code for mock set up.
+     *    @access private
+     */
+    function _chainMockReturns() {
+        $code  = "    function setReturnValue(\$method, \$value, \$args = false) {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->setReturnValue(\$method, \$value, \$args);\n";
+        $code .= "    }\n";
+        $code .= "    function setReturnValueAt(\$timing, \$method, \$value, \$args = false) {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->setReturnValueAt(\$timing, \$method, \$value, \$args);\n";
+        $code .= "    }\n";
+        $code .= "    function setReturnReference(\$method, &\$ref, \$args = false) {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->setReturnReference(\$method, \$ref, \$args);\n";
+        $code .= "    }\n";
+        $code .= "    function setReturnReferenceAt(\$timing, \$method, &\$ref, \$args = false) {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->setReturnReferenceAt(\$timing, \$method, \$ref, \$args);\n";
+        $code .= "    }\n";
+        return $code;
+    }
 
-        /**
-         *    Creates source code for chaining to an aggregated
-         *    mock object.
-         *    @return string                 Code for expectations.
-         *    @access private
-         */
-        function _chainMockExpectations() {
-            $code  = "    function expect(\$method, \$args = false) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->expect(\$method, \$args);\n";
-            $code .= "    }\n";
-            $code .= "    function expectArguments(\$method, \$args = false) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->expectArguments(\$method, \$args);\n";
-            $code .= "    }\n";
-            $code .= "    function expectAt(\$timing, \$method, \$args = false) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->expectArgumentsAt(\$timing, \$method, \$args);\n";
-            $code .= "    }\n";
-            $code .= "    function expectArgumentsAt(\$timing, \$method, \$args = false) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->expectArgumentsAt(\$timing, \$method, \$args);\n";
-            $code .= "    }\n";
-            $code .= "    function expectCallCount(\$method, \$count) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->expectCallCount(\$method, \$count);\n";
-            $code .= "    }\n";
-            $code .= "    function expectMaximumCallCount(\$method, \$count) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->expectMaximumCallCount(\$method, \$count);\n";
-            $code .= "    }\n";
-            $code .= "    function expectMinimumCallCount(\$method, \$count) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->expectMinimumCallCount(\$method, \$count);\n";
-            $code .= "    }\n";
-            $code .= "    function expectNever(\$method) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->expectNever(\$method);\n";
-            $code .= "    }\n";
-            $code .= "    function expectOnce(\$method, \$args = false) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->expectOnce(\$method, \$args);\n";
-            $code .= "    }\n";
-            $code .= "    function expectAtLeastOnce(\$method, \$args = false) {\n";
-            $code .= $this->_bailOutIfNotMocked("\$method");
-            $code .= "        \$this->_mock->expectAtLeastOnce(\$method, \$args);\n";
-            $code .= "    }\n";
-            $code .= "    function tally() {\n";
-            $code .= "        \$this->_mock->tally();\n";
-            $code .= "    }\n";
-            return $code;
-        }
+    /**
+     *    Creates source code for chaining to an aggregated
+     *    mock object.
+     *    @return string                 Code for expectations.
+     *    @access private
+     */
+    function _chainMockExpectations() {
+        $code  = "    function expect(\$method, \$args = false, \$msg = '%s') {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->expect(\$method, \$args, \$msg);\n";
+        $code .= "    }\n";
+        $code .= "    function expectArguments(\$method, \$args = false, \$msg = '%s') {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->expectArguments(\$method, \$args, \$msg);\n";
+        $code .= "    }\n";
+        $code .= "    function expectAt(\$timing, \$method, \$args = false, \$msg = '%s') {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->expectArgumentsAt(\$timing, \$method, \$args, \$msg);\n";
+        $code .= "    }\n";
+        $code .= "    function expectArgumentsAt(\$timing, \$method, \$args = false, \$msg = '%s') {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->expectArgumentsAt(\$timing, \$method, \$args, \$msg);\n";
+        $code .= "    }\n";
+        $code .= "    function expectCallCount(\$method, \$count) {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->expectCallCount(\$method, \$count, \$msg = '%s');\n";
+        $code .= "    }\n";
+        $code .= "    function expectMaximumCallCount(\$method, \$count, \$msg = '%s') {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->expectMaximumCallCount(\$method, \$count, \$msg = '%s');\n";
+        $code .= "    }\n";
+        $code .= "    function expectMinimumCallCount(\$method, \$count, \$msg = '%s') {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->expectMinimumCallCount(\$method, \$count, \$msg = '%s');\n";
+        $code .= "    }\n";
+        $code .= "    function expectNever(\$method) {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->expectNever(\$method);\n";
+        $code .= "    }\n";
+        $code .= "    function expectOnce(\$method, \$args = false, \$msg = '%s') {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->expectOnce(\$method, \$args, \$msg);\n";
+        $code .= "    }\n";
+        $code .= "    function expectAtLeastOnce(\$method, \$args = false, \$msg = '%s') {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->expectAtLeastOnce(\$method, \$args, \$msg);\n";
+        $code .= "    }\n";
+        $code .= "    function tally() {\n";
+        $code .= "    }\n";
+        return $code;
+    }
+    
+    /**
+     *    Adds code for chaining the throw methods.
+     *    @return string           Code for chains.
+     *    @access private
+     */
+    function _chainThrowMethods() {
+        $code  = "    function throwOn(\$method, \$exception = false, \$args = false) {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->throwOn(\$method, \$exception, \$args);\n";
+        $code .= "    }\n";
+        $code .= "    function throwAt(\$timing, \$method, \$exception = false, \$args = false) {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->throwAt(\$timing, \$method, \$exception, \$args);\n";
+        $code .= "    }\n";
+        $code .= "    function errorOn(\$method, \$error = 'A mock error', \$args = false, \$severity = E_USER_ERROR) {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->errorOn(\$method, \$error, \$args, \$severity);\n";
+        $code .= "    }\n";
+        $code .= "    function errorAt(\$timing, \$method, \$error = 'A mock error', \$args = false, \$severity = E_USER_ERROR) {\n";
+        $code .= $this->_bailOutIfNotMocked("\$method");
+        $code .= "        \$this->_mock->errorAt(\$timing, \$method, \$error, \$args, \$severity);\n";
+        $code .= "    }\n";
+        return $code;
+    }
 
-        /**
-         *    Creates source code to override a list of methods
-         *    with mock versions.
-         *    @param array $methods    Methods to be overridden
-         *                             with mock versions.
-         *    @return string           Code for overridden chains.
-         *    @access private
-         */
-        function _overrideMethods($methods) {
-            $code = "";
-            foreach ($methods as $method) {
-                $code .= "    " . $this->_reflection->getSignature($method) . " {\n";
-                $code .= "        \$args = func_get_args();\n";
-                $code .= "        \$result = &\$this->_mock->_invoke(\"$method\", \$args);\n";
-                $code .= "        return \$result;\n";
-                $code .= "    }\n";
+    /**
+     *    Creates source code to override a list of methods
+     *    with mock versions.
+     *    @param array $methods    Methods to be overridden
+     *                             with mock versions.
+     *    @return string           Code for overridden chains.
+     *    @access private
+     */
+    function _overrideMethods($methods) {
+        $code = "";
+        foreach ($methods as $method) {
+            if ($this->_isConstructor($method)) {
+                continue;
             }
-            return $code;
+            $code .= "    " . $this->_reflection->getSignature($method) . " {\n";
+            $code .= "        \$args = func_get_args();\n";
+            $code .= "        \$result = &\$this->_mock->_invoke(\"$method\", \$args);\n";
+            $code .= "        return \$result;\n";
+            $code .= "    }\n";
         }
+        return $code;
     }
+}
 ?>
index ee3cd05a8331fd98a57452f539a675718ad4bc63..f36a9581f410d7808fe3533a3dfb9dccae78dc2e 100644 (file)
 <?php
+/**
+ *  Base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage WebTester
+ *  @version    $Id$
+ */
+
+/**#@+
+    *   include other SimpleTest class files
+    */
+require_once(dirname(__FILE__) . '/http.php');
+require_once(dirname(__FILE__) . '/parser.php');
+require_once(dirname(__FILE__) . '/tag.php');
+require_once(dirname(__FILE__) . '/form.php');
+require_once(dirname(__FILE__) . '/selector.php');
+/**#@-*/
+
+/**
+ *    Creates tags and widgets given HTML tag
+ *    attributes.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleTagBuilder {
+
     /**
-     * Base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     WebTester
-     * @version        $Id$
-     */
-
-    /**#@+
-     * include other SimpleTest class files
-     */
-    require_once(dirname(__FILE__) . '/http.php');
-    require_once(dirname(__FILE__) . '/parser.php');
-    require_once(dirname(__FILE__) . '/tag.php');
-    require_once(dirname(__FILE__) . '/form.php');
-    require_once(dirname(__FILE__) . '/selector.php');
-    /**#@-*/
-
-    /**
-     *    Creates tags and widgets given HTML tag
-     *    attributes.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleTagBuilder {
-
-        /**
-         *    Factory for the tag objects. Creates the
-         *    appropriate tag object for the incoming tag name
-         *    and attributes.
-         *    @param string $name        HTML tag name.
-         *    @param hash $attributes    Element attributes.
-         *    @return SimpleTag          Tag object.
-         *    @access public
-         */
-        function createTag($name, $attributes) {
-            static $map = array(
-                    'a' => 'SimpleAnchorTag',
-                    'title' => 'SimpleTitleTag',
-                    'button' => 'SimpleButtonTag',
-                    'textarea' => 'SimpleTextAreaTag',
-                    'option' => 'SimpleOptionTag',
-                    'label' => 'SimpleLabelTag',
-                    'form' => 'SimpleFormTag',
-                    'frame' => 'SimpleFrameTag');
-            $attributes = $this->_keysToLowerCase($attributes);
-            if (array_key_exists($name, $map)) {
-                $tag_class = $map[$name];
-                return new $tag_class($attributes);
-            } elseif ($name == 'select') {
-                return $this->_createSelectionTag($attributes);
-            } elseif ($name == 'input') {
-                return $this->_createInputTag($attributes);
-            }
-            return new SimpleTag($name, $attributes);
-        }
-
-        /**
-         *    Factory for selection fields.
-         *    @param hash $attributes    Element attributes.
-         *    @return SimpleTag          Tag object.
-         *    @access protected
-         */
-        function _createSelectionTag($attributes) {
-            if (isset($attributes['multiple'])) {
-                return new MultipleSelectionTag($attributes);
-            }
-            return new SimpleSelectionTag($attributes);
-        }
-
-        /**
-         *    Factory for input tags.
-         *    @param hash $attributes    Element attributes.
-         *    @return SimpleTag          Tag object.
-         *    @access protected
-         */
-        function _createInputTag($attributes) {
-            if (! isset($attributes['type'])) {
-                return new SimpleTextTag($attributes);
-            }
-            $type = strtolower(trim($attributes['type']));
-            $map = array(
-                    'submit' => 'SimpleSubmitTag',
-                    'image' => 'SimpleImageSubmitTag',
-                    'checkbox' => 'SimpleCheckboxTag',
-                    'radio' => 'SimpleRadioButtonTag',
-                    'text' => 'SimpleTextTag',
-                    'hidden' => 'SimpleTextTag',
-                    'password' => 'SimpleTextTag',
-                    'file' => 'SimpleUploadTag');
-            if (array_key_exists($type, $map)) {
-                $tag_class = $map[$type];
-                return new $tag_class($attributes);
-            }
-            return false;
+     *    Factory for the tag objects. Creates the
+     *    appropriate tag object for the incoming tag name
+     *    and attributes.
+     *    @param string $name        HTML tag name.
+     *    @param hash $attributes    Element attributes.
+     *    @return SimpleTag          Tag object.
+     *    @access public
+     */
+    function createTag($name, $attributes) {
+        static $map = array(
+                'a' => 'SimpleAnchorTag',
+                'title' => 'SimpleTitleTag',
+                'base' => 'SimpleBaseTag',
+                'button' => 'SimpleButtonTag',
+                'textarea' => 'SimpleTextAreaTag',
+                'option' => 'SimpleOptionTag',
+                'label' => 'SimpleLabelTag',
+                'form' => 'SimpleFormTag',
+                'frame' => 'SimpleFrameTag');
+        $attributes = $this->_keysToLowerCase($attributes);
+        if (array_key_exists($name, $map)) {
+            $tag_class = $map[$name];
+            return new $tag_class($attributes);
+        } elseif ($name == 'select') {
+            return $this->_createSelectionTag($attributes);
+        } elseif ($name == 'input') {
+            return $this->_createInputTag($attributes);
+        }
+        return new SimpleTag($name, $attributes);
+    }
+
+    /**
+     *    Factory for selection fields.
+     *    @param hash $attributes    Element attributes.
+     *    @return SimpleTag          Tag object.
+     *    @access protected
+     */
+    function _createSelectionTag($attributes) {
+        if (isset($attributes['multiple'])) {
+            return new MultipleSelectionTag($attributes);
         }
+        return new SimpleSelectionTag($attributes);
+    }
 
-        /**
-         *    Make the keys lower case for case insensitive look-ups.
-         *    @param hash $map   Hash to convert.
-         *    @return hash       Unchanged values, but keys lower case.
-         *    @access private
-         */
-        function _keysToLowerCase($map) {
-            $lower = array();
-            foreach ($map as $key => $value) {
-                $lower[strtolower($key)] = $value;
-            }
-            return $lower;
-        }
-    }
-
-    /**
-     *    SAX event handler. Maintains a list of
-     *    open tags and dispatches them as they close.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimplePageBuilder extends SimpleSaxListener {
-        var $_tags;
-        var $_page;
-        var $_private_content_tag;
-
-        /**
-         *    Sets the builder up empty.
-         *    @access public
-         */
-        function SimplePageBuilder() {
-            $this->SimpleSaxListener();
-        }
-        
-        /**
-         *    Frees up any references so as to allow the PHP garbage
-         *    collection from unset() to work.
-         *    @access public
-         */
-        function free() {
-            unset($this->_tags);
-            unset($this->_page);
-            unset($this->_private_content_tags);
-        }
-
-        /**
-         *    Reads the raw content and send events
-         *    into the page to be built.
-         *    @param $response SimpleHttpResponse  Fetched response.
-         *    @return SimplePage                   Newly parsed page.
-         *    @access public
-         */
-        function &parse($response) {
-            $this->_tags = array();
-            $this->_page = &$this->_createPage($response);
-            $parser = &$this->_createParser($this);
-            $parser->parse($response->getContent());
-            $this->_page->acceptPageEnd();
-            return $this->_page;
-        }
-
-        /**
-         *    Creates an empty page.
-         *    @return SimplePage        New unparsed page.
-         *    @access protected
-         */
-        function &_createPage($response) {
-            $page = &new SimplePage($response);
-            return $page;
-        }
-
-        /**
-         *    Creates the parser used with the builder.
-         *    @param $listener SimpleSaxListener   Target of parser.
-         *    @return SimpleSaxParser              Parser to generate
-         *                                         events for the builder.
-         *    @access protected
-         */
-        function &_createParser(&$listener) {
-            $parser = &new SimpleHtmlSaxParser($listener);
-            return $parser;
-        }
-        
-        /**
-         *    Start of element event. Opens a new tag.
-         *    @param string $name         Element name.
-         *    @param hash $attributes     Attributes without content
-         *                                are marked as true.
-         *    @return boolean             False on parse error.
-         *    @access public
-         */
-        function startElement($name, $attributes) {
-            $factory = &new SimpleTagBuilder();
-            $tag = $factory->createTag($name, $attributes);
-            if (! $tag) {
-                return true;
-            }
-            if ($tag->getTagName() == 'label') {
-                $this->_page->acceptLabelStart($tag);
-                $this->_openTag($tag);
-                return true;
-            }
-            if ($tag->getTagName() == 'form') {
-                $this->_page->acceptFormStart($tag);
-                return true;
-            }
-            if ($tag->getTagName() == 'frameset') {
-                $this->_page->acceptFramesetStart($tag);
-                return true;
-            }
-            if ($tag->getTagName() == 'frame') {
-                $this->_page->acceptFrame($tag);
-                return true;
-            }
-            if ($tag->isPrivateContent() && ! isset($this->_private_content_tag)) {
-                $this->_private_content_tag = &$tag;
-            }
-            if ($tag->expectEndTag()) {
-                $this->_openTag($tag);
-                return true;
-            }
-            $this->_page->acceptTag($tag);
-            return true;
+    /**
+     *    Factory for input tags.
+     *    @param hash $attributes    Element attributes.
+     *    @return SimpleTag          Tag object.
+     *    @access protected
+     */
+    function _createInputTag($attributes) {
+        if (! isset($attributes['type'])) {
+            return new SimpleTextTag($attributes);
+        }
+        $type = strtolower(trim($attributes['type']));
+        $map = array(
+                'submit' => 'SimpleSubmitTag',
+                'image' => 'SimpleImageSubmitTag',
+                'checkbox' => 'SimpleCheckboxTag',
+                'radio' => 'SimpleRadioButtonTag',
+                'text' => 'SimpleTextTag',
+                'hidden' => 'SimpleTextTag',
+                'password' => 'SimpleTextTag',
+                'file' => 'SimpleUploadTag');
+        if (array_key_exists($type, $map)) {
+            $tag_class = $map[$type];
+            return new $tag_class($attributes);
+        }
+        return false;
+    }
+
+    /**
+     *    Make the keys lower case for case insensitive look-ups.
+     *    @param hash $map   Hash to convert.
+     *    @return hash       Unchanged values, but keys lower case.
+     *    @access private
+     */
+    function _keysToLowerCase($map) {
+        $lower = array();
+        foreach ($map as $key => $value) {
+            $lower[strtolower($key)] = $value;
         }
+        return $lower;
+    }
+}
+
+/**
+ *    SAX event handler. Maintains a list of
+ *    open tags and dispatches them as they close.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimplePageBuilder extends SimpleSaxListener {
+    var $_tags;
+    var $_page;
+    var $_private_content_tag;
 
-        /**
-         *    End of element event.
-         *    @param string $name        Element name.
-         *    @return boolean            False on parse error.
-         *    @access public
-         */
-        function endElement($name) {
-            if ($name == 'label') {
-                $this->_page->acceptLabelEnd();
-                return true;
-            }
-            if ($name == 'form') {
-                $this->_page->acceptFormEnd();
-                return true;
-            }
-            if ($name == 'frameset') {
-                $this->_page->acceptFramesetEnd();
-                return true;
-            }
-            if ($this->_hasNamedTagOnOpenTagStack($name)) {
-                $tag = array_pop($this->_tags[$name]);
-                if ($tag->isPrivateContent() && $this->_private_content_tag->getTagName() == $name) {
-                    unset($this->_private_content_tag);
-                }
-                $this->_addContentTagToOpenTags($tag);
-                $this->_page->acceptTag($tag);
-                return true;
-            }
+    /**
+     *    Sets the builder up empty.
+     *    @access public
+     */
+    function SimplePageBuilder() {
+        $this->SimpleSaxListener();
+    }
+    
+    /**
+     *    Frees up any references so as to allow the PHP garbage
+     *    collection from unset() to work.
+     *    @access public
+     */
+    function free() {
+        unset($this->_tags);
+        unset($this->_page);
+        unset($this->_private_content_tags);
+    }
+
+    /**
+     *    Reads the raw content and send events
+     *    into the page to be built.
+     *    @param $response SimpleHttpResponse  Fetched response.
+     *    @return SimplePage                   Newly parsed page.
+     *    @access public
+     */
+    function &parse($response) {
+        $this->_tags = array();
+        $this->_page = &$this->_createPage($response);
+        $parser = &$this->_createParser($this);
+        $parser->parse($response->getContent());
+        $this->_page->acceptPageEnd();
+        return $this->_page;
+    }
+
+    /**
+     *    Creates an empty page.
+     *    @return SimplePage        New unparsed page.
+     *    @access protected
+     */
+    function &_createPage($response) {
+        $page = &new SimplePage($response);
+        return $page;
+    }
+
+    /**
+     *    Creates the parser used with the builder.
+     *    @param $listener SimpleSaxListener   Target of parser.
+     *    @return SimpleSaxParser              Parser to generate
+     *                                         events for the builder.
+     *    @access protected
+     */
+    function &_createParser(&$listener) {
+        $parser = &new SimpleHtmlSaxParser($listener);
+        return $parser;
+    }
+    
+    /**
+     *    Start of element event. Opens a new tag.
+     *    @param string $name         Element name.
+     *    @param hash $attributes     Attributes without content
+     *                                are marked as true.
+     *    @return boolean             False on parse error.
+     *    @access public
+     */
+    function startElement($name, $attributes) {
+        $factory = &new SimpleTagBuilder();
+        $tag = $factory->createTag($name, $attributes);
+        if (! $tag) {
+            return true;
+        }
+        if ($tag->getTagName() == 'label') {
+            $this->_page->acceptLabelStart($tag);
+            $this->_openTag($tag);
+            return true;
+        }
+        if ($tag->getTagName() == 'form') {
+            $this->_page->acceptFormStart($tag);
+            return true;
+        }
+        if ($tag->getTagName() == 'frameset') {
+            $this->_page->acceptFramesetStart($tag);
             return true;
         }
+        if ($tag->getTagName() == 'frame') {
+            $this->_page->acceptFrame($tag);
+            return true;
+        }
+        if ($tag->isPrivateContent() && ! isset($this->_private_content_tag)) {
+            $this->_private_content_tag = &$tag;
+        }
+        if ($tag->expectEndTag()) {
+            $this->_openTag($tag);
+            return true;
+        }
+        $this->_page->acceptTag($tag);
+        return true;
+    }
 
-        /**
-         *    Test to see if there are any open tags awaiting
-         *    closure that match the tag name.
-         *    @param string $name        Element name.
-         *    @return boolean            True if any are still open.
-         *    @access private
-         */
-        function _hasNamedTagOnOpenTagStack($name) {
-            return isset($this->_tags[$name]) && (count($this->_tags[$name]) > 0);
-        }
-
-        /**
-         *    Unparsed, but relevant data. The data is added
-         *    to every open tag.
-         *    @param string $text        May include unparsed tags.
-         *    @return boolean            False on parse error.
-         *    @access public
-         */
-        function addContent($text) {
-            if (isset($this->_private_content_tag)) {
-                $this->_private_content_tag->addContent($text);
-            } else {
-                $this->_addContentToAllOpenTags($text);
+    /**
+     *    End of element event.
+     *    @param string $name        Element name.
+     *    @return boolean            False on parse error.
+     *    @access public
+     */
+    function endElement($name) {
+        if ($name == 'label') {
+            $this->_page->acceptLabelEnd();
+            return true;
+        }
+        if ($name == 'form') {
+            $this->_page->acceptFormEnd();
+            return true;
+        }
+        if ($name == 'frameset') {
+            $this->_page->acceptFramesetEnd();
+            return true;
+        }
+        if ($this->_hasNamedTagOnOpenTagStack($name)) {
+            $tag = array_pop($this->_tags[$name]);
+            if ($tag->isPrivateContent() && $this->_private_content_tag->getTagName() == $name) {
+                unset($this->_private_content_tag);
             }
+            $this->_addContentTagToOpenTags($tag);
+            $this->_page->acceptTag($tag);
             return true;
         }
+        return true;
+    }
 
-        /**
-         *    Any content fills all currently open tags unless it
-         *    is part of an option tag.
-         *    @param string $text        May include unparsed tags.
-         *    @access private
-         */
-        function _addContentToAllOpenTags($text) {
-            foreach (array_keys($this->_tags) as $name) {
-                for ($i = 0, $count = count($this->_tags[$name]); $i < $count; $i++) {
-                    $this->_tags[$name][$i]->addContent($text);
-                }
-            }
+    /**
+     *    Test to see if there are any open tags awaiting
+     *    closure that match the tag name.
+     *    @param string $name        Element name.
+     *    @return boolean            True if any are still open.
+     *    @access private
+     */
+    function _hasNamedTagOnOpenTagStack($name) {
+        return isset($this->_tags[$name]) && (count($this->_tags[$name]) > 0);
+    }
+
+    /**
+     *    Unparsed, but relevant data. The data is added
+     *    to every open tag.
+     *    @param string $text        May include unparsed tags.
+     *    @return boolean            False on parse error.
+     *    @access public
+     */
+    function addContent($text) {
+        if (isset($this->_private_content_tag)) {
+            $this->_private_content_tag->addContent($text);
+        } else {
+            $this->_addContentToAllOpenTags($text);
         }
+        return true;
+    }
 
-        /**
-         *    Parsed data in tag form. The parsed tag is added
-         *    to every open tag. Used for adding options to select
-         *    fields only.
-         *    @param SimpleTag $tag        Option tags only.
-         *    @access private
-         */
-        function _addContentTagToOpenTags(&$tag) {
-            if ($tag->getTagName() != 'option') {
-                return;
-            }
-            foreach (array_keys($this->_tags) as $name) {
-                for ($i = 0, $count = count($this->_tags[$name]); $i < $count; $i++) {
-                    $this->_tags[$name][$i]->addTag($tag);
-                }
+    /**
+     *    Any content fills all currently open tags unless it
+     *    is part of an option tag.
+     *    @param string $text        May include unparsed tags.
+     *    @access private
+     */
+    function _addContentToAllOpenTags($text) {
+        foreach (array_keys($this->_tags) as $name) {
+            for ($i = 0, $count = count($this->_tags[$name]); $i < $count; $i++) {
+                $this->_tags[$name][$i]->addContent($text);
             }
         }
+    }
 
-        /**
-         *    Opens a tag for receiving content. Multiple tags
-         *    will be receiving input at the same time.
-         *    @param SimpleTag $tag        New content tag.
-         *    @access private
-         */
-        function _openTag(&$tag) {
-            $name = $tag->getTagName();
-            if (! in_array($name, array_keys($this->_tags))) {
-                $this->_tags[$name] = array();
+    /**
+     *    Parsed data in tag form. The parsed tag is added
+     *    to every open tag. Used for adding options to select
+     *    fields only.
+     *    @param SimpleTag $tag        Option tags only.
+     *    @access private
+     */
+    function _addContentTagToOpenTags(&$tag) {
+        if ($tag->getTagName() != 'option') {
+            return;
+        }
+        foreach (array_keys($this->_tags) as $name) {
+            for ($i = 0, $count = count($this->_tags[$name]); $i < $count; $i++) {
+                $this->_tags[$name][$i]->addTag($tag);
             }
-            $this->_tags[$name][] = &$tag;
-        }
-    }
-
-    /**
-     *    A wrapper for a web page.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimplePage {
-        var $_links;
-        var $_title;
-        var $_last_widget;
-        var $_label;
-        var $_left_over_labels;
-        var $_open_forms;
-        var $_complete_forms;
-        var $_frameset;
-        var $_frames;
-        var $_frameset_nesting_level;
-        var $_transport_error;
-        var $_raw;
-        var $_text;
-        var $_sent;
-        var $_headers;
-        var $_method;
-        var $_url;
-        var $_request_data;
-
-        /**
-         *    Parses a page ready to access it's contents.
-         *    @param SimpleHttpResponse $response     Result of HTTP fetch.
-         *    @access public
-         */
-        function SimplePage($response = false) {
-            $this->_links = array();
-            $this->_title = false;
-            $this->_left_over_labels = array();
-            $this->_open_forms = array();
-            $this->_complete_forms = array();
-            $this->_frameset = false;
-            $this->_frames = array();
-            $this->_frameset_nesting_level = 0;
-            $this->_text = false;
-            if ($response) {
-                $this->_extractResponse($response);
+        }
+    }
+
+    /**
+     *    Opens a tag for receiving content. Multiple tags
+     *    will be receiving input at the same time.
+     *    @param SimpleTag $tag        New content tag.
+     *    @access private
+     */
+    function _openTag(&$tag) {
+        $name = $tag->getTagName();
+        if (! in_array($name, array_keys($this->_tags))) {
+            $this->_tags[$name] = array();
+        }
+        $this->_tags[$name][] = &$tag;
+    }
+}
+
+/**
+ *    A wrapper for a web page.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimplePage {
+    var $_links;
+    var $_title;
+    var $_last_widget;
+    var $_label;
+    var $_left_over_labels;
+    var $_open_forms;
+    var $_complete_forms;
+    var $_frameset;
+    var $_frames;
+    var $_frameset_nesting_level;
+    var $_transport_error;
+    var $_raw;
+    var $_text;
+    var $_sent;
+    var $_headers;
+    var $_method;
+    var $_url;
+    var $_base = false;
+    var $_request_data;
+
+    /**
+     *    Parses a page ready to access it's contents.
+     *    @param SimpleHttpResponse $response     Result of HTTP fetch.
+     *    @access public
+     */
+    function SimplePage($response = false) {
+        $this->_links = array();
+        $this->_title = false;
+        $this->_left_over_labels = array();
+        $this->_open_forms = array();
+        $this->_complete_forms = array();
+        $this->_frameset = false;
+        $this->_frames = array();
+        $this->_frameset_nesting_level = 0;
+        $this->_text = false;
+        if ($response) {
+            $this->_extractResponse($response);
+        } else {
+            $this->_noResponse();
+        }
+    }
+
+    /**
+     *    Extracts all of the response information.
+     *    @param SimpleHttpResponse $response    Response being parsed.
+     *    @access private
+     */
+    function _extractResponse($response) {
+        $this->_transport_error = $response->getError();
+        $this->_raw = $response->getContent();
+        $this->_sent = $response->getSent();
+        $this->_headers = $response->getHeaders();
+        $this->_method = $response->getMethod();
+        $this->_url = $response->getUrl();
+        $this->_request_data = $response->getRequestData();
+    }
+
+    /**
+     *    Sets up a missing response.
+     *    @access private
+     */
+    function _noResponse() {
+        $this->_transport_error = 'No page fetched yet';
+        $this->_raw = false;
+        $this->_sent = false;
+        $this->_headers = false;
+        $this->_method = 'GET';
+        $this->_url = false;
+        $this->_request_data = false;
+    }
+
+    /**
+     *    Original request as bytes sent down the wire.
+     *    @return mixed              Sent content.
+     *    @access public
+     */
+    function getRequest() {
+        return $this->_sent;
+    }
+
+    /**
+     *    Accessor for raw text of page.
+     *    @return string        Raw unparsed content.
+     *    @access public
+     */
+    function getRaw() {
+        return $this->_raw;
+    }
+
+    /**
+     *    Accessor for plain text of page as a text browser
+     *    would see it.
+     *    @return string        Plain text of page.
+     *    @access public
+     */
+    function getText() {
+        if (! $this->_text) {
+            $this->_text = SimpleHtmlSaxParser::normalise($this->_raw);
+        }
+        return $this->_text;
+    }
+
+    /**
+     *    Accessor for raw headers of page.
+     *    @return string       Header block as text.
+     *    @access public
+     */
+    function getHeaders() {
+        if ($this->_headers) {
+            return $this->_headers->getRaw();
+        }
+        return false;
+    }
+
+    /**
+     *    Original request method.
+     *    @return string        GET, POST or HEAD.
+     *    @access public
+     */
+    function getMethod() {
+        return $this->_method;
+    }
+
+    /**
+     *    Original resource name.
+     *    @return SimpleUrl        Current url.
+     *    @access public
+     */
+    function getUrl() {
+        return $this->_url;
+    }
+
+    /**
+     *    Base URL if set via BASE tag page url otherwise
+     *    @return SimpleUrl        Base url.
+     *    @access public
+     */
+    function getBaseUrl() {
+        return $this->_base;
+    }
+
+    /**
+     *    Original request data.
+     *    @return mixed              Sent content.
+     *    @access public
+     */
+    function getRequestData() {
+        return $this->_request_data;
+    }
+
+    /**
+     *    Accessor for last error.
+     *    @return string        Error from last response.
+     *    @access public
+     */
+    function getTransportError() {
+        return $this->_transport_error;
+    }
+
+    /**
+     *    Accessor for current MIME type.
+     *    @return string    MIME type as string; e.g. 'text/html'
+     *    @access public
+     */
+    function getMimeType() {
+        if ($this->_headers) {
+            return $this->_headers->getMimeType();
+        }
+        return false;
+    }
+
+    /**
+     *    Accessor for HTTP response code.
+     *    @return integer    HTTP response code received.
+     *    @access public
+     */
+    function getResponseCode() {
+        if ($this->_headers) {
+            return $this->_headers->getResponseCode();
+        }
+        return false;
+    }
+
+    /**
+     *    Accessor for last Authentication type. Only valid
+     *    straight after a challenge (401).
+     *    @return string    Description of challenge type.
+     *    @access public
+     */
+    function getAuthentication() {
+        if ($this->_headers) {
+            return $this->_headers->getAuthentication();
+        }
+        return false;
+    }
+
+    /**
+     *    Accessor for last Authentication realm. Only valid
+     *    straight after a challenge (401).
+     *    @return string    Name of security realm.
+     *    @access public
+     */
+    function getRealm() {
+        if ($this->_headers) {
+            return $this->_headers->getRealm();
+        }
+        return false;
+    }
+
+    /**
+     *    Accessor for current frame focus. Will be
+     *    false as no frames.
+     *    @return array    Always empty.
+     *    @access public
+     */
+    function getFrameFocus() {
+        return array();
+    }
+
+    /**
+     *    Sets the focus by index. The integer index starts from 1.
+     *    @param integer $choice    Chosen frame.
+     *    @return boolean           Always false.
+     *    @access public
+     */
+    function setFrameFocusByIndex($choice) {
+        return false;
+    }
+
+    /**
+     *    Sets the focus by name. Always fails for a leaf page.
+     *    @param string $name    Chosen frame.
+     *    @return boolean        False as no frames.
+     *    @access public
+     */
+    function setFrameFocus($name) {
+        return false;
+    }
+
+    /**
+     *    Clears the frame focus. Does nothing for a leaf page.
+     *    @access public
+     */
+    function clearFrameFocus() {
+    }
+
+    /**
+     *    Adds a tag to the page.
+     *    @param SimpleTag $tag        Tag to accept.
+     *    @access public
+     */
+    function acceptTag(&$tag) {
+        if ($tag->getTagName() == "a") {
+            $this->_addLink($tag);
+        } elseif ($tag->getTagName() == "base") {
+            $this->_setBase($tag);
+        } elseif ($tag->getTagName() == "title") {
+            $this->_setTitle($tag);
+        } elseif ($this->_isFormElement($tag->getTagName())) {
+            for ($i = 0; $i < count($this->_open_forms); $i++) {
+                $this->_open_forms[$i]->addWidget($tag);
+            }
+            $this->_last_widget = &$tag;
+        }
+    }
+
+    /**
+     *    Opens a label for a described widget.
+     *    @param SimpleFormTag $tag      Tag to accept.
+     *    @access public
+     */
+    function acceptLabelStart(&$tag) {
+        $this->_label = &$tag;
+        unset($this->_last_widget);
+    }
+
+    /**
+     *    Closes the most recently opened label.
+     *    @access public
+     */
+    function acceptLabelEnd() {
+        if (isset($this->_label)) {
+            if (isset($this->_last_widget)) {
+                $this->_last_widget->setLabel($this->_label->getText());
+                unset($this->_last_widget);
             } else {
-                $this->_noResponse();
+                $this->_left_over_labels[] = SimpleTestCompatibility::copy($this->_label);
             }
+            unset($this->_label);
         }
+    }
 
-        /**
-         *    Extracts all of the response information.
-         *    @param SimpleHttpResponse $response    Response being parsed.
-         *    @access private
-         */
-        function _extractResponse($response) {
-            $this->_transport_error = $response->getError();
-            $this->_raw = $response->getContent();
-            $this->_sent = $response->getSent();
-            $this->_headers = $response->getHeaders();
-            $this->_method = $response->getMethod();
-            $this->_url = $response->getUrl();
-            $this->_request_data = $response->getRequestData();
-        }
-
-        /**
-         *    Sets up a missing response.
-         *    @access private
-         */
-        function _noResponse() {
-            $this->_transport_error = 'No page fetched yet';
-            $this->_raw = false;
-            $this->_sent = false;
-            $this->_headers = false;
-            $this->_method = 'GET';
-            $this->_url = false;
-            $this->_request_data = false;
-        }
-
-        /**
-         *    Original request as bytes sent down the wire.
-         *    @return mixed              Sent content.
-         *    @access public
-         */
-        function getRequest() {
-            return $this->_sent;
-        }
-
-        /**
-         *    Accessor for raw text of page.
-         *    @return string        Raw unparsed content.
-         *    @access public
-         */
-        function getRaw() {
-            return $this->_raw;
-        }
-
-        /**
-         *    Accessor for plain text of page as a text browser
-         *    would see it.
-         *    @return string        Plain text of page.
-         *    @access public
-         */
-        function getText() {
-            if (! $this->_text) {
-                $this->_text = SimpleHtmlSaxParser::normalise($this->_raw);
-            }
-            return $this->_text;
+    /**
+     *    Tests to see if a tag is a possible form
+     *    element.
+     *    @param string $name     HTML element name.
+     *    @return boolean         True if form element.
+     *    @access private
+     */
+    function _isFormElement($name) {
+        return in_array($name, array('input', 'button', 'textarea', 'select'));
+    }
+
+    /**
+     *    Opens a form. New widgets go here.
+     *    @param SimpleFormTag $tag      Tag to accept.
+     *    @access public
+     */
+    function acceptFormStart(&$tag) {
+        $this->_open_forms[] = &new SimpleForm($tag, $this);
+    }
+
+    /**
+     *    Closes the most recently opened form.
+     *    @access public
+     */
+    function acceptFormEnd() {
+        if (count($this->_open_forms)) {
+            $this->_complete_forms[] = array_pop($this->_open_forms);
         }
+    }
 
-        /**
-         *    Accessor for raw headers of page.
-         *    @return string       Header block as text.
-         *    @access public
-         */
-        function getHeaders() {
-            if ($this->_headers) {
-                return $this->_headers->getRaw();
-            }
-            return false;
+    /**
+     *    Opens a frameset. A frameset may contain nested
+     *    frameset tags.
+     *    @param SimpleFramesetTag $tag      Tag to accept.
+     *    @access public
+     */
+    function acceptFramesetStart(&$tag) {
+        if (! $this->_isLoadingFrames()) {
+            $this->_frameset = &$tag;
         }
+        $this->_frameset_nesting_level++;
+    }
 
-        /**
-         *    Original request method.
-         *    @return string        GET, POST or HEAD.
-         *    @access public
-         */
-        function getMethod() {
-            return $this->_method;
-        }
-
-        /**
-         *    Original resource name.
-         *    @return SimpleUrl        Current url.
-         *    @access public
-         */
-        function getUrl() {
-            return $this->_url;
-        }
-
-        /**
-         *    Original request data.
-         *    @return mixed              Sent content.
-         *    @access public
-         */
-        function getRequestData() {
-            return $this->_request_data;
-        }
-
-        /**
-         *    Accessor for last error.
-         *    @return string        Error from last response.
-         *    @access public
-         */
-        function getTransportError() {
-            return $this->_transport_error;
-        }
-
-        /**
-         *    Accessor for current MIME type.
-         *    @return string    MIME type as string; e.g. 'text/html'
-         *    @access public
-         */
-        function getMimeType() {
-            if ($this->_headers) {
-                return $this->_headers->getMimeType();
-            }
-            return false;
+    /**
+     *    Closes the most recently opened frameset.
+     *    @access public
+     */
+    function acceptFramesetEnd() {
+        if ($this->_isLoadingFrames()) {
+            $this->_frameset_nesting_level--;
         }
+    }
 
-        /**
-         *    Accessor for HTTP response code.
-         *    @return integer    HTTP response code received.
-         *    @access public
-         */
-        function getResponseCode() {
-            if ($this->_headers) {
-                return $this->_headers->getResponseCode();
+    /**
+     *    Takes a single frame tag and stashes it in
+     *    the current frame set.
+     *    @param SimpleFrameTag $tag      Tag to accept.
+     *    @access public
+     */
+    function acceptFrame(&$tag) {
+        if ($this->_isLoadingFrames()) {
+            if ($tag->getAttribute('src')) {
+                $this->_frames[] = &$tag;
             }
-            return false;
         }
+    }
 
-        /**
-         *    Accessor for last Authentication type. Only valid
-         *    straight after a challenge (401).
-         *    @return string    Description of challenge type.
-         *    @access public
-         */
-        function getAuthentication() {
-            if ($this->_headers) {
-                return $this->_headers->getAuthentication();
-            }
+    /**
+     *    Test to see if in the middle of reading
+     *    a frameset.
+     *    @return boolean        True if inframeset.
+     *    @access private
+     */
+    function _isLoadingFrames() {
+        if (! $this->_frameset) {
             return false;
         }
+        return ($this->_frameset_nesting_level > 0);
+    }
+
+    /**
+     *    Test to see if link is an absolute one.
+     *    @param string $url     Url to test.
+     *    @return boolean        True if absolute.
+     *    @access protected
+     */
+    function _linkIsAbsolute($url) {
+        $parsed = new SimpleUrl($url);
+        return (boolean)($parsed->getScheme() && $parsed->getHost());
+    }
+
+    /**
+     *    Adds a link to the page.
+     *    @param SimpleAnchorTag $tag      Link to accept.
+     *    @access protected
+     */
+    function _addLink($tag) {
+        $this->_links[] = $tag;
+    }
 
-        /**
-         *    Accessor for last Authentication realm. Only valid
-         *    straight after a challenge (401).
-         *    @return string    Name of security realm.
-         *    @access public
-         */
-        function getRealm() {
-            if ($this->_headers) {
-                return $this->_headers->getRealm();
+    /**
+     *    Marker for end of complete page. Any work in
+     *    progress can now be closed.
+     *    @access public
+     */
+    function acceptPageEnd() {
+        while (count($this->_open_forms)) {
+            $this->_complete_forms[] = array_pop($this->_open_forms);
+        }
+        foreach ($this->_left_over_labels as $label) {
+            for ($i = 0, $count = count($this->_complete_forms); $i < $count; $i++) {
+                $this->_complete_forms[$i]->attachLabelBySelector(
+                        new SimpleById($label->getFor()),
+                        $label->getText());
             }
-            return false;
         }
+    }
 
-        /**
-         *    Accessor for current frame focus. Will be
-         *    false as no frames.
-         *    @return array    Always empty.
-         *    @access public
-         */
-        function getFrameFocus() {
-            return array();
-        }
+    /**
+     *    Test for the presence of a frameset.
+     *    @return boolean        True if frameset.
+     *    @access public
+     */
+    function hasFrames() {
+        return (boolean)$this->_frameset;
+    }
 
-        /**
-         *    Sets the focus by index. The integer index starts from 1.
-         *    @param integer $choice    Chosen frame.
-         *    @return boolean           Always false.
-         *    @access public
-         */
-        function setFrameFocusByIndex($choice) {
+    /**
+     *    Accessor for frame name and source URL for every frame that
+     *    will need to be loaded. Immediate children only.
+     *    @return boolean/array     False if no frameset or
+     *                              otherwise a hash of frame URLs.
+     *                              The key is either a numerical
+     *                              base one index or the name attribute.
+     *    @access public
+     */
+    function getFrameset() {
+        if (! $this->_frameset) {
             return false;
         }
+        $urls = array();
+        for ($i = 0; $i < count($this->_frames); $i++) {
+            $name = $this->_frames[$i]->getAttribute('name');
+            $url = new SimpleUrl($this->_frames[$i]->getAttribute('src'));
+            $urls[$name ? $name : $i + 1] = $this->expandUrl($url);
+        }
+        return $urls;
+    }
 
-        /**
-         *    Sets the focus by name. Always fails for a leaf page.
-         *    @param string $name    Chosen frame.
-         *    @return boolean        False as no frames.
-         *    @access public
-         */
-        function setFrameFocus($name) {
-            return false;
+    /**
+     *    Fetches a list of loaded frames.
+     *    @return array/string    Just the URL for a single page.
+     *    @access public
+     */
+    function getFrames() {
+        $url = $this->expandUrl($this->getUrl());
+        return $url->asString();
+    }
+
+    /**
+     *    Accessor for a list of all links.
+     *    @return array   List of urls with scheme of
+     *                    http or https and hostname.
+     *    @access public
+     */
+    function getUrls() {
+        $all = array();
+        foreach ($this->_links as $link) {
+            $url = $this->_getUrlFromLink($link);
+            $all[] = $url->asString();
         }
+        return $all;
+    }
 
-        /**
-         *    Clears the frame focus. Does nothing for a leaf page.
-         *    @access public
-         */
-        function clearFrameFocus() {
-        }
-
-        /**
-         *    Adds a tag to the page.
-         *    @param SimpleTag $tag        Tag to accept.
-         *    @access public
-         */
-        function acceptTag(&$tag) {
-            if ($tag->getTagName() == "a") {
-                $this->_addLink($tag);
-            } elseif ($tag->getTagName() == "title") {
-                $this->_setTitle($tag);
-            } elseif ($this->_isFormElement($tag->getTagName())) {
-                for ($i = 0; $i < count($this->_open_forms); $i++) {
-                    $this->_open_forms[$i]->addWidget($tag);
-                }
-                $this->_last_widget = &$tag;
+    /**
+     *    Accessor for URLs by the link label. Label will match
+     *    regardess of whitespace issues and case.
+     *    @param string $label    Text of link.
+     *    @return array           List of links with that label.
+     *    @access public
+     */
+    function getUrlsByLabel($label) {
+        $matches = array();
+        foreach ($this->_links as $link) {
+            if ($link->getText() == $label) {
+                $matches[] = $this->_getUrlFromLink($link);
             }
         }
+        return $matches;
+    }
 
-        /**
-         *    Opens a label for a described widget.
-         *    @param SimpleFormTag $tag      Tag to accept.
-         *    @access public
-         */
-        function acceptLabelStart(&$tag) {
-            $this->_label = &$tag;
-            unset($this->_last_widget);
-        }
-
-        /**
-         *    Closes the most recently opened label.
-         *    @access public
-         */
-        function acceptLabelEnd() {
-            if (isset($this->_label)) {
-                if (isset($this->_last_widget)) {
-                    $this->_last_widget->setLabel($this->_label->getText());
-                    unset($this->_last_widget);
-                } else {
-                    $this->_left_over_labels[] = SimpleTestCompatibility::copy($this->_label);
-                }
-                unset($this->_label);
+    /**
+     *    Accessor for a URL by the id attribute.
+     *    @param string $id       Id attribute of link.
+     *    @return SimpleUrl       URL with that id of false if none.
+     *    @access public
+     */
+    function getUrlById($id) {
+        foreach ($this->_links as $link) {
+            if ($link->getAttribute('id') === (string)$id) {
+                return $this->_getUrlFromLink($link);
             }
         }
+        return false;
+    }
 
-        /**
-         *    Tests to see if a tag is a possible form
-         *    element.
-         *    @param string $name     HTML element name.
-         *    @return boolean         True if form element.
-         *    @access private
-         */
-        function _isFormElement($name) {
-            return in_array($name, array('input', 'button', 'textarea', 'select'));
-        }
-
-        /**
-         *    Opens a form. New widgets go here.
-         *    @param SimpleFormTag $tag      Tag to accept.
-         *    @access public
-         */
-        function acceptFormStart(&$tag) {
-            $this->_open_forms[] = &new SimpleForm($tag, $this->getUrl());
-        }
-
-        /**
-         *    Closes the most recently opened form.
-         *    @access public
-         */
-        function acceptFormEnd() {
-            if (count($this->_open_forms)) {
-                $this->_complete_forms[] = array_pop($this->_open_forms);
-            }
+    /**
+     *    Converts a link tag into a target URL.
+     *    @param SimpleAnchor $link    Parsed link.
+     *    @return SimpleUrl            URL with frame target if any.
+     *    @access private
+     */
+    function _getUrlFromLink($link) {
+        $url = $this->expandUrl($link->getHref());
+        if ($link->getAttribute('target')) {
+            $url->setTarget($link->getAttribute('target'));
         }
+        return $url;
+    }
 
-        /**
-         *    Opens a frameset. A frameset may contain nested
-         *    frameset tags.
-         *    @param SimpleFramesetTag $tag      Tag to accept.
-         *    @access public
-         */
-        function acceptFramesetStart(&$tag) {
-            if (! $this->_isLoadingFrames()) {
-                $this->_frameset = &$tag;
-            }
-            $this->_frameset_nesting_level++;
+    /**
+     *    Expands expandomatic URLs into fully qualified
+     *    URLs.
+     *    @param SimpleUrl $url        Relative URL.
+     *    @return SimpleUrl            Absolute URL.
+     *    @access public
+     */
+    function expandUrl($url) {
+        if (! is_object($url)) {
+            $url = new SimpleUrl($url);
         }
+        $location = $this->getBaseUrl() ? $this->getBaseUrl() : new SimpleUrl();
+        return $url->makeAbsolute($location->makeAbsolute($this->getUrl()));
+    }
 
-        /**
-         *    Closes the most recently opened frameset.
-         *    @access public
-         */
-        function acceptFramesetEnd() {
-            if ($this->_isLoadingFrames()) {
-                $this->_frameset_nesting_level--;
-            }
+    /**
+     *    Sets the base url for the page.
+     *    @param SimpleTag $tag    Base URL for page.
+     *    @access protected
+     */
+    function _setBase(&$tag) {
+        $url = $tag->getAttribute('href');
+        $this->_base = new SimpleUrl($url);
+    }
+
+    /**
+     *    Sets the title tag contents.
+     *    @param SimpleTitleTag $tag    Title of page.
+     *    @access protected
+     */
+    function _setTitle(&$tag) {
+        $this->_title = &$tag;
+    }
+
+    /**
+     *    Accessor for parsed title.
+     *    @return string     Title or false if no title is present.
+     *    @access public
+     */
+    function getTitle() {
+        if ($this->_title) {
+            return $this->_title->getText();
         }
+        return false;
+    }
 
-        /**
-         *    Takes a single frame tag and stashes it in
-         *    the current frame set.
-         *    @param SimpleFrameTag $tag      Tag to accept.
-         *    @access public
-         */
-        function acceptFrame(&$tag) {
-            if ($this->_isLoadingFrames()) {
-                if ($tag->getAttribute('src')) {
-                    $this->_frames[] = &$tag;
-                }
+    /**
+     *    Finds a held form by button label. Will only
+     *    search correctly built forms.
+     *    @param SimpleSelector $selector       Button finder.
+     *    @return SimpleForm                    Form object containing
+     *                                          the button.
+     *    @access public
+     */
+    function &getFormBySubmit($selector) {
+        for ($i = 0; $i < count($this->_complete_forms); $i++) {
+            if ($this->_complete_forms[$i]->hasSubmit($selector)) {
+                return $this->_complete_forms[$i];
             }
         }
+        $null = null;
+        return $null;
+    }
 
-        /**
-         *    Test to see if in the middle of reading
-         *    a frameset.
-         *    @return boolean        True if inframeset.
-         *    @access private
-         */
-        function _isLoadingFrames() {
-            if (! $this->_frameset) {
-                return false;
-            }
-            return ($this->_frameset_nesting_level > 0);
-        }
-
-        /**
-         *    Test to see if link is an absolute one.
-         *    @param string $url     Url to test.
-         *    @return boolean        True if absolute.
-         *    @access protected
-         */
-        function _linkIsAbsolute($url) {
-            $parsed = new SimpleUrl($url);
-            return (boolean)($parsed->getScheme() && $parsed->getHost());
-        }
-
-        /**
-         *    Adds a link to the page.
-         *    @param SimpleAnchorTag $tag      Link to accept.
-         *    @access protected
-         */
-        function _addLink($tag) {
-            $this->_links[] = $tag;
-        }
-
-        /**
-         *    Marker for end of complete page. Any work in
-         *    progress can now be closed.
-         *    @access public
-         */
-        function acceptPageEnd() {
-            while (count($this->_open_forms)) {
-                $this->_complete_forms[] = array_pop($this->_open_forms);
-            }
-            foreach ($this->_left_over_labels as $label) {
-                for ($i = 0, $count = count($this->_complete_forms); $i < $count; $i++) {
-                    $this->_complete_forms[$i]->attachLabelBySelector(
-                            new SimpleById($label->getFor()),
-                            $label->getText());
-                }
+    /**
+     *    Finds a held form by image using a selector.
+     *    Will only search correctly built forms.
+     *    @param SimpleSelector $selector  Image finder.
+     *    @return SimpleForm               Form object containing
+     *                                     the image.
+     *    @access public
+     */
+    function &getFormByImage($selector) {
+        for ($i = 0; $i < count($this->_complete_forms); $i++) {
+            if ($this->_complete_forms[$i]->hasImage($selector)) {
+                return $this->_complete_forms[$i];
             }
         }
+        $null = null;
+        return $null;
+    }
 
-        /**
-         *    Test for the presence of a frameset.
-         *    @return boolean        True if frameset.
-         *    @access public
-         */
-        function hasFrames() {
-            return (boolean)$this->_frameset;
-        }
-
-        /**
-         *    Accessor for frame name and source URL for every frame that
-         *    will need to be loaded. Immediate children only.
-         *    @return boolean/array     False if no frameset or
-         *                              otherwise a hash of frame URLs.
-         *                              The key is either a numerical
-         *                              base one index or the name attribute.
-         *    @access public
-         */
-        function getFrameset() {
-            if (! $this->_frameset) {
-                return false;
-            }
-            $urls = array();
-            for ($i = 0; $i < count($this->_frames); $i++) {
-                $name = $this->_frames[$i]->getAttribute('name');
-                $url = new SimpleUrl($this->_frames[$i]->getAttribute('src'));
-                $urls[$name ? $name : $i + 1] = $url->makeAbsolute($this->getUrl());
-            }
-            return $urls;
-        }
-
-        /**
-         *    Fetches a list of loaded frames.
-         *    @return array/string    Just the URL for a single page.
-         *    @access public
-         */
-        function getFrames() {
-            $url = $this->getUrl();
-            return $url->asString();
-        }
-
-        /**
-         *    Accessor for a list of all fixed links.
-         *    @return array   List of urls with scheme of
-         *                    http or https and hostname.
-         *    @access public
-         */
-        function getAbsoluteUrls() {
-            $all = array();
-            foreach ($this->_links as $link) {
-                if ($this->_linkIsAbsolute($link->getHref())) {
-                    $all[] = $link->getHref();
-                }
-            }
-            return $all;
-        }
-
-        /**
-         *    Accessor for a list of all relative links.
-         *    @return array      List of urls without hostname.
-         *    @access public
-         */
-        function getRelativeUrls() {
-            $all = array();
-            foreach ($this->_links as $link) {
-                if (! $this->_linkIsAbsolute($link->getHref())) {
-                    $all[] = $link->getHref();
-                }
-            }
-            return $all;
-        }
-
-        /**
-         *    Accessor for URLs by the link label. Label will match
-         *    regardess of whitespace issues and case.
-         *    @param string $label    Text of link.
-         *    @return array           List of links with that label.
-         *    @access public
-         */
-        function getUrlsByLabel($label) {
-            $matches = array();
-            foreach ($this->_links as $link) {
-                if ($link->getText() == $label) {
-                    $matches[] = $this->_getUrlFromLink($link);
-                }
-            }
-            return $matches;
-        }
-
-        /**
-         *    Accessor for a URL by the id attribute.
-         *    @param string $id       Id attribute of link.
-         *    @return SimpleUrl       URL with that id of false if none.
-         *    @access public
-         */
-        function getUrlById($id) {
-            foreach ($this->_links as $link) {
-                if ($link->getAttribute('id') === (string)$id) {
-                    return $this->_getUrlFromLink($link);
-                }
+    /**
+     *    Finds a held form by the form ID. A way of
+     *    identifying a specific form when we have control
+     *    of the HTML code.
+     *    @param string $id     Form label.
+     *    @return SimpleForm    Form object containing the matching ID.
+     *    @access public
+     */
+    function &getFormById($id) {
+        for ($i = 0; $i < count($this->_complete_forms); $i++) {
+            if ($this->_complete_forms[$i]->getId() == $id) {
+                return $this->_complete_forms[$i];
             }
-            return false;
         }
+        $null = null;
+        return $null;
+    }
 
-        /**
-         *    Converts a link into a target URL.
-         *    @param SimpleAnchor $link    Parsed link.
-         *    @return SimpleUrl            URL with frame target if any.
-         *    @access private
-         */
-        function _getUrlFromLink($link) {
-            $url = $this->_makeAbsolute($link->getHref());
-            if ($link->getAttribute('target')) {
-                $url->setTarget($link->getAttribute('target'));
-            }
-            return $url;
-        }
-
-        /**
-         *    Expands expandomatic URLs into fully qualified
-         *    URLs.
-         *    @param SimpleUrl $url        Relative URL.
-         *    @return SimpleUrl            Absolute URL.
-         *    @access protected
-         */
-        function _makeAbsolute($url) {
-            if (! is_object($url)) {
-                $url = new SimpleUrl($url);
-            }
-            return $url->makeAbsolute($this->getUrl());
-        }
-
-        /**
-         *    Sets the title tag contents.
-         *    @param SimpleTitleTag $tag    Title of page.
-         *    @access protected
-         */
-        function _setTitle(&$tag) {
-            $this->_title = &$tag;
-        }
-
-        /**
-         *    Accessor for parsed title.
-         *    @return string     Title or false if no title is present.
-         *    @access public
-         */
-        function getTitle() {
-            if ($this->_title) {
-                return $this->_title->getText();
+    /**
+     *    Sets a field on each form in which the field is
+     *    available.
+     *    @param SimpleSelector $selector    Field finder.
+     *    @param string $value               Value to set field to.
+     *    @return boolean                    True if value is valid.
+     *    @access public
+     */
+    function setField($selector, $value, $position=false) {
+        $is_set = false;
+        for ($i = 0; $i < count($this->_complete_forms); $i++) {
+            if ($this->_complete_forms[$i]->setField($selector, $value, $position)) {
+                $is_set = true;
             }
-            return false;
         }
+        return $is_set;
+    }
 
-        /**
-         *    Finds a held form by button label. Will only
-         *    search correctly built forms.
-         *    @param SimpleSelector $selector       Button finder.
-         *    @return SimpleForm                    Form object containing
-         *                                          the button.
-         *    @access public
-         */
-        function &getFormBySubmit($selector) {
-            for ($i = 0; $i < count($this->_complete_forms); $i++) {
-                if ($this->_complete_forms[$i]->hasSubmit($selector)) {
-                    return $this->_complete_forms[$i];
-                }
-            }
-            $null = null;
-            return $null;
-        }
-
-        /**
-         *    Finds a held form by image using a selector.
-         *    Will only search correctly built forms.
-         *    @param SimpleSelector $selector  Image finder.
-         *    @return SimpleForm               Form object containing
-         *                                     the image.
-         *    @access public
-         */
-        function &getFormByImage($selector) {
-            for ($i = 0; $i < count($this->_complete_forms); $i++) {
-                if ($this->_complete_forms[$i]->hasImage($selector)) {
-                    return $this->_complete_forms[$i];
-                }
-            }
-            $null = null;
-            return $null;
-        }
-
-        /**
-         *    Finds a held form by the form ID. A way of
-         *    identifying a specific form when we have control
-         *    of the HTML code.
-         *    @param string $id     Form label.
-         *    @return SimpleForm    Form object containing the matching ID.
-         *    @access public
-         */
-        function &getFormById($id) {
-            for ($i = 0; $i < count($this->_complete_forms); $i++) {
-                if ($this->_complete_forms[$i]->getId() == $id) {
-                    return $this->_complete_forms[$i];
-                }
-            }
-            $null = null;
-            return $null;
-        }
-
-        /**
-         *    Sets a field on each form in which the field is
-         *    available.
-         *    @param SimpleSelector $selector    Field finder.
-         *    @param string $value               Value to set field to.
-         *    @return boolean                    True if value is valid.
-         *    @access public
-         */
-        function setField($selector, $value) {
-            $is_set = false;
-            for ($i = 0; $i < count($this->_complete_forms); $i++) {
-                if ($this->_complete_forms[$i]->setField($selector, $value)) {
-                    $is_set = true;
-                }
-            }
-            return $is_set;
-        }
-
-        /**
-         *    Accessor for a form element value within a page.
-         *    @param SimpleSelector $selector    Field finder.
-         *    @return string/boolean             A string if the field is
-         *                                       present, false if unchecked
-         *                                       and null if missing.
-         *    @access public
-         */
-        function getField($selector) {
-            for ($i = 0; $i < count($this->_complete_forms); $i++) {
-                $value = $this->_complete_forms[$i]->getValue($selector);
-                if (isset($value)) {
-                    return $value;
-                }
+    /**
+     *    Accessor for a form element value within a page.
+     *    @param SimpleSelector $selector    Field finder.
+     *    @return string/boolean             A string if the field is
+     *                                       present, false if unchecked
+     *                                       and null if missing.
+     *    @access public
+     */
+    function getField($selector) {
+        for ($i = 0; $i < count($this->_complete_forms); $i++) {
+            $value = $this->_complete_forms[$i]->getValue($selector);
+            if (isset($value)) {
+                return $value;
             }
-            return null;
         }
+        return null;
     }
-?>
+}
+?>
\ No newline at end of file
index 06977f8c21a60cabd78a260055d3c83f3929aa1f..37db3d0c9807eaadc530e7cd46229c4e53af7c76 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage MockObjects
+ *  @version    $Id$
+ */
+
+/**#@+
+ * Lexer mode stack constants
+ */
+foreach (array('LEXER_ENTER', 'LEXER_MATCHED',
+                'LEXER_UNMATCHED', 'LEXER_EXIT',
+                'LEXER_SPECIAL') as $i => $constant) {
+    if (! defined($constant)) {
+        define($constant, $i + 1);
+    }
+}
+/**#@-*/
+
+/**
+ *    Compounded regular expression. Any of
+ *    the contained patterns could match and
+ *    when one does, it's label is returned.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class ParallelRegex {
+    var $_patterns;
+    var $_labels;
+    var $_regex;
+    var $_case;
+    
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     MockObjects
-     * @version        $Id$
+     *    Constructor. Starts with no patterns.
+     *    @param boolean $case    True for case sensitive, false
+     *                            for insensitive.
+     *    @access public
      */
-
-    /**#@+
-     * Lexer mode stack constants
-     */
-    if (! defined('LEXER_ENTER')) {
-        define('LEXER_ENTER', 1);
-    }
-    if (! defined('LEXER_MATCHED')) {
-        define('LEXER_MATCHED', 2);
-    }
-    if (! defined('LEXER_UNMATCHED')) {
-        define('LEXER_UNMATCHED', 3);
-    }
-    if (! defined('LEXER_EXIT')) {
-        define('LEXER_EXIT', 4);
-    }
-    if (! defined('LEXER_SPECIAL')) {
-        define('LEXER_SPECIAL', 5);
-    }
-    /**#@-*/
-    
-    /**
-     *    Compounded regular expression. Any of
-     *    the contained patterns could match and
-     *    when one does, it's label is returned.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class ParallelRegex {
-        var $_patterns;
-        var $_labels;
-        var $_regex;
-        var $_case;
-        
-        /**
-         *    Constructor. Starts with no patterns.
-         *    @param boolean $case    True for case sensitive, false
-         *                            for insensitive.
-         *    @access public
-         */
-        function ParallelRegex($case) {
-            $this->_case = $case;
-            $this->_patterns = array();
-            $this->_labels = array();
-            $this->_regex = null;
-        }
-        
-        /**
-         *    Adds a pattern with an optional label.
-         *    @param string $pattern      Perl style regex, but ( and )
-         *                                lose the usual meaning.
-         *    @param string $label        Label of regex to be returned
-         *                                on a match.
-         *    @access public
-         */
-        function addPattern($pattern, $label = true) {
-            $count = count($this->_patterns);
-            $this->_patterns[$count] = $pattern;
-            $this->_labels[$count] = $label;
-            $this->_regex = null;
-        }
-        
-        /**
-         *    Attempts to match all patterns at once against
-         *    a string.
-         *    @param string $subject      String to match against.
-         *    @param string $match        First matched portion of
-         *                                subject.
-         *    @return boolean             True on success.
-         *    @access public
-         */
-        function match($subject, &$match) {
-            if (count($this->_patterns) == 0) {
-                return false;
-            }
-            if (! preg_match($this->_getCompoundedRegex(), $subject, $matches)) {
-                $match = '';
-                return false;
-            }
-            $match = $matches[0];
-            for ($i = 1; $i < count($matches); $i++) {
-                if ($matches[$i]) {
-                    return $this->_labels[$i - 1];
-                }
+    function ParallelRegex($case) {
+        $this->_case = $case;
+        $this->_patterns = array();
+        $this->_labels = array();
+        $this->_regex = null;
+    }
+    
+    /**
+     *    Adds a pattern with an optional label.
+     *    @param string $pattern      Perl style regex, but ( and )
+     *                                lose the usual meaning.
+     *    @param string $label        Label of regex to be returned
+     *                                on a match.
+     *    @access public
+     */
+    function addPattern($pattern, $label = true) {
+        $count = count($this->_patterns);
+        $this->_patterns[$count] = $pattern;
+        $this->_labels[$count] = $label;
+        $this->_regex = null;
+    }
+    
+    /**
+     *    Attempts to match all patterns at once against
+     *    a string.
+     *    @param string $subject      String to match against.
+     *    @param string $match        First matched portion of
+     *                                subject.
+     *    @return boolean             True on success.
+     *    @access public
+     */
+    function match($subject, &$match) {
+        if (count($this->_patterns) == 0) {
+            return false;
+        }
+        if (! preg_match($this->_getCompoundedRegex(), $subject, $matches)) {
+            $match = '';
+            return false;
+        }
+        $match = $matches[0];
+        for ($i = 1; $i < count($matches); $i++) {
+            if ($matches[$i]) {
+                return $this->_labels[$i - 1];
             }
-            return true;
         }
-        
-        /**
-         *    Compounds the patterns into a single
-         *    regular expression separated with the
-         *    "or" operator. Caches the regex.
-         *    Will automatically escape (, ) and / tokens.
-         *    @param array $patterns    List of patterns in order.
-         *    @access private
-         */
-        function _getCompoundedRegex() {
-            if ($this->_regex == null) {
-                for ($i = 0, $count = count($this->_patterns); $i < $count; $i++) {
-                    $this->_patterns[$i] = '(' . str_replace(
-                            array('/', '(', ')'),
-                            array('\/', '\(', '\)'),
-                            $this->_patterns[$i]) . ')';
-                }
-                $this->_regex = "/" . implode("|", $this->_patterns) . "/" . $this->_getPerlMatchingFlags();
+        return true;
+    }
+    
+    /**
+     *    Compounds the patterns into a single
+     *    regular expression separated with the
+     *    "or" operator. Caches the regex.
+     *    Will automatically escape (, ) and / tokens.
+     *    @param array $patterns    List of patterns in order.
+     *    @access private
+     */
+    function _getCompoundedRegex() {
+        if ($this->_regex == null) {
+            for ($i = 0, $count = count($this->_patterns); $i < $count; $i++) {
+                $this->_patterns[$i] = '(' . str_replace(
+                        array('/', '(', ')'),
+                        array('\/', '\(', '\)'),
+                        $this->_patterns[$i]) . ')';
             }
-            return $this->_regex;
-        }
-        
-        /**
-         *    Accessor for perl regex mode flags to use.
-         *    @return string       Perl regex flags.
-         *    @access private
-         */
-        function _getPerlMatchingFlags() {
-            return ($this->_case ? "msS" : "msSi");
+            $this->_regex = "/" . implode("|", $this->_patterns) . "/" . $this->_getPerlMatchingFlags();
         }
+        return $this->_regex;
     }
     
     /**
-     *    States for a stack machine.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Accessor for perl regex mode flags to use.
+     *    @return string       Perl regex flags.
+     *    @access private
      */
-    class SimpleStateStack {
-        var $_stack;
-        
-        /**
-         *    Constructor. Starts in named state.
-         *    @param string $start        Starting state name.
-         *    @access public
-         */
-        function SimpleStateStack($start) {
-            $this->_stack = array($start);
-        }
-        
-        /**
-         *    Accessor for current state.
-         *    @return string       State.
-         *    @access public
-         */
-        function getCurrent() {
-            return $this->_stack[count($this->_stack) - 1];
+    function _getPerlMatchingFlags() {
+        return ($this->_case ? "msS" : "msSi");
+    }
+}
+
+/**
+ *    States for a stack machine.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleStateStack {
+    var $_stack;
+    
+    /**
+     *    Constructor. Starts in named state.
+     *    @param string $start        Starting state name.
+     *    @access public
+     */
+    function SimpleStateStack($start) {
+        $this->_stack = array($start);
+    }
+    
+    /**
+     *    Accessor for current state.
+     *    @return string       State.
+     *    @access public
+     */
+    function getCurrent() {
+        return $this->_stack[count($this->_stack) - 1];
+    }
+    
+    /**
+     *    Adds a state to the stack and sets it
+     *    to be the current state.
+     *    @param string $state        New state.
+     *    @access public
+     */
+    function enter($state) {
+        array_push($this->_stack, $state);
+    }
+    
+    /**
+     *    Leaves the current state and reverts
+     *    to the previous one.
+     *    @return boolean    False if we drop off
+     *                       the bottom of the list.
+     *    @access public
+     */
+    function leave() {
+        if (count($this->_stack) == 1) {
+            return false;
         }
-        
-        /**
-         *    Adds a state to the stack and sets it
-         *    to be the current state.
-         *    @param string $state        New state.
-         *    @access public
-         */
-        function enter($state) {
-            array_push($this->_stack, $state);
+        array_pop($this->_stack);
+        return true;
+    }
+}
+
+/**
+ *    Accepts text and breaks it into tokens.
+ *    Some optimisation to make the sure the
+ *    content is only scanned by the PHP regex
+ *    parser once. Lexer modes must not start
+ *    with leading underscores.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleLexer {
+    var $_regexes;
+    var $_parser;
+    var $_mode;
+    var $_mode_handlers;
+    var $_case;
+    
+    /**
+     *    Sets up the lexer in case insensitive matching
+     *    by default.
+     *    @param SimpleSaxParser $parser  Handling strategy by
+     *                                    reference.
+     *    @param string $start            Starting handler.
+     *    @param boolean $case            True for case sensitive.
+     *    @access public
+     */
+    function SimpleLexer(&$parser, $start = "accept", $case = false) {
+        $this->_case = $case;
+        $this->_regexes = array();
+        $this->_parser = &$parser;
+        $this->_mode = &new SimpleStateStack($start);
+        $this->_mode_handlers = array($start => $start);
+    }
+    
+    /**
+     *    Adds a token search pattern for a particular
+     *    parsing mode. The pattern does not change the
+     *    current mode.
+     *    @param string $pattern      Perl style regex, but ( and )
+     *                                lose the usual meaning.
+     *    @param string $mode         Should only apply this
+     *                                pattern when dealing with
+     *                                this type of input.
+     *    @access public
+     */
+    function addPattern($pattern, $mode = "accept") {
+        if (! isset($this->_regexes[$mode])) {
+            $this->_regexes[$mode] = new ParallelRegex($this->_case);
         }
-        
-        /**
-         *    Leaves the current state and reverts
-         *    to the previous one.
-         *    @return boolean    False if we drop off
-         *                       the bottom of the list.
-         *    @access public
-         */
-        function leave() {
-            if (count($this->_stack) == 1) {
-                return false;
-            }
-            array_pop($this->_stack);
-            return true;
+        $this->_regexes[$mode]->addPattern($pattern);
+        if (! isset($this->_mode_handlers[$mode])) {
+            $this->_mode_handlers[$mode] = $mode;
         }
     }
     
     /**
-     *    Accepts text and breaks it into tokens.
-     *    Some optimisation to make the sure the
-     *    content is only scanned by the PHP regex
-     *    parser once. Lexer modes must not start
-     *    with leading underscores.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleLexer {
-        var $_regexes;
-        var $_parser;
-        var $_mode;
-        var $_mode_handlers;
-        var $_case;
-        
-        /**
-         *    Sets up the lexer in case insensitive matching
-         *    by default.
-         *    @param SimpleSaxParser $parser  Handling strategy by
-         *                                    reference.
-         *    @param string $start            Starting handler.
-         *    @param boolean $case            True for case sensitive.
-         *    @access public
-         */
-        function SimpleLexer(&$parser, $start = "accept", $case = false) {
-            $this->_case = $case;
-            $this->_regexes = array();
-            $this->_parser = &$parser;
-            $this->_mode = &new SimpleStateStack($start);
-            $this->_mode_handlers = array($start => $start);
+     *    Adds a pattern that will enter a new parsing
+     *    mode. Useful for entering parenthesis, strings,
+     *    tags, etc.
+     *    @param string $pattern      Perl style regex, but ( and )
+     *                                lose the usual meaning.
+     *    @param string $mode         Should only apply this
+     *                                pattern when dealing with
+     *                                this type of input.
+     *    @param string $new_mode     Change parsing to this new
+     *                                nested mode.
+     *    @access public
+     */
+    function addEntryPattern($pattern, $mode, $new_mode) {
+        if (! isset($this->_regexes[$mode])) {
+            $this->_regexes[$mode] = new ParallelRegex($this->_case);
         }
-        
-        /**
-         *    Adds a token search pattern for a particular
-         *    parsing mode. The pattern does not change the
-         *    current mode.
-         *    @param string $pattern      Perl style regex, but ( and )
-         *                                lose the usual meaning.
-         *    @param string $mode         Should only apply this
-         *                                pattern when dealing with
-         *                                this type of input.
-         *    @access public
-         */
-        function addPattern($pattern, $mode = "accept") {
-            if (! isset($this->_regexes[$mode])) {
-                $this->_regexes[$mode] = new ParallelRegex($this->_case);
-            }
-            $this->_regexes[$mode]->addPattern($pattern);
-            if (! isset($this->_mode_handlers[$mode])) {
-                $this->_mode_handlers[$mode] = $mode;
-            }
+        $this->_regexes[$mode]->addPattern($pattern, $new_mode);
+        if (! isset($this->_mode_handlers[$new_mode])) {
+            $this->_mode_handlers[$new_mode] = $new_mode;
         }
-        
-        /**
-         *    Adds a pattern that will enter a new parsing
-         *    mode. Useful for entering parenthesis, strings,
-         *    tags, etc.
-         *    @param string $pattern      Perl style regex, but ( and )
-         *                                lose the usual meaning.
-         *    @param string $mode         Should only apply this
-         *                                pattern when dealing with
-         *                                this type of input.
-         *    @param string $new_mode     Change parsing to this new
-         *                                nested mode.
-         *    @access public
-         */
-        function addEntryPattern($pattern, $mode, $new_mode) {
-            if (! isset($this->_regexes[$mode])) {
-                $this->_regexes[$mode] = new ParallelRegex($this->_case);
-            }
-            $this->_regexes[$mode]->addPattern($pattern, $new_mode);
-            if (! isset($this->_mode_handlers[$new_mode])) {
-                $this->_mode_handlers[$new_mode] = $new_mode;
-            }
+    }
+    
+    /**
+     *    Adds a pattern that will exit the current mode
+     *    and re-enter the previous one.
+     *    @param string $pattern      Perl style regex, but ( and )
+     *                                lose the usual meaning.
+     *    @param string $mode         Mode to leave.
+     *    @access public
+     */
+    function addExitPattern($pattern, $mode) {
+        if (! isset($this->_regexes[$mode])) {
+            $this->_regexes[$mode] = new ParallelRegex($this->_case);
         }
-        
-        /**
-         *    Adds a pattern that will exit the current mode
-         *    and re-enter the previous one.
-         *    @param string $pattern      Perl style regex, but ( and )
-         *                                lose the usual meaning.
-         *    @param string $mode         Mode to leave.
-         *    @access public
-         */
-        function addExitPattern($pattern, $mode) {
-            if (! isset($this->_regexes[$mode])) {
-                $this->_regexes[$mode] = new ParallelRegex($this->_case);
-            }
-            $this->_regexes[$mode]->addPattern($pattern, "__exit");
-            if (! isset($this->_mode_handlers[$mode])) {
-                $this->_mode_handlers[$mode] = $mode;
-            }
+        $this->_regexes[$mode]->addPattern($pattern, "__exit");
+        if (! isset($this->_mode_handlers[$mode])) {
+            $this->_mode_handlers[$mode] = $mode;
         }
-        
-        /**
-         *    Adds a pattern that has a special mode. Acts as an entry
-         *    and exit pattern in one go, effectively calling a special
-         *    parser handler for this token only.
-         *    @param string $pattern      Perl style regex, but ( and )
-         *                                lose the usual meaning.
-         *    @param string $mode         Should only apply this
-         *                                pattern when dealing with
-         *                                this type of input.
-         *    @param string $special      Use this mode for this one token.
-         *    @access public
-         */
-        function addSpecialPattern($pattern, $mode, $special) {
-            if (! isset($this->_regexes[$mode])) {
-                $this->_regexes[$mode] = new ParallelRegex($this->_case);
-            }
-            $this->_regexes[$mode]->addPattern($pattern, "_$special");
-            if (! isset($this->_mode_handlers[$special])) {
-                $this->_mode_handlers[$special] = $special;
-            }
+    }
+    
+    /**
+     *    Adds a pattern that has a special mode. Acts as an entry
+     *    and exit pattern in one go, effectively calling a special
+     *    parser handler for this token only.
+     *    @param string $pattern      Perl style regex, but ( and )
+     *                                lose the usual meaning.
+     *    @param string $mode         Should only apply this
+     *                                pattern when dealing with
+     *                                this type of input.
+     *    @param string $special      Use this mode for this one token.
+     *    @access public
+     */
+    function addSpecialPattern($pattern, $mode, $special) {
+        if (! isset($this->_regexes[$mode])) {
+            $this->_regexes[$mode] = new ParallelRegex($this->_case);
         }
-        
-        /**
-         *    Adds a mapping from a mode to another handler.
-         *    @param string $mode        Mode to be remapped.
-         *    @param string $handler     New target handler.
-         *    @access public
-         */
-        function mapHandler($mode, $handler) {
-            $this->_mode_handlers[$mode] = $handler;
+        $this->_regexes[$mode]->addPattern($pattern, "_$special");
+        if (! isset($this->_mode_handlers[$special])) {
+            $this->_mode_handlers[$special] = $special;
         }
-        
-        /**
-         *    Splits the page text into tokens. Will fail
-         *    if the handlers report an error or if no
-         *    content is consumed. If successful then each
-         *    unparsed and parsed token invokes a call to the
-         *    held listener.
-         *    @param string $raw        Raw HTML text.
-         *    @return boolean           True on success, else false.
-         *    @access public
-         */
-        function parse($raw) {
-            if (! isset($this->_parser)) {
+    }
+    
+    /**
+     *    Adds a mapping from a mode to another handler.
+     *    @param string $mode        Mode to be remapped.
+     *    @param string $handler     New target handler.
+     *    @access public
+     */
+    function mapHandler($mode, $handler) {
+        $this->_mode_handlers[$mode] = $handler;
+    }
+    
+    /**
+     *    Splits the page text into tokens. Will fail
+     *    if the handlers report an error or if no
+     *    content is consumed. If successful then each
+     *    unparsed and parsed token invokes a call to the
+     *    held listener.
+     *    @param string $raw        Raw HTML text.
+     *    @return boolean           True on success, else false.
+     *    @access public
+     */
+    function parse($raw) {
+        if (! isset($this->_parser)) {
+            return false;
+        }
+        $length = strlen($raw);
+        while (is_array($parsed = $this->_reduce($raw))) {
+            list($raw, $unmatched, $matched, $mode) = $parsed;
+            if (! $this->_dispatchTokens($unmatched, $matched, $mode)) {
                 return false;
             }
-            $length = strlen($raw);
-            while (is_array($parsed = $this->_reduce($raw))) {
-                list($raw, $unmatched, $matched, $mode) = $parsed;
-                if (! $this->_dispatchTokens($unmatched, $matched, $mode)) {
-                    return false;
-                }
-                if ($raw === '') {
-                    return true;
-                }
-                if (strlen($raw) == $length) {
-                    return false;
-                }
-                $length = strlen($raw);
-            }
-            if (! $parsed) {
-                return false;
+            if ($raw === '') {
+                return true;
             }
-            return $this->_invokeParser($raw, LEXER_UNMATCHED);
-        }
-        
-        /**
-         *    Sends the matched token and any leading unmatched
-         *    text to the parser changing the lexer to a new
-         *    mode if one is listed.
-         *    @param string $unmatched    Unmatched leading portion.
-         *    @param string $matched      Actual token match.
-         *    @param string $mode         Mode after match. A boolean
-         *                                false mode causes no change.
-         *    @return boolean             False if there was any error
-         *                                from the parser.
-         *    @access private
-         */
-        function _dispatchTokens($unmatched, $matched, $mode = false) {
-            if (! $this->_invokeParser($unmatched, LEXER_UNMATCHED)) {
+            if (strlen($raw) == $length) {
                 return false;
             }
-            if (is_bool($mode)) {
-                return $this->_invokeParser($matched, LEXER_MATCHED);
-            }
-            if ($this->_isModeEnd($mode)) {
-                if (! $this->_invokeParser($matched, LEXER_EXIT)) {
-                    return false;
-                }
-                return $this->_mode->leave();
-            }
-            if ($this->_isSpecialMode($mode)) {
-                $this->_mode->enter($this->_decodeSpecial($mode));
-                if (! $this->_invokeParser($matched, LEXER_SPECIAL)) {
-                    return false;
-                }
-                return $this->_mode->leave();
-            }
-            $this->_mode->enter($mode);
-            return $this->_invokeParser($matched, LEXER_ENTER);
+            $length = strlen($raw);
         }
-        
-        /**
-         *    Tests to see if the new mode is actually to leave
-         *    the current mode and pop an item from the matching
-         *    mode stack.
-         *    @param string $mode    Mode to test.
-         *    @return boolean        True if this is the exit mode.
-         *    @access private
-         */
-        function _isModeEnd($mode) {
-            return ($mode === "__exit");
+        if (! $parsed) {
+            return false;
         }
-        
-        /**
-         *    Test to see if the mode is one where this mode
-         *    is entered for this token only and automatically
-         *    leaves immediately afterwoods.
-         *    @param string $mode    Mode to test.
-         *    @return boolean        True if this is the exit mode.
-         *    @access private
-         */
-        function _isSpecialMode($mode) {
-            return (strncmp($mode, "_", 1) == 0);
+        return $this->_invokeParser($raw, LEXER_UNMATCHED);
+    }
+    
+    /**
+     *    Sends the matched token and any leading unmatched
+     *    text to the parser changing the lexer to a new
+     *    mode if one is listed.
+     *    @param string $unmatched    Unmatched leading portion.
+     *    @param string $matched      Actual token match.
+     *    @param string $mode         Mode after match. A boolean
+     *                                false mode causes no change.
+     *    @return boolean             False if there was any error
+     *                                from the parser.
+     *    @access private
+     */
+    function _dispatchTokens($unmatched, $matched, $mode = false) {
+        if (! $this->_invokeParser($unmatched, LEXER_UNMATCHED)) {
+            return false;
         }
-        
-        /**
-         *    Strips the magic underscore marking single token
-         *    modes.
-         *    @param string $mode    Mode to decode.
-         *    @return string         Underlying mode name.
-         *    @access private
-         */
-        function _decodeSpecial($mode) {
-            return substr($mode, 1);
+        if (is_bool($mode)) {
+            return $this->_invokeParser($matched, LEXER_MATCHED);
         }
-        
-        /**
-         *    Calls the parser method named after the current
-         *    mode. Empty content will be ignored. The lexer
-         *    has a parser handler for each mode in the lexer.
-         *    @param string $content        Text parsed.
-         *    @param boolean $is_match      Token is recognised rather
-         *                                  than unparsed data.
-         *    @access private
-         */
-        function _invokeParser($content, $is_match) {
-            if (($content === '') || ($content === false)) {
-                return true;
+        if ($this->_isModeEnd($mode)) {
+            if (! $this->_invokeParser($matched, LEXER_EXIT)) {
+                return false;
             }
-            $handler = $this->_mode_handlers[$this->_mode->getCurrent()];
-            return $this->_parser->$handler($content, $is_match);
+            return $this->_mode->leave();
         }
-        
-        /**
-         *    Tries to match a chunk of text and if successful
-         *    removes the recognised chunk and any leading
-         *    unparsed data. Empty strings will not be matched.
-         *    @param string $raw         The subject to parse. This is the
-         *                               content that will be eaten.
-         *    @return array/boolean      Three item list of unparsed
-         *                               content followed by the
-         *                               recognised token and finally the
-         *                               action the parser is to take.
-         *                               True if no match, false if there
-         *                               is a parsing error.
-         *    @access private
-         */
-        function _reduce($raw) {
-            if ($action = $this->_regexes[$this->_mode->getCurrent()]->match($raw, $match)) {
-                $unparsed_character_count = strpos($raw, $match);
-                $unparsed = substr($raw, 0, $unparsed_character_count);
-                $raw = substr($raw, $unparsed_character_count + strlen($match));
-                return array($raw, $unparsed, $match, $action);
+        if ($this->_isSpecialMode($mode)) {
+            $this->_mode->enter($this->_decodeSpecial($mode));
+            if (! $this->_invokeParser($matched, LEXER_SPECIAL)) {
+                return false;
             }
-            return true;
+            return $this->_mode->leave();
         }
+        $this->_mode->enter($mode);
+        return $this->_invokeParser($matched, LEXER_ENTER);
     }
     
     /**
-     *    Breas HTML into SAX events.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleHtmlLexer extends SimpleLexer {
-        
-        /**
-         *    Sets up the lexer with case insensitive matching
-         *    and adds the HTML handlers.
-         *    @param SimpleSaxParser $parser  Handling strategy by
-         *                                    reference.
-         *    @access public
-         */
-        function SimpleHtmlLexer(&$parser) {
-            $this->SimpleLexer($parser, 'text');
-            $this->mapHandler('text', 'acceptTextToken');
-            $this->_addSkipping();
-            foreach ($this->_getParsedTags() as $tag) {
-                $this->_addTag($tag);
-            }
-            $this->_addInTagTokens();
-        }
-        
-        /**
-         *    List of parsed tags. Others are ignored.
-         *    @return array        List of searched for tags.
-         *    @access private
-         */
-        function _getParsedTags() {
-            return array('a', 'title', 'form', 'input', 'button', 'textarea', 'select',
-                    'option', 'frameset', 'frame', 'label');
-        }
-        
-        /**
-         *    The lexer has to skip certain sections such
-         *    as server code, client code and styles.
-         *    @access private
-         */
-        function _addSkipping() {
-            $this->mapHandler('css', 'ignore');
-            $this->addEntryPattern('<style', 'text', 'css');
-            $this->addExitPattern('</style>', 'css');
-            $this->mapHandler('js', 'ignore');
-            $this->addEntryPattern('<script', 'text', 'js');
-            $this->addExitPattern('</script>', 'js');
-            $this->mapHandler('comment', 'ignore');
-            $this->addEntryPattern('<!--', 'text', 'comment');
-            $this->addExitPattern('-->', 'comment');
-        }
-        
-        /**
-         *    Pattern matches to start and end a tag.
-         *    @param string $tag          Name of tag to scan for.
-         *    @access private
-         */
-        function _addTag($tag) {
-            $this->addSpecialPattern("</$tag>", 'text', 'acceptEndToken');
-            $this->addEntryPattern("<$tag", 'text', 'tag');
-        }
-        
-        /**
-         *    Pattern matches to parse the inside of a tag
-         *    including the attributes and their quoting.
-         *    @access private
-         */
-        function _addInTagTokens() {
-            $this->mapHandler('tag', 'acceptStartToken');
-            $this->addSpecialPattern('\s+', 'tag', 'ignore');
-            $this->_addAttributeTokens();
-            $this->addExitPattern('/>', 'tag');
-            $this->addExitPattern('>', 'tag');
-        }
-        
-        /**
-         *    Matches attributes that are either single quoted,
-         *    double quoted or unquoted.
-         *    @access private
-         */
-        function _addAttributeTokens() {
-            $this->mapHandler('dq_attribute', 'acceptAttributeToken');
-            $this->addEntryPattern('=\s*"', 'tag', 'dq_attribute');
-            $this->addPattern("\\\\\"", 'dq_attribute');
-            $this->addExitPattern('"', 'dq_attribute');
-            $this->mapHandler('sq_attribute', 'acceptAttributeToken');
-            $this->addEntryPattern("=\s*'", 'tag', 'sq_attribute');
-            $this->addPattern("\\\\'", 'sq_attribute');
-            $this->addExitPattern("'", 'sq_attribute');
-            $this->mapHandler('uq_attribute', 'acceptAttributeToken');
-            $this->addSpecialPattern('=\s*[^>\s]*', 'tag', 'uq_attribute');
-        }
+     *    Tests to see if the new mode is actually to leave
+     *    the current mode and pop an item from the matching
+     *    mode stack.
+     *    @param string $mode    Mode to test.
+     *    @return boolean        True if this is the exit mode.
+     *    @access private
+     */
+    function _isModeEnd($mode) {
+        return ($mode === "__exit");
     }
     
     /**
-     *    Converts HTML tokens into selected SAX events.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleHtmlSaxParser {
-        var $_lexer;
-        var $_listener;
-        var $_tag;
-        var $_attributes;
-        var $_current_attribute;
-        
-        /**
-         *    Sets the listener.
-         *    @param SimpleSaxListener $listener    SAX event handler.
-         *    @access public
-         */
-        function SimpleHtmlSaxParser(&$listener) {
-            $this->_listener = &$listener;
-            $this->_lexer = &$this->createLexer($this);
-            $this->_tag = '';
-            $this->_attributes = array();
-            $this->_current_attribute = '';
-        }
-        
-        /**
-         *    Runs the content through the lexer which
-         *    should call back to the acceptors.
-         *    @param string $raw      Page text to parse.
-         *    @return boolean         False if parse error.
-         *    @access public
-         */
-        function parse($raw) {
-            return $this->_lexer->parse($raw);
-        }
-        
-        /**
-         *    Sets up the matching lexer. Starts in 'text' mode.
-         *    @param SimpleSaxParser $parser    Event generator, usually $self.
-         *    @return SimpleLexer               Lexer suitable for this parser.
-         *    @access public
-         *    @static
-         */
-        function &createLexer(&$parser) {
-            $lexer = &new SimpleHtmlLexer($parser);
-            return $lexer;
-        }
-        
-        /**
-         *    Accepts a token from the tag mode. If the
-         *    starting element completes then the element
-         *    is dispatched and the current attributes
-         *    set back to empty. The element or attribute
-         *    name is converted to lower case.
-         *    @param string $token     Incoming characters.
-         *    @param integer $event    Lexer event type.
-         *    @return boolean          False if parse error.
-         *    @access public
-         */
-        function acceptStartToken($token, $event) {
-            if ($event == LEXER_ENTER) {
-                $this->_tag = strtolower(substr($token, 1));
-                return true;
-            }
-            if ($event == LEXER_EXIT) {
-                $success = $this->_listener->startElement(
-                        $this->_tag,
-                        $this->_attributes);
-                $this->_tag = '';
-                $this->_attributes = array();
-                return $success;
-            }
-            if ($token != '=') {
-                $this->_current_attribute = strtolower(SimpleHtmlSaxParser::decodeHtml($token));
-                $this->_attributes[$this->_current_attribute] = '';
-            }
+     *    Test to see if the mode is one where this mode
+     *    is entered for this token only and automatically
+     *    leaves immediately afterwoods.
+     *    @param string $mode    Mode to test.
+     *    @return boolean        True if this is the exit mode.
+     *    @access private
+     */
+    function _isSpecialMode($mode) {
+        return (strncmp($mode, "_", 1) == 0);
+    }
+    
+    /**
+     *    Strips the magic underscore marking single token
+     *    modes.
+     *    @param string $mode    Mode to decode.
+     *    @return string         Underlying mode name.
+     *    @access private
+     */
+    function _decodeSpecial($mode) {
+        return substr($mode, 1);
+    }
+    
+    /**
+     *    Calls the parser method named after the current
+     *    mode. Empty content will be ignored. The lexer
+     *    has a parser handler for each mode in the lexer.
+     *    @param string $content        Text parsed.
+     *    @param boolean $is_match      Token is recognised rather
+     *                                  than unparsed data.
+     *    @access private
+     */
+    function _invokeParser($content, $is_match) {
+        if (($content === '') || ($content === false)) {
             return true;
         }
-        
-        /**
-         *    Accepts a token from the end tag mode.
-         *    The element name is converted to lower case.
-         *    @param string $token     Incoming characters.
-         *    @param integer $event    Lexer event type.
-         *    @return boolean          False if parse error.
-         *    @access public
-         */
-        function acceptEndToken($token, $event) {
-            if (! preg_match('/<\/(.*)>/', $token, $matches)) {
-                return false;
-            }
-            return $this->_listener->endElement(strtolower($matches[1]));
-        }
-        
-        /**
-         *    Part of the tag data.
-         *    @param string $token     Incoming characters.
-         *    @param integer $event    Lexer event type.
-         *    @return boolean          False if parse error.
-         *    @access public
-         */
-        function acceptAttributeToken($token, $event) {
-            if ($this->_current_attribute) {
-                if ($event == LEXER_UNMATCHED) {
-                    $this->_attributes[$this->_current_attribute] .=
-                            SimpleHtmlSaxParser::decodeHtml($token);
-                }
-                if ($event == LEXER_SPECIAL) {
-                    $this->_attributes[$this->_current_attribute] .=
-                            preg_replace('/^=\s*/' , '', SimpleHtmlSaxParser::decodeHtml($token));
-                }
-            }
+        $handler = $this->_mode_handlers[$this->_mode->getCurrent()];
+        return $this->_parser->$handler($content, $is_match);
+    }
+    
+    /**
+     *    Tries to match a chunk of text and if successful
+     *    removes the recognised chunk and any leading
+     *    unparsed data. Empty strings will not be matched.
+     *    @param string $raw         The subject to parse. This is the
+     *                               content that will be eaten.
+     *    @return array/boolean      Three item list of unparsed
+     *                               content followed by the
+     *                               recognised token and finally the
+     *                               action the parser is to take.
+     *                               True if no match, false if there
+     *                               is a parsing error.
+     *    @access private
+     */
+    function _reduce($raw) {
+        if ($action = $this->_regexes[$this->_mode->getCurrent()]->match($raw, $match)) {
+            $unparsed_character_count = strpos($raw, $match);
+            $unparsed = substr($raw, 0, $unparsed_character_count);
+            $raw = substr($raw, $unparsed_character_count + strlen($match));
+            return array($raw, $unparsed, $match, $action);
+        }
+        return true;
+    }
+}
+
+/**
+ *    Breaks HTML into SAX events.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleHtmlLexer extends SimpleLexer {
+    
+    /**
+     *    Sets up the lexer with case insensitive matching
+     *    and adds the HTML handlers.
+     *    @param SimpleSaxParser $parser  Handling strategy by
+     *                                    reference.
+     *    @access public
+     */
+    function SimpleHtmlLexer(&$parser) {
+        $this->SimpleLexer($parser, 'text');
+        $this->mapHandler('text', 'acceptTextToken');
+        $this->_addSkipping();
+        foreach ($this->_getParsedTags() as $tag) {
+            $this->_addTag($tag);
+        }
+        $this->_addInTagTokens();
+    }
+    
+    /**
+     *    List of parsed tags. Others are ignored.
+     *    @return array        List of searched for tags.
+     *    @access private
+     */
+    function _getParsedTags() {
+        return array('a', 'base', 'title', 'form', 'input', 'button', 'textarea', 'select',
+                'option', 'frameset', 'frame', 'label');
+    }
+    
+    /**
+     *    The lexer has to skip certain sections such
+     *    as server code, client code and styles.
+     *    @access private
+     */
+    function _addSkipping() {
+        $this->mapHandler('css', 'ignore');
+        $this->addEntryPattern('<style', 'text', 'css');
+        $this->addExitPattern('</style>', 'css');
+        $this->mapHandler('js', 'ignore');
+        $this->addEntryPattern('<script', 'text', 'js');
+        $this->addExitPattern('</script>', 'js');
+        $this->mapHandler('comment', 'ignore');
+        $this->addEntryPattern('<!--', 'text', 'comment');
+        $this->addExitPattern('-->', 'comment');
+    }
+    
+    /**
+     *    Pattern matches to start and end a tag.
+     *    @param string $tag          Name of tag to scan for.
+     *    @access private
+     */
+    function _addTag($tag) {
+        $this->addSpecialPattern("</$tag>", 'text', 'acceptEndToken');
+        $this->addEntryPattern("<$tag", 'text', 'tag');
+    }
+    
+    /**
+     *    Pattern matches to parse the inside of a tag
+     *    including the attributes and their quoting.
+     *    @access private
+     */
+    function _addInTagTokens() {
+        $this->mapHandler('tag', 'acceptStartToken');
+        $this->addSpecialPattern('\s+', 'tag', 'ignore');
+        $this->_addAttributeTokens();
+        $this->addExitPattern('/>', 'tag');
+        $this->addExitPattern('>', 'tag');
+    }
+    
+    /**
+     *    Matches attributes that are either single quoted,
+     *    double quoted or unquoted.
+     *    @access private
+     */
+    function _addAttributeTokens() {
+        $this->mapHandler('dq_attribute', 'acceptAttributeToken');
+        $this->addEntryPattern('=\s*"', 'tag', 'dq_attribute');
+        $this->addPattern("\\\\\"", 'dq_attribute');
+        $this->addExitPattern('"', 'dq_attribute');
+        $this->mapHandler('sq_attribute', 'acceptAttributeToken');
+        $this->addEntryPattern("=\s*'", 'tag', 'sq_attribute');
+        $this->addPattern("\\\\'", 'sq_attribute');
+        $this->addExitPattern("'", 'sq_attribute');
+        $this->mapHandler('uq_attribute', 'acceptAttributeToken');
+        $this->addSpecialPattern('=\s*[^>\s]*', 'tag', 'uq_attribute');
+    }
+}
+
+/**
+ *    Converts HTML tokens into selected SAX events.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleHtmlSaxParser {
+    var $_lexer;
+    var $_listener;
+    var $_tag;
+    var $_attributes;
+    var $_current_attribute;
+    
+    /**
+     *    Sets the listener.
+     *    @param SimpleSaxListener $listener    SAX event handler.
+     *    @access public
+     */
+    function SimpleHtmlSaxParser(&$listener) {
+        $this->_listener = &$listener;
+        $this->_lexer = &$this->createLexer($this);
+        $this->_tag = '';
+        $this->_attributes = array();
+        $this->_current_attribute = '';
+    }
+    
+    /**
+     *    Runs the content through the lexer which
+     *    should call back to the acceptors.
+     *    @param string $raw      Page text to parse.
+     *    @return boolean         False if parse error.
+     *    @access public
+     */
+    function parse($raw) {
+        return $this->_lexer->parse($raw);
+    }
+    
+    /**
+     *    Sets up the matching lexer. Starts in 'text' mode.
+     *    @param SimpleSaxParser $parser    Event generator, usually $self.
+     *    @return SimpleLexer               Lexer suitable for this parser.
+     *    @access public
+     *    @static
+     */
+    function &createLexer(&$parser) {
+        $lexer = &new SimpleHtmlLexer($parser);
+        return $lexer;
+    }
+    
+    /**
+     *    Accepts a token from the tag mode. If the
+     *    starting element completes then the element
+     *    is dispatched and the current attributes
+     *    set back to empty. The element or attribute
+     *    name is converted to lower case.
+     *    @param string $token     Incoming characters.
+     *    @param integer $event    Lexer event type.
+     *    @return boolean          False if parse error.
+     *    @access public
+     */
+    function acceptStartToken($token, $event) {
+        if ($event == LEXER_ENTER) {
+            $this->_tag = strtolower(substr($token, 1));
             return true;
         }
-        
-        /**
-         *    A character entity.
-         *    @param string $token    Incoming characters.
-         *    @param integer $event   Lexer event type.
-         *    @return boolean         False if parse error.
-         *    @access public
-         */
-        function acceptEntityToken($token, $event) {
+        if ($event == LEXER_EXIT) {
+            $success = $this->_listener->startElement(
+                    $this->_tag,
+                    $this->_attributes);
+            $this->_tag = '';
+            $this->_attributes = array();
+            return $success;
         }
-        
-        /**
-         *    Character data between tags regarded as
-         *    important.
-         *    @param string $token     Incoming characters.
-         *    @param integer $event    Lexer event type.
-         *    @return boolean          False if parse error.
-         *    @access public
-         */
-        function acceptTextToken($token, $event) {
-            return $this->_listener->addContent($token);
+        if ($token != '=') {
+            $this->_current_attribute = strtolower(SimpleHtmlSaxParser::decodeHtml($token));
+            $this->_attributes[$this->_current_attribute] = '';
         }
-        
-        /**
-         *    Incoming data to be ignored.
-         *    @param string $token     Incoming characters.
-         *    @param integer $event    Lexer event type.
-         *    @return boolean          False if parse error.
-         *    @access public
-         */
-        function ignore($token, $event) {
-            return true;
+        return true;
+    }
+    
+    /**
+     *    Accepts a token from the end tag mode.
+     *    The element name is converted to lower case.
+     *    @param string $token     Incoming characters.
+     *    @param integer $event    Lexer event type.
+     *    @return boolean          False if parse error.
+     *    @access public
+     */
+    function acceptEndToken($token, $event) {
+        if (! preg_match('/<\/(.*)>/', $token, $matches)) {
+            return false;
         }
-        
-        /**
-         *    Decodes any HTML entities.
-         *    @param string $html    Incoming HTML.
-         *    @return string         Outgoing plain text.
-         *    @access public
-         *    @static
-         */
-        function decodeHtml($html) {
-            static $translations;
-            if (! isset($translations)) {
-                $translations = array_flip(get_html_translation_table(HTML_ENTITIES));
+        return $this->_listener->endElement(strtolower($matches[1]));
+    }
+    
+    /**
+     *    Part of the tag data.
+     *    @param string $token     Incoming characters.
+     *    @param integer $event    Lexer event type.
+     *    @return boolean          False if parse error.
+     *    @access public
+     */
+    function acceptAttributeToken($token, $event) {
+        if ($this->_current_attribute) {
+            if ($event == LEXER_UNMATCHED) {
+                $this->_attributes[$this->_current_attribute] .=
+                        SimpleHtmlSaxParser::decodeHtml($token);
+            }
+            if ($event == LEXER_SPECIAL) {
+                $this->_attributes[$this->_current_attribute] .=
+                        preg_replace('/^=\s*/' , '', SimpleHtmlSaxParser::decodeHtml($token));
             }
-            return strtr($html, $translations);
-        }
-        
-        /**
-         *    Turns HTML into text browser visible text. Images
-         *    are converted to their alt text and tags are supressed.
-         *    Entities are converted to their visible representation.
-         *    @param string $html        HTML to convert.
-         *    @return string             Plain text.
-         *    @access public
-         *    @static
-         */
-        function normalise($html) {
-            $text = preg_replace('|<!--.*?-->|', '', $html);
-            $text = preg_replace('|<img.*?alt\s*=\s*"(.*?)".*?>|', ' \1 ', $text);
-            $text = preg_replace('|<img.*?alt\s*=\s*\'(.*?)\'.*?>|', ' \1 ', $text);
-            $text = preg_replace('|<img.*?alt\s*=\s*([a-zA-Z_]+).*?>|', ' \1 ', $text);
-            $text = preg_replace('|<.*?>|', '', $text);
-            $text = SimpleHtmlSaxParser::decodeHtml($text);
-            $text = preg_replace('|\s+|', ' ', $text);
-            return trim($text);
         }
+        return true;
     }
     
     /**
-     *    SAX event handler.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     *    @abstract
+     *    A character entity.
+     *    @param string $token    Incoming characters.
+     *    @param integer $event   Lexer event type.
+     *    @return boolean         False if parse error.
+     *    @access public
      */
-    class SimpleSaxListener {
-        
-        /**
-         *    Sets the document to write to.
-         *    @access public
-         */
-        function SimpleSaxListener() {
-        }
-        
-        /**
-         *    Start of element event.
-         *    @param string $name        Element name.
-         *    @param hash $attributes    Name value pairs.
-         *                               Attributes without content
-         *                               are marked as true.
-         *    @return boolean            False on parse error.
-         *    @access public
-         */
-        function startElement($name, $attributes) {
-        }
-        
-        /**
-         *    End of element event.
-         *    @param string $name        Element name.
-         *    @return boolean            False on parse error.
-         *    @access public
-         */
-        function endElement($name) {
-        }
-        
-        /**
-         *    Unparsed, but relevant data.
-         *    @param string $text        May include unparsed tags.
-         *    @return boolean            False on parse error.
-         *    @access public
-         */
-        function addContent($text) {
-        }
+    function acceptEntityToken($token, $event) {
+    }
+    
+    /**
+     *    Character data between tags regarded as
+     *    important.
+     *    @param string $token     Incoming characters.
+     *    @param integer $event    Lexer event type.
+     *    @return boolean          False if parse error.
+     *    @access public
+     */
+    function acceptTextToken($token, $event) {
+        return $this->_listener->addContent($token);
+    }
+    
+    /**
+     *    Incoming data to be ignored.
+     *    @param string $token     Incoming characters.
+     *    @param integer $event    Lexer event type.
+     *    @return boolean          False if parse error.
+     *    @access public
+     */
+    function ignore($token, $event) {
+        return true;
+    }
+    
+    /**
+     *    Decodes any HTML entities.
+     *    @param string $html    Incoming HTML.
+     *    @return string         Outgoing plain text.
+     *    @access public
+     *    @static
+     */
+    function decodeHtml($html) {
+        return html_entity_decode($html, ENT_QUOTES);
+    }
+    
+    /**
+     *    Turns HTML into text browser visible text. Images
+     *    are converted to their alt text and tags are supressed.
+     *    Entities are converted to their visible representation.
+     *    @param string $html        HTML to convert.
+     *    @return string             Plain text.
+     *    @access public
+     *    @static
+     */
+    function normalise($html) {
+        $text = preg_replace('|<!--.*?-->|', '', $html);
+        $text = preg_replace('|<script[^>]*>.*?</script>|', '', $text);
+        $text = preg_replace('|<img[^>]*alt\s*=\s*"([^"]*)"[^>]*>|', ' \1 ', $text);
+        $text = preg_replace('|<img[^>]*alt\s*=\s*\'([^\']*)\'[^>]*>|', ' \1 ', $text);
+        $text = preg_replace('|<img[^>]*alt\s*=\s*([a-zA-Z_]+)[^>]*>|', ' \1 ', $text);
+        $text = preg_replace('|<[^>]*>|', '', $text);
+        $text = SimpleHtmlSaxParser::decodeHtml($text);
+        $text = preg_replace('|\s+|', ' ', $text);
+        return trim(trim($text), "\xA0");        // TODO: The \xAO is a &nbsp;. Add a test for this.
+    }
+}
+
+/**
+ *    SAX event handler.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ *    @abstract
+ */
+class SimpleSaxListener {
+    
+    /**
+     *    Sets the document to write to.
+     *    @access public
+     */
+    function SimpleSaxListener() {
+    }
+    
+    /**
+     *    Start of element event.
+     *    @param string $name        Element name.
+     *    @param hash $attributes    Name value pairs.
+     *                               Attributes without content
+     *                               are marked as true.
+     *    @return boolean            False on parse error.
+     *    @access public
+     */
+    function startElement($name, $attributes) {
+    }
+    
+    /**
+     *    End of element event.
+     *    @param string $name        Element name.
+     *    @return boolean            False on parse error.
+     *    @access public
+     */
+    function endElement($name) {
+    }
+    
+    /**
+     *    Unparsed, but relevant data.
+     *    @param string $text        May include unparsed tags.
+     *    @return boolean            False on parse error.
+     *    @access public
+     */
+    function addContent($text) {
     }
+}
 ?>
\ No newline at end of file
index a624106fb9996547f3871c7d4aa8a1a8df1375c8..c30eea888db359f0c7b872848bd0946bfeceed8b 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**
+ *  Version specific reflection API.
+ *  @package SimpleTest
+ *  @subpackage UnitTester
+ *  @ignore duplicate with reflection_php5.php
+ */
+class SimpleReflection {
+    var $_interface;
+
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
+     *    Stashes the class/interface.
+     *    @param string $interface    Class or interface
+     *                                to inspect.
      */
+    function SimpleReflection($interface) {
+        $this->_interface = $interface;
+    }
 
     /**
-     *    Version specific reflection API.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Checks that a class has been declared.
+     *    @return boolean        True if defined.
+     *    @access public
      */
-    class SimpleReflection {
-        var $_interface;
-
-        /**
-         *    Stashes the class/interface.
-         *    @param string $interface    Class or interface
-         *                                to inspect.
-         */
-        function SimpleReflection($interface) {
-            $this->_interface = $interface;
-        }
+    function classExists() {
+        return class_exists($this->_interface);
+    }
 
-        /**
-         *    Checks that a class has been declared.
-         *    @return boolean        True if defined.
-         *    @access public
-         */
-        function classExists() {
-            return class_exists($this->_interface);
-        }
+    /**
+     *    Needed to kill the autoload feature in PHP5
+     *    for classes created dynamically.
+     *    @return boolean        True if defined.
+     *    @access public
+     */
+    function classExistsSansAutoload() {
+        return class_exists($this->_interface);
+    }
 
-        /**
-         *    Needed to kill the autoload feature in PHP5
-         *    for classes created dynamically.
-         *    @return boolean        True if defined.
-         *    @access public
-         */
-        function classExistsSansAutoload() {
-            return class_exists($this->_interface);
-        }
+    /**
+     *    Checks that a class or interface has been
+     *    declared.
+     *    @return boolean        True if defined.
+     *    @access public
+     */
+    function classOrInterfaceExists() {
+        return class_exists($this->_interface);
+    }
 
-        /**
-         *    Checks that a class or interface has been
-         *    declared.
-         *    @return boolean        True if defined.
-         *    @access public
-         */
-        function classOrInterfaceExists() {
-            return class_exists($this->_interface);
-        }
+    /**
+     *    Needed to kill the autoload feature in PHP5
+     *    for classes created dynamically.
+     *    @return boolean        True if defined.
+     *    @access public
+     */
+    function classOrInterfaceExistsSansAutoload() {
+        return class_exists($this->_interface);
+    }
 
-        /**
-         *    Needed to kill the autoload feature in PHP5
-         *    for classes created dynamically.
-         *    @return boolean        True if defined.
-         *    @access public
-         */
-        function classOrInterfaceExistsSansAutoload() {
-            return class_exists($this->_interface);
-        }
+    /**
+     *    Gets the list of methods on a class or
+     *    interface.
+     *    @returns array          List of method names.
+     *    @access public
+     */
+    function getMethods() {
+        return get_class_methods($this->_interface);
+    }
 
-        /**
-         *    Gets the list of methods on a class or
-         *    interface.
-         *    @returns array          List of method names.
-         *    @access public
-         */
-        function getMethods() {
-            return get_class_methods($this->_interface);
-        }
+    /**
+     *    Gets the list of interfaces from a class. If the
+     *    class name is actually an interface then just that
+     *    interface is returned.
+     *    @returns array          List of interfaces.
+     *    @access public
+     */
+    function getInterfaces() {
+        return array();
+    }
 
-        /**
-         *    Gets the list of interfaces from a class. If the
-         *       class name is actually an interface then just that
-         *       interface is returned.
-         *    @returns array          List of interfaces.
-         *    @access public
-         */
-        function getInterfaces() {
-            return array();
-        }
+    /**
+     *    Finds the parent class name.
+     *    @returns string      Parent class name.
+     *    @access public
+     */
+    function getParent() {
+        return strtolower(get_parent_class($this->_interface));
+    }
 
-        /**
-         *    Finds the parent class name.
-         *    @returns string      Parent class name.
-         *    @access public
-         */
-        function getParent() {
-            return strtolower(get_parent_class($this->_interface));
-        }
+    /**
+     *    Determines if the class is abstract, which for PHP 4
+     *    will never be the case.
+     *    @returns boolean      True if abstract.
+     *    @access public
+     */
+    function isAbstract() {
+        return false;
+    }
 
-        /**
-         *    Determines if the class is abstract, which for PHP 4
-         *    will never be the case.
-         *    @returns boolean      True if abstract.
-         *    @access public
-         */
-        function isAbstract() {
-            return false;
-        }
+    /**
+     *    Determines if the the entity is an interface, which for PHP 4
+     *    will never be the case.
+     *    @returns boolean      True if interface.
+     *    @access public
+     */
+    function isInterface() {
+        return false;
+    }
+        
+    /**
+     *    Scans for final methods, but as it's PHP 4 there
+     *    aren't any.
+     *    @returns boolean   True if the class has a final method.
+     *    @access public
+     */
+    function hasFinal() {
+        return false;
+    }
 
-        /**
-         *       Gets the source code matching the declaration
-         *       of a method.
-         *       @param string $method           Method name.
-         *    @access public
-         */
-        function getSignature($method) {
-               return "function &$method()";
-        }
+    /**
+     *    Gets the source code matching the declaration
+     *    of a method.
+     *    @param string $method       Method name.
+     *    @access public
+     */
+    function getSignature($method) {
+        return "function &$method()";
     }
+}
 ?>
\ No newline at end of file
index 7e023f37d098738e9b226b344523022e96c0855b..0f2e50b3038837bd6173894a14336660ed555dbe 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**
+ *    Version specific reflection API.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class SimpleReflection {
+    var $_interface;
+
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
+     *    Stashes the class/interface.
+     *    @param string $interface    Class or interface
+     *                                to inspect.
      */
+    function SimpleReflection($interface) {
+        $this->_interface = $interface;
+    }
 
     /**
-     *    Version specific reflection API.
-     *    @package SimpleTest
-     *    @subpackage UnitTester
+     *    Checks that a class has been declared. Versions
+     *    before PHP5.0.2 need a check that it's not really
+     *    an interface.
+     *    @return boolean            True if defined.
+     *    @access public
      */
-    class SimpleReflection {
-        var $_interface;
-
-        /**
-         *    Stashes the class/interface.
-         *    @param string $interface    Class or interface
-         *                                to inspect.
-         */
-        function SimpleReflection($interface) {
-            $this->_interface = $interface;
+    function classExists() {
+        if (! class_exists($this->_interface)) {
+            return false;
         }
+        $reflection = new ReflectionClass($this->_interface);
+        return ! $reflection->isInterface();
+    }
 
-        /**
-         *    Checks that a class has been declared. Versions
-         *    before PHP5.0.2 need a check that it's not really
-         *    an interface.
-         *    @return boolean            True if defined.
-         *    @access public
-         */
-        function classExists() {
-            if (! class_exists($this->_interface)) {
-                return false;
+    /**
+     *    Needed to kill the autoload feature in PHP5
+     *    for classes created dynamically.
+     *    @return boolean        True if defined.
+     *    @access public
+     */
+    function classExistsSansAutoload() {
+        return class_exists($this->_interface, false);
+    }
+
+    /**
+     *    Checks that a class or interface has been
+     *    declared.
+     *    @return boolean            True if defined.
+     *    @access public
+     */
+    function classOrInterfaceExists() {
+        return $this->_classOrInterfaceExistsWithAutoload($this->_interface, true);
+    }
+
+    /**
+     *    Needed to kill the autoload feature in PHP5
+     *    for classes created dynamically.
+     *    @return boolean        True if defined.
+     *    @access public
+     */
+    function classOrInterfaceExistsSansAutoload() {
+        return $this->_classOrInterfaceExistsWithAutoload($this->_interface, false);
+    }
+
+    /**
+     *    Needed to select the autoload feature in PHP5
+     *    for classes created dynamically.
+     *    @param string $interface       Class or interface name.
+     *    @param boolean $autoload       True totriggerautoload.
+     *    @return boolean                True if interface defined.
+     *    @access private
+     */
+    function _classOrInterfaceExistsWithAutoload($interface, $autoload) {
+        if (function_exists('interface_exists')) {
+            if (interface_exists($this->_interface, $autoload)) {
+                return true;
             }
-            $reflection = new ReflectionClass($this->_interface);
-            return ! $reflection->isInterface();
         }
+        return class_exists($this->_interface, $autoload);
+    }
 
-        /**
-         *    Needed to kill the autoload feature in PHP5
-         *    for classes created dynamically.
-         *    @return boolean        True if defined.
-         *    @access public
-         */
-        function classExistsSansAutoload() {
-            return class_exists($this->_interface, false);
-        }
+    /**
+     *    Gets the list of methods on a class or
+     *    interface.
+     *    @returns array              List of method names.
+     *    @access public
+     */
+    function getMethods() {
+        return array_unique(get_class_methods($this->_interface));
+    }
 
-        /**
-         *    Checks that a class or interface has been
-         *    declared.
-         *    @return boolean            True if defined.
-         *    @access public
-         */
-        function classOrInterfaceExists() {
-            return $this->_classOrInterfaceExistsWithAutoload($this->_interface, true);
+    /**
+     *    Gets the list of interfaces from a class. If the
+     *    class name is actually an interface then just that
+     *    interface is returned.
+     *    @returns array          List of interfaces.
+     *    @access public
+     */
+    function getInterfaces() {
+        $reflection = new ReflectionClass($this->_interface);
+        if ($reflection->isInterface()) {
+            return array($this->_interface);
         }
+        return $this->_onlyParents($reflection->getInterfaces());
+    }
 
-        /**
-         *    Needed to kill the autoload feature in PHP5
-         *    for classes created dynamically.
-         *    @return boolean        True if defined.
-         *    @access public
-         */
-        function classOrInterfaceExistsSansAutoload() {
-            return $this->_classOrInterfaceExistsWithAutoload($this->_interface, false);
+    /**
+     *    Gets the list of methods for the implemented
+     *    interfaces only.
+     *    @returns array      List of enforced method signatures.
+     *    @access public
+     */
+    function getInterfaceMethods() {
+        $methods = array();
+        foreach ($this->getInterfaces() as $interface) {
+            $methods = array_merge($methods, get_class_methods($interface));
         }
+        return array_unique($methods);
+    }
 
-        /**
-         *    Needed to select the autoload feature in PHP5
-         *    for classes created dynamically.
-         *    @param string $interface       Class or interface name.
-         *    @param boolean $autoload       True totriggerautoload.
-         *    @return boolean                True if interface defined.
-         *    @access private
-         */
-        function _classOrInterfaceExistsWithAutoload($interface, $autoload) {
-            if (function_exists('interface_exists')) {
-                if (interface_exists($this->_interface, $autoload)) {
-                    return true;
-                }
-            }
-            return class_exists($this->_interface, $autoload);
-        }
+    /**
+     *    Checks to see if the method signature has to be tightly
+     *    specified.
+     *    @param string $method        Method name.
+     *    @returns boolean             True if enforced.
+     *    @access private
+     */
+    function _isInterfaceMethod($method) {
+        return in_array($method, $this->getInterfaceMethods());
+    }
 
-        /**
-         *    Gets the list of methods on a class or
-         *    interface. Needs to recursively look at all of
-         *    the interfaces included.
-         *    @returns array              List of method names.
-         *    @access public
-         */
-        function getMethods() {
-            return array_unique(get_class_methods($this->_interface));
+    /**
+     *    Finds the parent class name.
+     *    @returns string      Parent class name.
+     *    @access public
+     */
+    function getParent() {
+        $reflection = new ReflectionClass($this->_interface);
+        $parent = $reflection->getParentClass();
+        if ($parent) {
+            return $parent->getName();
         }
+        return false;
+    }
 
-        /**
-         *    Gets the list of interfaces from a class. If the
-         *    class name is actually an interface then just that
-         *    interface is returned.
-         *    @returns array          List of interfaces.
-         *    @access public
-         */
-        function getInterfaces() {
-            $reflection = new ReflectionClass($this->_interface);
-            if ($reflection->isInterface()) {
-               return array($this->_interface);
+    /**
+     *    Trivially determines if the class is abstract.
+     *    @returns boolean      True if abstract.
+     *    @access public
+     */
+    function isAbstract() {
+        $reflection = new ReflectionClass($this->_interface);
+        return $reflection->isAbstract();
+    }
+
+    /**
+     *    Trivially determines if the class is an interface.
+     *    @returns boolean      True if interface.
+     *    @access public
+     */
+    function isInterface() {
+        $reflection = new ReflectionClass($this->_interface);
+        return $reflection->isInterface();
+    }
+
+    /**
+     *    Scans for final methods, as they screw up inherited
+     *    mocks by not allowing you to override them.
+     *    @returns boolean   True if the class has a final method.
+     *    @access public
+     */
+    function hasFinal() {
+        $reflection = new ReflectionClass($this->_interface);
+        foreach ($reflection->getMethods() as $method) {
+            if ($method->isFinal()) {
+                return true;
             }
-            return $this->_onlyParents($reflection->getInterfaces());
         }
+        return false;
+    }
 
-        /**
-         *    Gets the list of methods for the implemented
-         *    interfaces only.
-         *    @returns array      List of enforced method signatures.
-         *    @access public
-         */
-        function getInterfaceMethods() {
-            $methods = array();
-            foreach ($this->getInterfaces() as $interface) {
-                $methods = array_merge($methods, get_class_methods($interface));
+    /**
+     *    Whittles a list of interfaces down to only the
+     *    necessary top level parents.
+     *    @param array $interfaces     Reflection API interfaces
+     *                                 to reduce.
+     *    @returns array               List of parent interface names.
+     *    @access private
+     */
+    function _onlyParents($interfaces) {
+        $parents = array();
+        $blacklist = array();
+        foreach ($interfaces as $interface) {
+            foreach($interfaces as $possible_parent) {
+                if ($interface->getName() == $possible_parent->getName()) {
+                    continue;
+                }
+                if ($interface->isSubClassOf($possible_parent)) {
+                    $blacklist[$possible_parent->getName()] = true;
+                }
+            }
+            if (!isset($blacklist[$interface->getName()])) {
+                $parents[] = $interface->getName();
             }
-            return array_unique($methods);
-        }
-        
-        /**
-         *    Checks to see if the method signature has to be tightly
-         *    specified.
-         *    @param string $method        Method name.
-         *    @returns boolean             True if enforced.
-         *    @access private
-         */
-        function _isInterfaceMethod($method) {
-            return in_array($method, $this->getInterfaceMethods());
         }
+        return $parents;
+    }
 
-        /**
-         *    Finds the parent class name.
-         *    @returns string      Parent class name.
-         *    @access public
-         */
-        function getParent() {
-            $reflection = new ReflectionClass($this->_interface);
-            $parent = $reflection->getParentClass();
-            if ($parent) {
-                return $parent->getName();
-            }
+    /**
+     * Checks whether a method is abstract or not.
+     * @param   string   $name  Method name.
+     * @return  bool            true if method is abstract, else false
+     * @access  private
+     */
+    function _isAbstractMethod($name) {
+        $interface = new ReflectionClass($this->_interface);
+        if (! $interface->hasMethod($name)) {
             return false;
         }
+        return $interface->getMethod($name)->isAbstract();
+    }
 
-        /**
-         *    Determines if the class is abstract.
-         *    @returns boolean      True if abstract.
-         *    @access public
-         */
-        function isAbstract() {
-            $reflection = new ReflectionClass($this->_interface);
-            return $reflection->isAbstract();
-        }
+    /**
+     * Checks whether a method is the constructor.
+     * @param   string   $name  Method name.
+     * @return  bool            true if method is the constructor
+     * @access  private
+     */
+    function _isConstructor($name) {
+        return ($name == '__construct') || ($name == $this->_interface);
+    }
 
-        /**
-         *    Wittles a list of interfaces down to only the top
-         *    level parents.
-         *    @param array $interfaces     Reflection API interfaces
-         *                                 to reduce.
-         *    @returns array               List of parent interface names.
-         *    @access private
-         */
-        function _onlyParents($interfaces) {
-            $parents = array();
-            foreach ($interfaces as $interface) {
-                foreach($interfaces as $possible_parent) {
-                    if ($interface->getName() == $possible_parent->getName()) {
-                        continue;
-                    }
-                    if ($interface->isSubClassOf($possible_parent)) {
-                        break;
-                    }
-                }
-                $parents[] = $interface->getName();
+    /**
+     * Checks whether a method is abstract in all parents or not.
+     * @param   string   $name  Method name.
+     * @return  bool            true if method is abstract in parent, else false
+     * @access  private
+     */
+    function _isAbstractMethodInParents($name) {
+        $interface = new ReflectionClass($this->_interface);
+        $parent = $interface->getParentClass();
+        while($parent) {
+            if (! $parent->hasMethod($name)) {
+                return false;
             }
-            return $parents;
+            if ($parent->getMethod($name)->isAbstract()) {
+                return true;
+            }
+            $parent = $parent->getParentClass();
         }
+        return false;
+    }
 
-        /**
-         *    Gets the source code matching the declaration
-         *    of a method.
-         *    @param string $name    Method name.
-         *    @return string         Method signature up to last
-         *                           bracket.
-         *    @access public
-         */
-        function getSignature($name) {
-               if ($name == '__set') {
-                       return 'function __set($key, $value)';
-               }
-               if ($name == '__call') {
-                       return 'function __call($method, $arguments)';
-               }
-            if (version_compare(phpversion(), '5.1.0', '>=')) {
-                if (in_array($name, array('__get', '__isset', $name == '__unset'))) {
-                    return "function {$name}(\$key)";
-                }
+    /**
+     * Checks whether a method is static or not.
+     * @param   string  $name   Method name
+     * @return  bool            true if method is static, else false
+     * @access  private
+     */
+    function _isStaticMethod($name) {
+        $interface = new ReflectionClass($this->_interface);
+        if (! $interface->hasMethod($name)) {
+            return false;
+        }
+        return $interface->getMethod($name)->isStatic();
+    }
+
+    /**
+     *    Writes the source code matching the declaration
+     *    of a method.
+     *    @param string $name    Method name.
+     *    @return string         Method signature up to last
+     *                           bracket.
+     *    @access public
+     */
+    function getSignature($name) {
+        if ($name == '__set') {
+            return 'function __set($key, $value)';
+        }
+        if ($name == '__call') {
+            return 'function __call($method, $arguments)';
+        }
+        if (version_compare(phpversion(), '5.1.0', '>=')) {
+            if (in_array($name, array('__get', '__isset', $name == '__unset'))) {
+                return "function {$name}(\$key)";
             }
-               if (! is_callable(array($this->_interface, $name))) {
-                       return "function $name()";
-               }
-               if ($this->_isInterfaceMethod($name)) {
-                   return $this->_getFullSignature($name);
-               }
-               return "function $name()";
         }
-        
-        /**
-         *    For a signature specified in an interface, full
-         *    details must be replicated to be a valid implementation.
-         *    @param string $name    Method name.
-         *    @return string         Method signature up to last
-         *                           bracket.
-         *    @access private
-         */
-        function _getFullSignature($name) {
-            $interface = new ReflectionClass($this->_interface);
-            $method = $interface->getMethod($name);
-            $reference = $method->returnsReference() ? '&' : '';
-            return "function $reference$name(" .
-                    implode(', ', $this->_getParameterSignatures($method)) .
-                    ")";
+        if ($name == '__toString') {
+            return "function $name()";
         }
+        if ($this->_isInterfaceMethod($name) ||
+                $this->_isAbstractMethod($name) ||
+                $this->_isAbstractMethodInParents($name) ||
+                $this->_isStaticMethod($name)) {
+            return $this->_getFullSignature($name);
+        }
+        return "function $name()";
+    }
+
+    /**
+     *    For a signature specified in an interface, full
+     *    details must be replicated to be a valid implementation.
+     *    @param string $name    Method name.
+     *    @return string         Method signature up to last
+     *                           bracket.
+     *    @access private
+     */
+    function _getFullSignature($name) {
+        $interface = new ReflectionClass($this->_interface);
+        $method = $interface->getMethod($name);
+        $reference = $method->returnsReference() ? '&' : '';
+        $static = $method->isStatic() ? 'static ' : '';
+        return "{$static}function $reference$name(" .
+                implode(', ', $this->_getParameterSignatures($method)) .
+                ")";
+    }
 
-        /**
-         *    Gets the source code for each parameter.
-         *    @param ReflectionMethod $method   Method object from
-         *                                                         reflection API
-         *    @return array                     List of strings, each
-         *                                      a snippet of code.
-         *    @access private
-         */
-        function _getParameterSignatures($method) {
-            $signatures = array();
-            foreach ($method->getParameters() as $parameter) {
-                $type = $parameter->getClass();
-               $signatures[] =
-                        (! is_null($type) ? $type->getName() . ' ' : '') .
-                        ($parameter->isPassedByReference() ? '&' : '') .
-                        '$' . $this->_suppressSpurious($parameter->getName()) .
-                        ($this->_isOptional($parameter) ? ' = null' : '');
+    /**
+     *    Gets the source code for each parameter.
+     *    @param ReflectionMethod $method   Method object from
+     *                                      reflection API
+     *    @return array                     List of strings, each
+     *                                      a snippet of code.
+     *    @access private
+     */
+    function _getParameterSignatures($method) {
+        $signatures = array();
+        foreach ($method->getParameters() as $parameter) {
+            $signature = '';
+            $type = $parameter->getClass();
+            if (is_null($type) && version_compare(phpversion(), '5.1.0', '>=') && $parameter->isArray()) {
+                $signature .= 'array ';
+            } elseif (!is_null($type)) {
+                $signature .= $type->getName() . ' ';
+            }
+            if ($parameter->isPassedByReference()) {
+                $signature .= '&';
+            }
+            $signature .= '$' . $this->_suppressSpurious($parameter->getName());
+            if ($this->_isOptional($parameter)) {
+                $signature .= ' = null';
             }
-            return $signatures;
+            $signatures[] = $signature;
         }
+        return $signatures;
+    }
 
-        /**
-         *    The SPL library has problems with the
-         *    Reflection library. In particular, you can
-         *    get extra characters in parameter names :(.
-         *    @param string $name    Parameter name.
-         *    @return string         Cleaner name.
-         *    @access private
-         */
-        function _suppressSpurious($name) {
-            return str_replace(array('[', ']', ' '), '', $name);
-        }
+    /**
+     *    The SPL library has problems with the
+     *    Reflection library. In particular, you can
+     *    get extra characters in parameter names :(.
+     *    @param string $name    Parameter name.
+     *    @return string         Cleaner name.
+     *    @access private
+     */
+    function _suppressSpurious($name) {
+        return str_replace(array('[', ']', ' '), '', $name);
+    }
 
-        /**
-         *    Test of a reflection parameter being optional
-         *    that works with early versions of PHP5.
-         *    @param reflectionParameter $parameter    Is this optional.
-         *    @return boolean                          True if optional.
-         *    @access private
-         */
-        function _isOptional($parameter) {
-            if (method_exists($parameter, 'isOptional')) {
-                return $parameter->isOptional();
-            }
-            return false;
+    /**
+     *    Test of a reflection parameter being optional
+     *    that works with early versions of PHP5.
+     *    @param reflectionParameter $parameter    Is this optional.
+     *    @return boolean                          True if optional.
+     *    @access private
+     */
+    function _isOptional($parameter) {
+        if (method_exists($parameter, 'isOptional')) {
+            return $parameter->isOptional();
         }
+        return false;
     }
-?>
\ No newline at end of file
+}
+?>
index c7e02b124ee7ce518ae3a4245f61e429d5ba1547..15e55c98acee885421d0dbd81775a51458d4b520 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ *  include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/browser.php');
+require_once(dirname(__FILE__) . '/xml.php');
+require_once(dirname(__FILE__) . '/test_case.php');
+/**#@-*/
+
+/**
+ *    Runs an XML formated test on a remote server.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class RemoteTestCase {
+    var $_url;
+    var $_dry_url;
+    var $_size;
+    
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
+     *    Sets the location of the remote test.
+     *    @param string $url       Test location.
+     *    @param string $dry_url   Location for dry run.
+     *    @access public
      */
-
-    /**#@+
-     * include other SimpleTest class files
+    function RemoteTestCase($url, $dry_url = false) {
+        $this->_url = $url;
+        $this->_dry_url = $dry_url ? $dry_url : $url;
+        $this->_size = false;
+    }
+    
+    /**
+     *    Accessor for the test name for subclasses.
+     *    @return string           Name of the test.
+     *    @access public
      */
-    require_once(dirname(__FILE__) . '/browser.php');
-    require_once(dirname(__FILE__) . '/xml.php');
-    require_once(dirname(__FILE__) . '/test_case.php');
-    /**#@-*/
+    function getLabel() {
+        return $this->_url;
+    }
 
     /**
-     *    Runs an XML formated test on a remote server.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Runs the top level test for this class. Currently
+     *    reads the data as a single chunk. I'll fix this
+     *    once I have added iteration to the browser.
+     *    @param SimpleReporter $reporter    Target of test results.
+     *    @returns boolean                   True if no failures.
+     *    @access public
      */
-    class RemoteTestCase {
-        var $_url;
-        var $_dry_url;
-        var $_size;
-        
-        /**
-         *    Sets the location of the remote test.
-         *    @param string $url       Test location.
-         *    @param string $dry_url   Location for dry run.
-         *    @access public
-         */
-        function RemoteTestCase($url, $dry_url = false) {
-            $this->_url = $url;
-            $this->_dry_url = $dry_url ? $dry_url : $url;
-            $this->_size = false;
+    function run(&$reporter) {
+        $browser = &$this->_createBrowser();
+        $xml = $browser->get($this->_url);
+        if (! $xml) {
+            trigger_error('Cannot read remote test URL [' . $this->_url . ']');
+            return false;
         }
-        
-        /**
-         *    Accessor for the test name for subclasses.
-         *    @return string           Name of the test.
-         *    @access public
-         */
-        function getLabel() {
-            return $this->_url;
+        $parser = &$this->_createParser($reporter);
+        if (! $parser->parse($xml)) {
+            trigger_error('Cannot parse incoming XML from [' . $this->_url . ']');
+            return false;
         }
-
-        /**
-         *    Runs the top level test for this class. Currently
-         *    reads the data as a single chunk. I'll fix this
-         *    once I have added iteration to the browser.
-         *    @param SimpleReporter $reporter    Target of test results.
-         *    @returns boolean                   True if no failures.
-         *    @access public
-         */
-        function run(&$reporter) {
+        return true;
+    }
+    
+    /**
+     *    Creates a new web browser object for fetching
+     *    the XML report.
+     *    @return SimpleBrowser           New browser.
+     *    @access protected
+     */
+    function &_createBrowser() {
+        $browser = &new SimpleBrowser();
+        return $browser;
+    }
+    
+    /**
+     *    Creates the XML parser.
+     *    @param SimpleReporter $reporter    Target of test results.
+     *    @return SimpleTestXmlListener      XML reader.
+     *    @access protected
+     */
+    function &_createParser(&$reporter) {
+        $parser = &new SimpleTestXmlParser($reporter);
+        return $parser;
+    }
+    
+    /**
+     *    Accessor for the number of subtests.
+     *    @return integer           Number of test cases.
+     *    @access public
+     */
+    function getSize() {
+        if ($this->_size === false) {
             $browser = &$this->_createBrowser();
-            $xml = $browser->get($this->_url);
+            $xml = $browser->get($this->_dry_url);
             if (! $xml) {
-                trigger_error('Cannot read remote test URL [' . $this->_url . ']');
+                trigger_error('Cannot read remote test URL [' . $this->_dry_url . ']');
                 return false;
             }
+            $reporter = &new SimpleReporter();
             $parser = &$this->_createParser($reporter);
             if (! $parser->parse($xml)) {
-                trigger_error('Cannot parse incoming XML from [' . $this->_url . ']');
+                trigger_error('Cannot parse incoming XML from [' . $this->_dry_url . ']');
                 return false;
             }
-            return true;
-        }
-        
-        /**
-         *    Creates a new web browser object for fetching
-         *    the XML report.
-         *    @return SimpleBrowser           New browser.
-         *    @access protected
-         */
-        function &_createBrowser() {
-            $browser = &new SimpleBrowser();
-            return $browser;
-        }
-        
-        /**
-         *    Creates the XML parser.
-         *    @param SimpleReporter $reporter    Target of test results.
-         *    @return SimpleTestXmlListener      XML reader.
-         *    @access protected
-         */
-        function &_createParser(&$reporter) {
-            $parser = &new SimpleTestXmlParser($reporter);
-            return $parser;
-        }
-        
-        /**
-         *    Accessor for the number of subtests.
-         *    @return integer           Number of test cases.
-         *    @access public
-         */
-        function getSize() {
-            if ($this->_size === false) {
-                $browser = &$this->_createBrowser();
-                $xml = $browser->get($this->_dry_url);
-                if (! $xml) {
-                    trigger_error('Cannot read remote test URL [' . $this->_dry_url . ']');
-                    return false;
-                }
-                $reporter = &new SimpleReporter();
-                $parser = &$this->_createParser($reporter);
-                if (! $parser->parse($xml)) {
-                    trigger_error('Cannot parse incoming XML from [' . $this->_dry_url . ']');
-                    return false;
-                }
-                $this->_size = $reporter->getTestCaseCount();
-            }
-            return $this->_size;
+            $this->_size = $reporter->getTestCaseCount();
         }
+        return $this->_size;
     }
+}
 ?>
\ No newline at end of file
index 492faab9af83576d1123c32c1cbf485e548ec71b..20a6c9e514f988a2a0657d4502dcd67af95f9695 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ *  include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/scorer.php');
+/**#@-*/
+
+/**
+ *    Sample minimal test displayer. Generates only
+ *    failure messages and a pass count.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class HtmlReporter extends SimpleReporter {
+    var $_character_set;
+
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
+     *    Does nothing yet. The first output will
+     *    be sent on the first test start. For use
+     *    by a web browser.
+     *    @access public
      */
+    function HtmlReporter($character_set = 'ISO-8859-1') {
+        $this->SimpleReporter();
+        $this->_character_set = $character_set;
+    }
 
-    /**#@+
-     * include other SimpleTest class files
+    /**
+     *    Paints the top of the web page setting the
+     *    title to the name of the starting test.
+     *    @param string $test_name      Name class of test.
+     *    @access public
      */
-    require_once(dirname(__FILE__) . '/scorer.php');
-    /**#@-*/
+    function paintHeader($test_name) {
+        $this->sendNoCacheHeaders();
+        print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">";
+        print "<html>\n<head>\n<title>$test_name</title>\n";
+        print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" .
+                $this->_character_set . "\">\n";
+        print "<style type=\"text/css\">\n";
+        print $this->_getCss() . "\n";
+        print "</style>\n";
+        print "</head>\n<body>\n";
+        print "<h1>$test_name</h1>\n";
+        flush();
+    }
 
     /**
-     *    Sample minimal test displayer. Generates only
-     *    failure messages and a pass count.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Send the headers necessary to ensure the page is
+     *    reloaded on every request. Otherwise you could be
+     *    scratching your head over out of date test data.
+     *    @access public
+     *    @static
      */
-    class HtmlReporter extends SimpleReporter {
-        var $_character_set;
-
-        /**
-         *    Does nothing yet. The first output will
-         *    be sent on the first test start. For use
-         *    by a web browser.
-         *    @access public
-         */
-        function HtmlReporter($character_set = 'ISO-8859-1') {
-            $this->SimpleReporter();
-            $this->_character_set = $character_set;
-        }
-
-        /**
-         *    Paints the top of the web page setting the
-         *    title to the name of the starting test.
-         *    @param string $test_name      Name class of test.
-         *    @access public
-         */
-        function paintHeader($test_name) {
-            $this->sendNoCacheHeaders();
-            print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">";
-            print "<html>\n<head>\n<title>$test_name</title>\n";
-            print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" .
-                    $this->_character_set . "\">\n";
-            print "<style type=\"text/css\">\n";
-            print $this->_getCss() . "\n";
-            print "</style>\n";
-            print "</head>\n<body>\n";
-            print "<h1>$test_name</h1>\n";
-            flush();
-        }
-
-        /**
-         *    Send the headers necessary to ensure the page is
-         *    reloaded on every request. Otherwise you could be
-         *    scratching your head over out of date test data.
-         *    @access public
-         *    @static
-         */
-        function sendNoCacheHeaders() {
-            if (! headers_sent()) {
-                header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
-                header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
-                header("Cache-Control: no-store, no-cache, must-revalidate");
-                header("Cache-Control: post-check=0, pre-check=0", false);
-                header("Pragma: no-cache");
-            }
-        }
-
-        /**
-         *    Paints the CSS. Add additional styles here.
-         *    @return string            CSS code as text.
-         *    @access protected
-         */
-        function _getCss() {
-            return ".fail { background-color: inherit; color: red; }" .
-                    ".pass { background-color: inherit; color: green; }" .
-                    " pre { background-color: lightgray; color: inherit; }";
+    function sendNoCacheHeaders() {
+        if (! headers_sent()) {
+            header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
+            header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
+            header("Cache-Control: no-store, no-cache, must-revalidate");
+            header("Cache-Control: post-check=0, pre-check=0", false);
+            header("Pragma: no-cache");
         }
+    }
 
-        /**
-         *    Paints the end of the test with a summary of
-         *    the passes and failures.
-         *    @param string $test_name        Name class of test.
-         *    @access public
-         */
-        function paintFooter($test_name) {
-            $colour = ($this->getFailCount() + $this->getExceptionCount() > 0 ? "red" : "green");
-            print "<div style=\"";
-            print "padding: 8px; margin-top: 1em; background-color: $colour; color: white;";
-            print "\">";
-            print $this->getTestCaseProgress() . "/" . $this->getTestCaseCount();
-            print " test cases complete:\n";
-            print "<strong>" . $this->getPassCount() . "</strong> passes, ";
-            print "<strong>" . $this->getFailCount() . "</strong> fails and ";
-            print "<strong>" . $this->getExceptionCount() . "</strong> exceptions.";
-            print "</div>\n";
-            print "</body>\n</html>\n";
-        }
+    /**
+     *    Paints the CSS. Add additional styles here.
+     *    @return string            CSS code as text.
+     *    @access protected
+     */
+    function _getCss() {
+        return ".fail { background-color: inherit; color: red; }" .
+                ".pass { background-color: inherit; color: green; }" .
+                " pre { background-color: lightgray; color: inherit; }";
+    }
 
-        /**
-         *    Paints the test failure with a breadcrumbs
-         *    trail of the nesting test suites below the
-         *    top level test.
-         *    @param string $message    Failure message displayed in
-         *                              the context of the other tests.
-         *    @access public
-         */
-        function paintFail($message) {
-            parent::paintFail($message);
-            print "<span class=\"fail\">Fail</span>: ";
-            $breadcrumb = $this->getTestList();
-            array_shift($breadcrumb);
-            print implode(" -&gt; ", $breadcrumb);
-            print " -&gt; " . $this->_htmlEntities($message) . "<br />\n";
-        }
+    /**
+     *    Paints the end of the test with a summary of
+     *    the passes and failures.
+     *    @param string $test_name        Name class of test.
+     *    @access public
+     */
+    function paintFooter($test_name) {
+        $colour = ($this->getFailCount() + $this->getExceptionCount() > 0 ? "red" : "green");
+        print "<div style=\"";
+        print "padding: 8px; margin-top: 1em; background-color: $colour; color: white;";
+        print "\">";
+        print $this->getTestCaseProgress() . "/" . $this->getTestCaseCount();
+        print " test cases complete:\n";
+        print "<strong>" . $this->getPassCount() . "</strong> passes, ";
+        print "<strong>" . $this->getFailCount() . "</strong> fails and ";
+        print "<strong>" . $this->getExceptionCount() . "</strong> exceptions.";
+        print "</div>\n";
+        print "</body>\n</html>\n";
+    }
 
-        /**
-         *    Paints a PHP error.
-         *    @param string $message        Message is ignored.
-         *    @access public
-         */
-        function paintError($message) {
-            parent::paintError($message);
-            print "<span class=\"fail\">Exception</span>: ";
-            $breadcrumb = $this->getTestList();
-            array_shift($breadcrumb);
-            print implode(" -&gt; ", $breadcrumb);
-            print " -&gt; <strong>" . $this->_htmlEntities($message) . "</strong><br />\n";
-        }
+    /**
+     *    Paints the test failure with a breadcrumbs
+     *    trail of the nesting test suites below the
+     *    top level test.
+     *    @param string $message    Failure message displayed in
+     *                              the context of the other tests.
+     *    @access public
+     */
+    function paintFail($message) {
+        parent::paintFail($message);
+        print "<span class=\"fail\">Fail</span>: ";
+        $breadcrumb = $this->getTestList();
+        array_shift($breadcrumb);
+        print implode(" -&gt; ", $breadcrumb);
+        print " -&gt; " . $this->_htmlEntities($message) . "<br />\n";
+    }
 
-        /**
-         *    Paints a PHP exception.
-         *    @param Exception $exception        Exception to display.
-         *    @access public
-         */
-        function paintException($exception) {
-            parent::paintException($exception);
-            print "<span class=\"fail\">Exception</span>: ";
-            $breadcrumb = $this->getTestList();
-            array_shift($breadcrumb);
-            print implode(" -&gt; ", $breadcrumb);
-            $message = 'Unexpected exception of type [' . get_class($exception) .
-                    '] with message ['. $exception->getMessage() .
-                    '] in ['. $exception->getFile() .
-                    ' line ' . $exception->getLine() . ']';
-            print " -&gt; <strong>" . $this->_htmlEntities($message) . "</strong><br />\n";
-        }
-               
-               /**
-                *    Prints the message for skipping tests.
-         *    @param string $message    Text of skip condition.
-         *    @access public
-         */
-               function paintSkip($message) {
-            parent::paintSkip($message);
-            print "<span class=\"pass\">Skipped</span>: ";
-            $breadcrumb = $this->getTestList();
-            array_shift($breadcrumb);
-            print implode(" -&gt; ", $breadcrumb);
-            print " -&gt; " . $this->_htmlEntities($message) . "<br />\n";
-               }
+    /**
+     *    Paints a PHP error.
+     *    @param string $message        Message is ignored.
+     *    @access public
+     */
+    function paintError($message) {
+        parent::paintError($message);
+        print "<span class=\"fail\">Exception</span>: ";
+        $breadcrumb = $this->getTestList();
+        array_shift($breadcrumb);
+        print implode(" -&gt; ", $breadcrumb);
+        print " -&gt; <strong>" . $this->_htmlEntities($message) . "</strong><br />\n";
+    }
 
-        /**
-         *    Paints formatted text such as dumped variables.
-         *    @param string $message        Text to show.
-         *    @access public
-         */
-        function paintFormattedMessage($message) {
-            print '<pre>' . $this->_htmlEntities($message) . '</pre>';
-        }
+    /**
+     *    Paints a PHP exception.
+     *    @param Exception $exception        Exception to display.
+     *    @access public
+     */
+    function paintException($exception) {
+        parent::paintException($exception);
+        print "<span class=\"fail\">Exception</span>: ";
+        $breadcrumb = $this->getTestList();
+        array_shift($breadcrumb);
+        print implode(" -&gt; ", $breadcrumb);
+        $message = 'Unexpected exception of type [' . get_class($exception) .
+                '] with message ['. $exception->getMessage() .
+                '] in ['. $exception->getFile() .
+                ' line ' . $exception->getLine() . ']';
+        print " -&gt; <strong>" . $this->_htmlEntities($message) . "</strong><br />\n";
+    }
+    
+    /**
+     *    Prints the message for skipping tests.
+     *    @param string $message    Text of skip condition.
+     *    @access public
+     */
+    function paintSkip($message) {
+        parent::paintSkip($message);
+        print "<span class=\"pass\">Skipped</span>: ";
+        $breadcrumb = $this->getTestList();
+        array_shift($breadcrumb);
+        print implode(" -&gt; ", $breadcrumb);
+        print " -&gt; " . $this->_htmlEntities($message) . "<br />\n";
+    }
 
-        /**
-         *    Character set adjusted entity conversion.
-         *    @param string $message    Plain text or Unicode message.
-         *    @return string            Browser readable message.
-         *    @access protected
-         */
-        function _htmlEntities($message) {
-            return htmlentities($message, ENT_COMPAT, $this->_character_set);
-        }
+    /**
+     *    Paints formatted text such as dumped variables.
+     *    @param string $message        Text to show.
+     *    @access public
+     */
+    function paintFormattedMessage($message) {
+        print '<pre>' . $this->_htmlEntities($message) . '</pre>';
     }
 
     /**
-     *    Sample minimal test displayer. Generates only
-     *    failure messages and a pass count. For command
-     *    line use. I've tried to make it look like JUnit,
-     *    but I wanted to output the errors as they arrived
-     *    which meant dropping the dots.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Character set adjusted entity conversion.
+     *    @param string $message    Plain text or Unicode message.
+     *    @return string            Browser readable message.
+     *    @access protected
      */
-    class TextReporter extends SimpleReporter {
+    function _htmlEntities($message) {
+        return htmlentities($message, ENT_COMPAT, $this->_character_set);
+    }
+}
 
-        /**
-         *    Does nothing yet. The first output will
-         *    be sent on the first test start.
-         *    @access public
-         */
-        function TextReporter() {
-            $this->SimpleReporter();
-        }
+/**
+ *    Sample minimal test displayer. Generates only
+ *    failure messages and a pass count. For command
+ *    line use. I've tried to make it look like JUnit,
+ *    but I wanted to output the errors as they arrived
+ *    which meant dropping the dots.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class TextReporter extends SimpleReporter {
 
-        /**
-         *    Paints the title only.
-         *    @param string $test_name        Name class of test.
-         *    @access public
-         */
-        function paintHeader($test_name) {
-            if (! SimpleReporter::inCli()) {
-                header('Content-type: text/plain');
-            }
-            print "$test_name\n";
-            flush();
-        }
+    /**
+     *    Does nothing yet. The first output will
+     *    be sent on the first test start.
+     *    @access public
+     */
+    function TextReporter() {
+        $this->SimpleReporter();
+    }
 
-        /**
-         *    Paints the end of the test with a summary of
-         *    the passes and failures.
-         *    @param string $test_name        Name class of test.
-         *    @access public
-         */
-        function paintFooter($test_name) {
-            if ($this->getFailCount() + $this->getExceptionCount() == 0) {
-                print "OK\n";
-            } else {
-                print "FAILURES!!!\n";
-            }
-            print "Test cases run: " . $this->getTestCaseProgress() .
-                    "/" . $this->getTestCaseCount() .
-                    ", Passes: " . $this->getPassCount() .
-                    ", Failures: " . $this->getFailCount() .
-                    ", Exceptions: " . $this->getExceptionCount() . "\n";
+    /**
+     *    Paints the title only.
+     *    @param string $test_name        Name class of test.
+     *    @access public
+     */
+    function paintHeader($test_name) {
+        if (! SimpleReporter::inCli()) {
+            header('Content-type: text/plain');
         }
+        print "$test_name\n";
+        flush();
+    }
 
-        /**
-         *    Paints the test failure as a stack trace.
-         *    @param string $message    Failure message displayed in
-         *                              the context of the other tests.
-         *    @access public
-         */
-        function paintFail($message) {
-            parent::paintFail($message);
-            print $this->getFailCount() . ") $message\n";
-            $breadcrumb = $this->getTestList();
-            array_shift($breadcrumb);
-            print "\tin " . implode("\n\tin ", array_reverse($breadcrumb));
-            print "\n";
+    /**
+     *    Paints the end of the test with a summary of
+     *    the passes and failures.
+     *    @param string $test_name        Name class of test.
+     *    @access public
+     */
+    function paintFooter($test_name) {
+        if ($this->getFailCount() + $this->getExceptionCount() == 0) {
+            print "OK\n";
+        } else {
+            print "FAILURES!!!\n";
         }
+        print "Test cases run: " . $this->getTestCaseProgress() .
+                "/" . $this->getTestCaseCount() .
+                ", Passes: " . $this->getPassCount() .
+                ", Failures: " . $this->getFailCount() .
+                ", Exceptions: " . $this->getExceptionCount() . "\n";
+    }
 
-        /**
-         *    Paints a PHP error or exception.
-         *    @param string $message        Message to be shown.
-         *    @access public
-         *    @abstract
-         */
-        function paintError($message) {
-            parent::paintError($message);
-            print "Exception " . $this->getExceptionCount() . "!\n$message\n";
-        }
+    /**
+     *    Paints the test failure as a stack trace.
+     *    @param string $message    Failure message displayed in
+     *                              the context of the other tests.
+     *    @access public
+     */
+    function paintFail($message) {
+        parent::paintFail($message);
+        print $this->getFailCount() . ") $message\n";
+        $breadcrumb = $this->getTestList();
+        array_shift($breadcrumb);
+        print "\tin " . implode("\n\tin ", array_reverse($breadcrumb));
+        print "\n";
+    }
 
-        /**
-         *    Paints a PHP error or exception.
-         *    @param Exception $exception      Exception to describe.
-         *    @access public
-         *    @abstract
-         */
-        function paintException($exception) {
-            parent::paintException($exception);
-            $message = 'Unexpected exception of type [' . get_class($exception) .
-                    '] with message ['. $exception->getMessage() .
-                    '] in ['. $exception->getFile() .
-                    ' line ' . $exception->getLine() . ']';
-            print "Exception " . $this->getExceptionCount() . "!\n$message\n";
-        }
-               
-               /**
-                *    Prints the message for skipping tests.
-         *    @param string $message    Text of skip condition.
-         *    @access public
-         */
-               function paintSkip($message) {
-            parent::paintSkip($message);
-            print "Skip: $message\n";
-               }
+    /**
+     *    Paints a PHP error or exception.
+     *    @param string $message        Message to be shown.
+     *    @access public
+     *    @abstract
+     */
+    function paintError($message) {
+        parent::paintError($message);
+        print "Exception " . $this->getExceptionCount() . "!\n$message\n";
+        $breadcrumb = $this->getTestList();
+        array_shift($breadcrumb);
+        print "\tin " . implode("\n\tin ", array_reverse($breadcrumb));
+        print "\n";
+    }
 
-        /**
-         *    Paints formatted text such as dumped variables.
-         *    @param string $message        Text to show.
-         *    @access public
-         */
-        function paintFormattedMessage($message) {
-            print "$message\n";
-            flush();
-        }
+    /**
+     *    Paints a PHP error or exception.
+     *    @param Exception $exception      Exception to describe.
+     *    @access public
+     *    @abstract
+     */
+    function paintException($exception) {
+        parent::paintException($exception);
+        $message = 'Unexpected exception of type [' . get_class($exception) .
+                '] with message ['. $exception->getMessage() .
+                '] in ['. $exception->getFile() .
+                ' line ' . $exception->getLine() . ']';
+        print "Exception " . $this->getExceptionCount() . "!\n$message\n";
+        $breadcrumb = $this->getTestList();
+        array_shift($breadcrumb);
+        print "\tin " . implode("\n\tin ", array_reverse($breadcrumb));
+        print "\n";
+    }
+    
+    /**
+     *    Prints the message for skipping tests.
+     *    @param string $message    Text of skip condition.
+     *    @access public
+     */
+    function paintSkip($message) {
+        parent::paintSkip($message);
+        print "Skip: $message\n";
     }
 
     /**
-     *    Runs just a single test group, a single case or
-     *    even a single test within that case.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Paints formatted text such as dumped variables.
+     *    @param string $message        Text to show.
+     *    @access public
      */
-    class SelectiveReporter extends SimpleReporterDecorator {
-        var $_just_this_case =false;
-        var $_just_this_test = false;
-        var $_within_test_case = true;
+    function paintFormattedMessage($message) {
+        print "$message\n";
+        flush();
+    }
+}
 
-        /**
-         *    Selects the test case or group to be run,
-         *    and optionally a specific test.
-         *    @param SimpleScorer $reporter    Reporter to receive events.
-         *    @param string $just_this_case    Only this case or group will run.
-         *    @param string $just_this_test    Only this test method will run.
-         */
-        function SelectiveReporter(&$reporter, $just_this_case = false, $just_this_test = false) {
-            if (isset($just_this_case) && $just_this_case) {
-                $this->_just_this_case = strtolower($just_this_case);
-                $this->_within_test_case = false;
-            }
-            if (isset($just_this_test) && $just_this_test) {
-                $this->_just_this_test = strtolower($just_this_test);
-            }
-            $this->SimpleReporterDecorator($reporter);
+/**
+ *    Runs just a single test group, a single case or
+ *    even a single test within that case.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class SelectiveReporter extends SimpleReporterDecorator {
+    var $_just_this_case = false;
+    var $_just_this_test = false;
+    var $_on;
+    
+    /**
+     *    Selects the test case or group to be run,
+     *    and optionally a specific test.
+     *    @param SimpleScorer $reporter    Reporter to receive events.
+     *    @param string $just_this_case    Only this case or group will run.
+     *    @param string $just_this_test    Only this test method will run.
+     */
+    function SelectiveReporter(&$reporter, $just_this_case = false, $just_this_test = false) {
+        if (isset($just_this_case) && $just_this_case) {
+            $this->_just_this_case = strtolower($just_this_case);
+            $this->_off();
+        } else {
+            $this->_on();
         }
-
-        /**
-         *    Compares criteria to actual the case/group name.
-         *    @param string $test_case    The incoming test.
-         *    @return boolean             True if matched.
-         *    @access protected
-         */
-        function _isCaseMatch($test_case) {
-            if ($this->_just_this_case) {
-                return $this->_just_this_case == strtolower($test_case);
-            }
-            return false;
+        if (isset($just_this_test) && $just_this_test) {
+            $this->_just_this_test = strtolower($just_this_test);
         }
+        $this->SimpleReporterDecorator($reporter);
+    }
+
+    /**
+     *    Compares criteria to actual the case/group name.
+     *    @param string $test_case    The incoming test.
+     *    @return boolean             True if matched.
+     *    @access protected
+     */
+    function _matchesTestCase($test_case) {
+        return $this->_just_this_case == strtolower($test_case);
+    }
 
-        /**
-         *    Compares criteria to actual the test name.
-         *    @param string $method       The incoming test method.
-         *    @return boolean             True if matched.
-         *    @access protected
-         */
-        function _isTestMatch($method) {
+    /**
+     *    Compares criteria to actual the test name. If no
+     *    name was specified at the beginning, then all tests
+     *    can run.
+     *    @param string $method       The incoming test method.
+     *    @return boolean             True if matched.
+     *    @access protected
+     */
+    function _shouldRunTest($test_case, $method) {
+        if ($this->_isOn() || $this->_matchesTestCase($test_case)) {
             if ($this->_just_this_test) {
                 return $this->_just_this_test == strtolower($method);
-            }
-            return true;
-        }
-
-        /**
-         *    Veto everything that doesn't match the method wanted.
-         *    @param string $test_case       Name of test case.
-         *    @param string $method          Name of test method.
-         *    @return boolean                True if test should be run.
-         *    @access public
-         */
-        function shouldInvoke($test_case, $method) {
-            if ($this->_within_test_case && $this->_isTestMatch($method)) {
-                return $this->_reporter->shouldInvoke($test_case, $method);
-            }
-            return false;
-        }
-
-        /**
-         *    Paints the start of a group test.
-         *    @param string $test_case     Name of test or other label.
-         *    @param integer $size         Number of test cases starting.
-         *    @access public
-         */
-        function paintGroupStart($test_case, $size) {
-            if ($this->_isCaseMatch($test_case)) {
-                $this->_within_test_case = true;
-            }
-            if ($this->_within_test_case) {
-                $this->_reporter->paintGroupStart($test_case, $size);
+            } else {
+                return true;
             }
         }
+        return false;
+    }
+    
+    /**
+     *    Switch on testing for the group or subgroup.
+     *    @access private
+     */
+    function _on() {
+        $this->_on = true;
+    }
+    
+    /**
+     *    Switch off testing for the group or subgroup.
+     *    @access private
+     */
+    function _off() {
+        $this->_on = false;
+    }
+    
+    /**
+     *    Is this group actually being tested?
+     *    @return boolean     True if the current test group is active.
+     *    @access private
+     */
+    function _isOn() {
+        return $this->_on;
+    }
 
-        /**
-         *    Paints the end of a group test.
-         *    @param string $test_case     Name of test or other label.
-         *    @access public
-         */
-        function paintGroupEnd($test_case) {
-            if ($this->_within_test_case) {
-                $this->_reporter->paintGroupEnd($test_case);
-            }
-            if ($this->_isCaseMatch($test_case)) {
-                $this->_within_test_case = false;
-            }
+    /**
+     *    Veto everything that doesn't match the method wanted.
+     *    @param string $test_case       Name of test case.
+     *    @param string $method          Name of test method.
+     *    @return boolean                True if test should be run.
+     *    @access public
+     */
+    function shouldInvoke($test_case, $method) {
+        if ($this->_shouldRunTest($test_case, $method)) {
+            return $this->_reporter->shouldInvoke($test_case, $method);
         }
+        return false;
+    }
 
-        /**
-         *    Paints the start of a test case.
-         *    @param string $test_case     Name of test or other label.
-         *    @access public
-         */
-        function paintCaseStart($test_case) {
-            if ($this->_isCaseMatch($test_case)) {
-                $this->_within_test_case = true;
-            }
-            if ($this->_within_test_case) {
-                $this->_reporter->paintCaseStart($test_case);
-            }
+    /**
+     *    Paints the start of a group test.
+     *    @param string $test_case     Name of test or other label.
+     *    @param integer $size         Number of test cases starting.
+     *    @access public
+     */
+    function paintGroupStart($test_case, $size) {
+        if ($this->_just_this_case && $this->_matchesTestCase($test_case)) {
+            $this->_on();
         }
+        $this->_reporter->paintGroupStart($test_case, $size);
+    }
 
-        /**
-         *    Paints the end of a test case.
-         *    @param string $test_case     Name of test or other label.
-         *    @access public
-         */
-        function paintCaseEnd($test_case) {
-            if ($this->_within_test_case) {
-                $this->_reporter->paintCaseEnd($test_case);
-            }
-            if ($this->_isCaseMatch($test_case)) {
-                $this->_within_test_case = false;
-            }
+    /**
+     *    Paints the end of a group test.
+     *    @param string $test_case     Name of test or other label.
+     *    @access public
+     */
+    function paintGroupEnd($test_case) {
+        $this->_reporter->paintGroupEnd($test_case);
+        if ($this->_just_this_case && $this->_matchesTestCase($test_case)) {
+            $this->_off();
         }
     }
+}
+
+/**
+ *    Suppresses skip messages.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class NoSkipsReporter extends SimpleReporterDecorator {
+    
+    /**
+     *    Does nothing.
+     *    @param string $message    Text of skip condition.
+     *    @access public
+     */
+    function paintSkip($message) { }
+}
 ?>
\ No newline at end of file
index 279d714dbfbee4ea1683f2157fa7bb01d999bba1..b0dc65d2d80ab8f04df8b60a0601560c2137e950 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**#@+*/
+require_once(dirname(__FILE__) . '/invoker.php');
+/**#@-*/
+
+/**
+ *    Can receive test events and display them. Display
+ *    is achieved by making display methods available
+ *    and visiting the incoming event.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ *    @abstract
+ */
+class SimpleScorer {
+    var $_passes;
+    var $_fails;
+    var $_exceptions;
+    var $_is_dry_run;
+
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
+     *    Starts the test run with no results.
+     *    @access public
      */
+    function SimpleScorer() {
+        $this->_passes = 0;
+        $this->_fails = 0;
+        $this->_exceptions = 0;
+        $this->_is_dry_run = false;
+    }
 
-    /**#@+*/
-    require_once(dirname(__FILE__) . '/invoker.php');
-    /**#@-*/
+    /**
+     *    Signals that the next evaluation will be a dry
+     *    run. That is, the structure events will be
+     *    recorded, but no tests will be run.
+     *    @param boolean $is_dry        Dry run if true.
+     *    @access public
+     */
+    function makeDry($is_dry = true) {
+        $this->_is_dry_run = $is_dry;
+    }
 
     /**
-     *    Can recieve test events and display them. Display
-     *    is achieved by making display methods available
-     *    and visiting the incoming event.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
-     *    @abstract
+     *    The reporter has a veto on what should be run.
+     *    @param string $test_case_name  name of test case.
+     *    @param string $method          Name of test method.
+     *    @access public
      */
-    class SimpleScorer {
-        var $_passes;
-        var $_fails;
-        var $_exceptions;
-        var $_is_dry_run;
-
-        /**
-         *    Starts the test run with no results.
-         *    @access public
-         */
-        function SimpleScorer() {
-            $this->_passes = 0;
-            $this->_fails = 0;
-            $this->_exceptions = 0;
-            $this->_is_dry_run = false;
-        }
+    function shouldInvoke($test_case_name, $method) {
+        return ! $this->_is_dry_run;
+    }
 
-        /**
-         *    Signals that the next evaluation will be a dry
-         *    run. That is, the structure events will be
-         *    recorded, but no tests will be run.
-         *    @param boolean $is_dry        Dry run if true.
-         *    @access public
-         */
-        function makeDry($is_dry = true) {
-            $this->_is_dry_run = $is_dry;
-        }
+    /**
+     *    Can wrap the invoker in preperation for running
+     *    a test.
+     *    @param SimpleInvoker $invoker   Individual test runner.
+     *    @return SimpleInvoker           Wrapped test runner.
+     *    @access public
+     */
+    function &createInvoker(&$invoker) {
+        return $invoker;
+    }
 
-        /**
-         *    The reporter has a veto on what should be run.
-         *    @param string $test_case_name  name of test case.
-         *    @param string $method          Name of test method.
-         *    @access public
-         */
-        function shouldInvoke($test_case_name, $method) {
-            return ! $this->_is_dry_run;
+    /**
+     *    Accessor for current status. Will be false
+     *    if there have been any failures or exceptions.
+     *    Used for command line tools.
+     *    @return boolean        True if no failures.
+     *    @access public
+     */
+    function getStatus() {
+        if ($this->_exceptions + $this->_fails > 0) {
+            return false;
         }
+        return true;
+    }
 
-        /**
-         *    Can wrap the invoker in preperation for running
-         *    a test.
-         *    @param SimpleInvoker $invoker   Individual test runner.
-         *    @return SimpleInvoker           Wrapped test runner.
-         *    @access public
-         */
-        function &createInvoker(&$invoker) {
-            return $invoker;
-        }
+    /**
+     *    Paints the start of a group test.
+     *    @param string $test_name     Name of test or other label.
+     *    @param integer $size         Number of test cases starting.
+     *    @access public
+     */
+    function paintGroupStart($test_name, $size) {
+    }
 
-        /**
-         *    Accessor for current status. Will be false
-         *    if there have been any failures or exceptions.
-         *    Used for command line tools.
-         *    @return boolean        True if no failures.
-         *    @access public
-         */
-        function getStatus() {
-            if ($this->_exceptions + $this->_fails > 0) {
-                return false;
-            }
-            return true;
-        }
+    /**
+     *    Paints the end of a group test.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintGroupEnd($test_name) {
+    }
 
-        /**
-         *    Paints the start of a group test.
-         *    @param string $test_name     Name of test or other label.
-         *    @param integer $size         Number of test cases starting.
-         *    @access public
-         */
-        function paintGroupStart($test_name, $size) {
-        }
+    /**
+     *    Paints the start of a test case.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintCaseStart($test_name) {
+    }
 
-        /**
-         *    Paints the end of a group test.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintGroupEnd($test_name) {
-        }
+    /**
+     *    Paints the end of a test case.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintCaseEnd($test_name) {
+    }
 
-        /**
-         *    Paints the start of a test case.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintCaseStart($test_name) {
-        }
+    /**
+     *    Paints the start of a test method.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintMethodStart($test_name) {
+    }
 
-        /**
-         *    Paints the end of a test case.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintCaseEnd($test_name) {
-        }
+    /**
+     *    Paints the end of a test method.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintMethodEnd($test_name) {
+    }
 
-        /**
-         *    Paints the start of a test method.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintMethodStart($test_name) {
-        }
+    /**
+     *    Increments the pass count.
+     *    @param string $message        Message is ignored.
+     *    @access public
+     */
+    function paintPass($message) {
+        $this->_passes++;
+    }
 
-        /**
-         *    Paints the end of a test method.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintMethodEnd($test_name) {
-        }
+    /**
+     *    Increments the fail count.
+     *    @param string $message        Message is ignored.
+     *    @access public
+     */
+    function paintFail($message) {
+        $this->_fails++;
+    }
 
-        /**
-         *    Increments the pass count.
-         *    @param string $message        Message is ignored.
-         *    @access public
-         */
-        function paintPass($message) {
-            $this->_passes++;
-        }
+    /**
+     *    Deals with PHP 4 throwing an error.
+     *    @param string $message    Text of error formatted by
+     *                              the test case.
+     *    @access public
+     */
+    function paintError($message) {
+        $this->_exceptions++;
+    }
 
-        /**
-         *    Increments the fail count.
-         *    @param string $message        Message is ignored.
-         *    @access public
-         */
-        function paintFail($message) {
-            $this->_fails++;
-        }
+    /**
+     *    Deals with PHP 5 throwing an exception.
+     *    @param Exception $exception    The actual exception thrown.
+     *    @access public
+     */
+    function paintException($exception) {
+        $this->_exceptions++;
+    }
+    
+    /**
+     *    Prints the message for skipping tests.
+     *    @param string $message    Text of skip condition.
+     *    @access public
+     */
+    function paintSkip($message) {
+    }
 
-        /**
-         *    Deals with PHP 4 throwing an error.
-         *    @param string $message    Text of error formatted by
-         *                              the test case.
-         *    @access public
-         */
-        function paintError($message) {
-            $this->_exceptions++;
-        }
+    /**
+     *    Accessor for the number of passes so far.
+     *    @return integer       Number of passes.
+     *    @access public
+     */
+    function getPassCount() {
+        return $this->_passes;
+    }
 
-        /**
-         *    Deals with PHP 5 throwing an exception.
-         *    @param Exception $exception    The actual exception thrown.
-         *    @access public
-         */
-        function paintException($exception) {
-            $this->_exceptions++;
-        }
-               
-               /**
-                *    Prints the message for skipping tests.
-         *    @param string $message    Text of skip condition.
-         *    @access public
-         */
-               function paintSkip($message) {
-               }
-
-        /**
-         *    Accessor for the number of passes so far.
-         *    @return integer       Number of passes.
-         *    @access public
-         */
-        function getPassCount() {
-            return $this->_passes;
-        }
+    /**
+     *    Accessor for the number of fails so far.
+     *    @return integer       Number of fails.
+     *    @access public
+     */
+    function getFailCount() {
+        return $this->_fails;
+    }
 
-        /**
-         *    Accessor for the number of fails so far.
-         *    @return integer       Number of fails.
-         *    @access public
-         */
-        function getFailCount() {
-            return $this->_fails;
-        }
+    /**
+     *    Accessor for the number of untrapped errors
+     *    so far.
+     *    @return integer       Number of exceptions.
+     *    @access public
+     */
+    function getExceptionCount() {
+        return $this->_exceptions;
+    }
 
-        /**
-         *    Accessor for the number of untrapped errors
-         *    so far.
-         *    @return integer       Number of exceptions.
-         *    @access public
-         */
-        function getExceptionCount() {
-            return $this->_exceptions;
-        }
+    /**
+     *    Paints a simple supplementary message.
+     *    @param string $message        Text to display.
+     *    @access public
+     */
+    function paintMessage($message) {
+    }
 
-        /**
-         *    Paints a simple supplementary message.
-         *    @param string $message        Text to display.
-         *    @access public
-         */
-        function paintMessage($message) {
-        }
+    /**
+     *    Paints a formatted ASCII message such as a
+     *    variable dump.
+     *    @param string $message        Text to display.
+     *    @access public
+     */
+    function paintFormattedMessage($message) {
+    }
 
-        /**
-         *    Paints a formatted ASCII message such as a
-         *    variable dump.
-         *    @param string $message        Text to display.
-         *    @access public
-         */
-        function paintFormattedMessage($message) {
-        }
+    /**
+     *    By default just ignores user generated events.
+     *    @param string $type        Event type as text.
+     *    @param mixed $payload      Message or object.
+     *    @access public
+     */
+    function paintSignal($type, $payload) {
+    }
+}
+
+/**
+ *    Recipient of generated test messages that can display
+ *    page footers and headers. Also keeps track of the
+ *    test nesting. This is the main base class on which
+ *    to build the finished test (page based) displays.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class SimpleReporter extends SimpleScorer {
+    var $_test_stack;
+    var $_size;
+    var $_progress;
 
-        /**
-         *    By default just ignores user generated events.
-         *    @param string $type        Event type as text.
-         *    @param mixed $payload      Message or object.
-         *    @access public
-         */
-        function paintSignal($type, $payload) {
-        }
+    /**
+     *    Starts the display with no results in.
+     *    @access public
+     */
+    function SimpleReporter() {
+        $this->SimpleScorer();
+        $this->_test_stack = array();
+        $this->_size = null;
+        $this->_progress = 0;
+    }
+    
+    /**
+     *    Gets the formatter for variables and other small
+     *    generic data items.
+     *    @return SimpleDumper          Formatter.
+     *    @access public
+     */
+    function getDumper() {
+        return new SimpleDumper();
     }
 
     /**
-     *    Recipient of generated test messages that can display
-     *    page footers and headers. Also keeps track of the
-     *    test nesting. This is the main base class on which
-     *    to build the finished test (page based) displays.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
-     */
-    class SimpleReporter extends SimpleScorer {
-        var $_test_stack;
-        var $_size;
-        var $_progress;
-
-        /**
-         *    Starts the display with no results in.
-         *    @access public
-         */
-        function SimpleReporter() {
-            $this->SimpleScorer();
-            $this->_test_stack = array();
-            $this->_size = null;
-            $this->_progress = 0;
+     *    Paints the start of a group test. Will also paint
+     *    the page header and footer if this is the
+     *    first test. Will stash the size if the first
+     *    start.
+     *    @param string $test_name   Name of test that is starting.
+     *    @param integer $size       Number of test cases starting.
+     *    @access public
+     */
+    function paintGroupStart($test_name, $size) {
+        if (! isset($this->_size)) {
+            $this->_size = $size;
         }
-        
-        /**
-         *    Gets the formatter for variables and other small
-         *    generic data items.
-         *    @return SimpleDumper          Formatter.
-         *    @access public
-         */
-        function getDumper() {
-            return new SimpleDumper();
+        if (count($this->_test_stack) == 0) {
+            $this->paintHeader($test_name);
         }
+        $this->_test_stack[] = $test_name;
+    }
 
-        /**
-         *    Paints the start of a group test. Will also paint
-         *    the page header and footer if this is the
-         *    first test. Will stash the size if the first
-         *    start.
-         *    @param string $test_name   Name of test that is starting.
-         *    @param integer $size       Number of test cases starting.
-         *    @access public
-         */
-        function paintGroupStart($test_name, $size) {
-            if (! isset($this->_size)) {
-                $this->_size = $size;
-            }
-            if (count($this->_test_stack) == 0) {
-                $this->paintHeader($test_name);
-            }
-            $this->_test_stack[] = $test_name;
+    /**
+     *    Paints the end of a group test. Will paint the page
+     *    footer if the stack of tests has unwound.
+     *    @param string $test_name   Name of test that is ending.
+     *    @param integer $progress   Number of test cases ending.
+     *    @access public
+     */
+    function paintGroupEnd($test_name) {
+        array_pop($this->_test_stack);
+        if (count($this->_test_stack) == 0) {
+            $this->paintFooter($test_name);
         }
+    }
 
-        /**
-         *    Paints the end of a group test. Will paint the page
-         *    footer if the stack of tests has unwound.
-         *    @param string $test_name   Name of test that is ending.
-         *    @param integer $progress   Number of test cases ending.
-         *    @access public
-         */
-        function paintGroupEnd($test_name) {
-            array_pop($this->_test_stack);
-            if (count($this->_test_stack) == 0) {
-                $this->paintFooter($test_name);
-            }
+    /**
+     *    Paints the start of a test case. Will also paint
+     *    the page header and footer if this is the
+     *    first test. Will stash the size if the first
+     *    start.
+     *    @param string $test_name   Name of test that is starting.
+     *    @access public
+     */
+    function paintCaseStart($test_name) {
+        if (! isset($this->_size)) {
+            $this->_size = 1;
         }
-
-        /**
-         *    Paints the start of a test case. Will also paint
-         *    the page header and footer if this is the
-         *    first test. Will stash the size if the first
-         *    start.
-         *    @param string $test_name   Name of test that is starting.
-         *    @access public
-         */
-        function paintCaseStart($test_name) {
-            if (! isset($this->_size)) {
-                $this->_size = 1;
-            }
-            if (count($this->_test_stack) == 0) {
-                $this->paintHeader($test_name);
-            }
-            $this->_test_stack[] = $test_name;
+        if (count($this->_test_stack) == 0) {
+            $this->paintHeader($test_name);
         }
+        $this->_test_stack[] = $test_name;
+    }
 
-        /**
-         *    Paints the end of a test case. Will paint the page
-         *    footer if the stack of tests has unwound.
-         *    @param string $test_name   Name of test that is ending.
-         *    @access public
-         */
-        function paintCaseEnd($test_name) {
-            $this->_progress++;
-            array_pop($this->_test_stack);
-            if (count($this->_test_stack) == 0) {
-                $this->paintFooter($test_name);
-            }
+    /**
+     *    Paints the end of a test case. Will paint the page
+     *    footer if the stack of tests has unwound.
+     *    @param string $test_name   Name of test that is ending.
+     *    @access public
+     */
+    function paintCaseEnd($test_name) {
+        $this->_progress++;
+        array_pop($this->_test_stack);
+        if (count($this->_test_stack) == 0) {
+            $this->paintFooter($test_name);
         }
+    }
 
-        /**
-         *    Paints the start of a test method.
-         *    @param string $test_name   Name of test that is starting.
-         *    @access public
-         */
-        function paintMethodStart($test_name) {
-            $this->_test_stack[] = $test_name;
-        }
+    /**
+     *    Paints the start of a test method.
+     *    @param string $test_name   Name of test that is starting.
+     *    @access public
+     */
+    function paintMethodStart($test_name) {
+        $this->_test_stack[] = $test_name;
+    }
 
-        /**
-         *    Paints the end of a test method. Will paint the page
-         *    footer if the stack of tests has unwound.
-         *    @param string $test_name   Name of test that is ending.
-         *    @access public
-         */
-        function paintMethodEnd($test_name) {
-            array_pop($this->_test_stack);
-        }
+    /**
+     *    Paints the end of a test method. Will paint the page
+     *    footer if the stack of tests has unwound.
+     *    @param string $test_name   Name of test that is ending.
+     *    @access public
+     */
+    function paintMethodEnd($test_name) {
+        array_pop($this->_test_stack);
+    }
 
-        /**
-         *    Paints the test document header.
-         *    @param string $test_name     First test top level
-         *                                 to start.
-         *    @access public
-         *    @abstract
-         */
-        function paintHeader($test_name) {
-        }
+    /**
+     *    Paints the test document header.
+     *    @param string $test_name     First test top level
+     *                                 to start.
+     *    @access public
+     *    @abstract
+     */
+    function paintHeader($test_name) {
+    }
 
-        /**
-         *    Paints the test document footer.
-         *    @param string $test_name        The top level test.
-         *    @access public
-         *    @abstract
-         */
-        function paintFooter($test_name) {
-        }
+    /**
+     *    Paints the test document footer.
+     *    @param string $test_name        The top level test.
+     *    @access public
+     *    @abstract
+     */
+    function paintFooter($test_name) {
+    }
 
-        /**
-         *    Accessor for internal test stack. For
-         *    subclasses that need to see the whole test
-         *    history for display purposes.
-         *    @return array     List of methods in nesting order.
-         *    @access public
-         */
-        function getTestList() {
-            return $this->_test_stack;
-        }
+    /**
+     *    Accessor for internal test stack. For
+     *    subclasses that need to see the whole test
+     *    history for display purposes.
+     *    @return array     List of methods in nesting order.
+     *    @access public
+     */
+    function getTestList() {
+        return $this->_test_stack;
+    }
 
-        /**
-         *    Accessor for total test size in number
-         *    of test cases. Null until the first
-         *    test is started.
-         *    @return integer   Total number of cases at start.
-         *    @access public
-         */
-        function getTestCaseCount() {
-            return $this->_size;
-        }
+    /**
+     *    Accessor for total test size in number
+     *    of test cases. Null until the first
+     *    test is started.
+     *    @return integer   Total number of cases at start.
+     *    @access public
+     */
+    function getTestCaseCount() {
+        return $this->_size;
+    }
 
-        /**
-         *    Accessor for the number of test cases
-         *    completed so far.
-         *    @return integer   Number of ended cases.
-         *    @access public
-         */
-        function getTestCaseProgress() {
-            return $this->_progress;
-        }
+    /**
+     *    Accessor for the number of test cases
+     *    completed so far.
+     *    @return integer   Number of ended cases.
+     *    @access public
+     */
+    function getTestCaseProgress() {
+        return $this->_progress;
+    }
 
-        /**
-         *    Static check for running in the comand line.
-         *    @return boolean        True if CLI.
-         *    @access public
-         *    @static
-         */
-        function inCli() {
-            return php_sapi_name() == 'cli';
-        }
+    /**
+     *    Static check for running in the comand line.
+     *    @return boolean        True if CLI.
+     *    @access public
+     *    @static
+     */
+    function inCli() {
+        return php_sapi_name() == 'cli';
     }
+}
+
+/**
+ *    For modifying the behaviour of the visual reporters.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class SimpleReporterDecorator {
+    var $_reporter;
 
     /**
-     *    For modifying the behaviour of the visual reporters.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Mediates between the reporter and the test case.
+     *    @param SimpleScorer $reporter       Reporter to receive events.
      */
-    class SimpleReporterDecorator {
-        var $_reporter;
+    function SimpleReporterDecorator(&$reporter) {
+        $this->_reporter = &$reporter;
+    }
 
-        /**
-         *    Mediates between the reporter and the test case.
-         *    @param SimpleScorer $reporter       Reporter to receive events.
-         */
-        function SimpleReporterDecorator(&$reporter) {
-            $this->_reporter = &$reporter;
-        }
+    /**
+     *    Signals that the next evaluation will be a dry
+     *    run. That is, the structure events will be
+     *    recorded, but no tests will be run.
+     *    @param boolean $is_dry        Dry run if true.
+     *    @access public
+     */
+    function makeDry($is_dry = true) {
+        $this->_reporter->makeDry($is_dry);
+    }
 
-        /**
-         *    Signals that the next evaluation will be a dry
-         *    run. That is, the structure events will be
-         *    recorded, but no tests will be run.
-         *    @param boolean $is_dry        Dry run if true.
-         *    @access public
-         */
-        function makeDry($is_dry = true) {
-            $this->_reporter->makeDry($is_dry);
-        }
+    /**
+     *    Accessor for current status. Will be false
+     *    if there have been any failures or exceptions.
+     *    Used for command line tools.
+     *    @return boolean        True if no failures.
+     *    @access public
+     */
+    function getStatus() {
+        return $this->_reporter->getStatus();
+    }
 
-        /**
-         *    Accessor for current status. Will be false
-         *    if there have been any failures or exceptions.
-         *    Used for command line tools.
-         *    @return boolean        True if no failures.
-         *    @access public
-         */
-        function getStatus() {
-            return $this->_reporter->getStatus();
-        }
+    /**
+     *    The reporter has a veto on what should be run.
+     *    @param string $test_case_name  name of test case.
+     *    @param string $method          Name of test method.
+     *    @return boolean                True if test should be run.
+     *    @access public
+     */
+    function shouldInvoke($test_case_name, $method) {
+        return $this->_reporter->shouldInvoke($test_case_name, $method);
+    }
 
-        /**
-         *    The reporter has a veto on what should be run.
-         *    @param string $test_case_name  name of test case.
-         *    @param string $method          Name of test method.
-         *    @return boolean                True if test should be run.
-         *    @access public
-         */
-        function shouldInvoke($test_case_name, $method) {
-            return $this->_reporter->shouldInvoke($test_case_name, $method);
-        }
+    /**
+     *    Can wrap the invoker in preperation for running
+     *    a test.
+     *    @param SimpleInvoker $invoker   Individual test runner.
+     *    @return SimpleInvoker           Wrapped test runner.
+     *    @access public
+     */
+    function &createInvoker(&$invoker) {
+        return $this->_reporter->createInvoker($invoker);
+    }
+    
+    /**
+     *    Gets the formatter for variables and other small
+     *    generic data items.
+     *    @return SimpleDumper          Formatter.
+     *    @access public
+     */
+    function getDumper() {
+        return $this->_reporter->getDumper();
+    }
 
-        /**
-         *    Can wrap the invoker in preperation for running
-         *    a test.
-         *    @param SimpleInvoker $invoker   Individual test runner.
-         *    @return SimpleInvoker           Wrapped test runner.
-         *    @access public
-         */
-        function &createInvoker(&$invoker) {
-            return $this->_reporter->createInvoker($invoker);
-        }
-        
-        /**
-         *    Gets the formatter for variables and other small
-         *    generic data items.
-         *    @return SimpleDumper          Formatter.
-         *    @access public
-         */
-        function getDumper() {
-            return $this->_reporter->getDumper();
-        }
+    /**
+     *    Paints the start of a group test.
+     *    @param string $test_name     Name of test or other label.
+     *    @param integer $size         Number of test cases starting.
+     *    @access public
+     */
+    function paintGroupStart($test_name, $size) {
+        $this->_reporter->paintGroupStart($test_name, $size);
+    }
 
-        /**
-         *    Paints the start of a group test.
-         *    @param string $test_name     Name of test or other label.
-         *    @param integer $size         Number of test cases starting.
-         *    @access public
-         */
-        function paintGroupStart($test_name, $size) {
-            $this->_reporter->paintGroupStart($test_name, $size);
-        }
+    /**
+     *    Paints the end of a group test.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintGroupEnd($test_name) {
+        $this->_reporter->paintGroupEnd($test_name);
+    }
 
-        /**
-         *    Paints the end of a group test.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintGroupEnd($test_name) {
-            $this->_reporter->paintGroupEnd($test_name);
-        }
+    /**
+     *    Paints the start of a test case.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintCaseStart($test_name) {
+        $this->_reporter->paintCaseStart($test_name);
+    }
 
-        /**
-         *    Paints the start of a test case.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintCaseStart($test_name) {
-            $this->_reporter->paintCaseStart($test_name);
-        }
+    /**
+     *    Paints the end of a test case.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintCaseEnd($test_name) {
+        $this->_reporter->paintCaseEnd($test_name);
+    }
 
-        /**
-         *    Paints the end of a test case.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintCaseEnd($test_name) {
-            $this->_reporter->paintCaseEnd($test_name);
-        }
+    /**
+     *    Paints the start of a test method.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintMethodStart($test_name) {
+        $this->_reporter->paintMethodStart($test_name);
+    }
 
-        /**
-         *    Paints the start of a test method.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintMethodStart($test_name) {
-            $this->_reporter->paintMethodStart($test_name);
-        }
+    /**
+     *    Paints the end of a test method.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintMethodEnd($test_name) {
+        $this->_reporter->paintMethodEnd($test_name);
+    }
 
-        /**
-         *    Paints the end of a test method.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintMethodEnd($test_name) {
-            $this->_reporter->paintMethodEnd($test_name);
-        }
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param string $message        Message is ignored.
+     *    @access public
+     */
+    function paintPass($message) {
+        $this->_reporter->paintPass($message);
+    }
 
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param string $message        Message is ignored.
-         *    @access public
-         */
-        function paintPass($message) {
-            $this->_reporter->paintPass($message);
-        }
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param string $message        Message is ignored.
+     *    @access public
+     */
+    function paintFail($message) {
+        $this->_reporter->paintFail($message);
+    }
 
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param string $message        Message is ignored.
-         *    @access public
-         */
-        function paintFail($message) {
-            $this->_reporter->paintFail($message);
-        }
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param string $message    Text of error formatted by
+     *                              the test case.
+     *    @access public
+     */
+    function paintError($message) {
+        $this->_reporter->paintError($message);
+    }
 
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param string $message    Text of error formatted by
-         *                              the test case.
-         *    @access public
-         */
-        function paintError($message) {
-            $this->_reporter->paintError($message);
-        }
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param Exception $exception        Exception to show.
+     *    @access public
+     */
+    function paintException($exception) {
+        $this->_reporter->paintException($exception);
+    }
+    
+    /**
+     *    Prints the message for skipping tests.
+     *    @param string $message    Text of skip condition.
+     *    @access public
+     */
+    function paintSkip($message) {
+        $this->_reporter->paintSkip($message);
+    }
 
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param Exception $exception        Exception to show.
-         *    @access public
-         */
-        function paintException($exception) {
-            $this->_reporter->paintException($exception);
-        }
-               
-               /**
-                *    Prints the message for skipping tests.
-         *    @param string $message    Text of skip condition.
-         *    @access public
-         */
-               function paintSkip($message) {
-            $this->_reporter->paintSkip($message);
-               }
-
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param string $message        Text to display.
-         *    @access public
-         */
-        function paintMessage($message) {
-            $this->_reporter->paintMessage($message);
-        }
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param string $message        Text to display.
+     *    @access public
+     */
+    function paintMessage($message) {
+        $this->_reporter->paintMessage($message);
+    }
 
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param string $message        Text to display.
-         *    @access public
-         */
-        function paintFormattedMessage($message) {
-            $this->_reporter->paintFormattedMessage($message);
-        }
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param string $message        Text to display.
+     *    @access public
+     */
+    function paintFormattedMessage($message) {
+        $this->_reporter->paintFormattedMessage($message);
+    }
 
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param string $type        Event type as text.
-         *    @param mixed $payload      Message or object.
-         *    @return boolean            Should return false if this
-         *                               type of signal should fail the
-         *                               test suite.
-         *    @access public
-         */
-        function paintSignal($type, &$payload) {
-            $this->_reporter->paintSignal($type, $payload);
-        }
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param string $type        Event type as text.
+     *    @param mixed $payload      Message or object.
+     *    @return boolean            Should return false if this
+     *                               type of signal should fail the
+     *                               test suite.
+     *    @access public
+     */
+    function paintSignal($type, &$payload) {
+        $this->_reporter->paintSignal($type, $payload);
     }
+}
+
+/**
+ *    For sending messages to multiple reporters at
+ *    the same time.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class MultipleReporter {
+    var $_reporters = array();
 
     /**
-     *    For sending messages to multiple reporters at
-     *    the same time.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Adds a reporter to the subscriber list.
+     *    @param SimpleScorer $reporter     Reporter to receive events.
+     *    @access public
      */
-    class MultipleReporter {
-        var $_reporters = array();
+    function attachReporter(&$reporter) {
+        $this->_reporters[] = &$reporter;
+    }
 
-        /**
-         *    Adds a reporter to the subscriber list.
-         *    @param SimpleScorer $reporter     Reporter to receive events.
-         *    @access public
-         */
-        function attachReporter(&$reporter) {
-            $this->_reporters[] = &$reporter;
+    /**
+     *    Signals that the next evaluation will be a dry
+     *    run. That is, the structure events will be
+     *    recorded, but no tests will be run.
+     *    @param boolean $is_dry        Dry run if true.
+     *    @access public
+     */
+    function makeDry($is_dry = true) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->makeDry($is_dry);
         }
+    }
 
-        /**
-         *    Signals that the next evaluation will be a dry
-         *    run. That is, the structure events will be
-         *    recorded, but no tests will be run.
-         *    @param boolean $is_dry        Dry run if true.
-         *    @access public
-         */
-        function makeDry($is_dry = true) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->makeDry($is_dry);
+    /**
+     *    Accessor for current status. Will be false
+     *    if there have been any failures or exceptions.
+     *    If any reporter reports a failure, the whole
+     *    suite fails.
+     *    @return boolean        True if no failures.
+     *    @access public
+     */
+    function getStatus() {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            if (! $this->_reporters[$i]->getStatus()) {
+                return false;
             }
         }
+        return true;
+    }
 
-        /**
-         *    Accessor for current status. Will be false
-         *    if there have been any failures or exceptions.
-         *    If any reporter reports a failure, the whole
-         *    suite fails.
-         *    @return boolean        True if no failures.
-         *    @access public
-         */
-        function getStatus() {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                if (! $this->_reporters[$i]->getStatus()) {
-                    return false;
-                }
+    /**
+     *    The reporter has a veto on what should be run.
+     *    It requires all reporters to want to run the method.
+     *    @param string $test_case_name  name of test case.
+     *    @param string $method          Name of test method.
+     *    @access public
+     */
+    function shouldInvoke($test_case_name, $method) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            if (! $this->_reporters[$i]->shouldInvoke($test_case_name, $method)) {
+                return false;
             }
-            return true;
         }
+        return true;
+    }
 
-        /**
-         *    The reporter has a veto on what should be run.
-         *    It requires all reporters to want to run the method.
-         *    @param string $test_case_name  name of test case.
-         *    @param string $method          Name of test method.
-         *    @access public
-         */
-        function shouldInvoke($test_case_name, $method) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                if (! $this->_reporters[$i]->shouldInvoke($test_case_name, $method)) {
-                    return false;
-                }
-            }
-            return true;
+    /**
+     *    Every reporter gets a chance to wrap the invoker.
+     *    @param SimpleInvoker $invoker   Individual test runner.
+     *    @return SimpleInvoker           Wrapped test runner.
+     *    @access public
+     */
+    function &createInvoker(&$invoker) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $invoker = &$this->_reporters[$i]->createInvoker($invoker);
         }
+        return $invoker;
+    }
+    
+    /**
+     *    Gets the formatter for variables and other small
+     *    generic data items.
+     *    @return SimpleDumper          Formatter.
+     *    @access public
+     */
+    function getDumper() {
+        return new SimpleDumper();
+    }
 
-        /**
-         *    Every reporter gets a chance to wrap the invoker.
-         *    @param SimpleInvoker $invoker   Individual test runner.
-         *    @return SimpleInvoker           Wrapped test runner.
-         *    @access public
-         */
-        function &createInvoker(&$invoker) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $invoker = &$this->_reporters[$i]->createInvoker($invoker);
-            }
-            return $invoker;
-        }
-        
-        /**
-         *    Gets the formatter for variables and other small
-         *    generic data items.
-         *    @return SimpleDumper          Formatter.
-         *    @access public
-         */
-        function getDumper() {
-            return new SimpleDumper();
+    /**
+     *    Paints the start of a group test.
+     *    @param string $test_name     Name of test or other label.
+     *    @param integer $size         Number of test cases starting.
+     *    @access public
+     */
+    function paintGroupStart($test_name, $size) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintGroupStart($test_name, $size);
         }
+    }
 
-        /**
-         *    Paints the start of a group test.
-         *    @param string $test_name     Name of test or other label.
-         *    @param integer $size         Number of test cases starting.
-         *    @access public
-         */
-        function paintGroupStart($test_name, $size) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintGroupStart($test_name, $size);
-            }
+    /**
+     *    Paints the end of a group test.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintGroupEnd($test_name) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintGroupEnd($test_name);
         }
+    }
 
-        /**
-         *    Paints the end of a group test.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintGroupEnd($test_name) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintGroupEnd($test_name);
-            }
+    /**
+     *    Paints the start of a test case.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintCaseStart($test_name) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintCaseStart($test_name);
         }
+    }
 
-        /**
-         *    Paints the start of a test case.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintCaseStart($test_name) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintCaseStart($test_name);
-            }
+    /**
+     *    Paints the end of a test case.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintCaseEnd($test_name) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintCaseEnd($test_name);
         }
+    }
 
-        /**
-         *    Paints the end of a test case.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintCaseEnd($test_name) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintCaseEnd($test_name);
-            }
+    /**
+     *    Paints the start of a test method.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintMethodStart($test_name) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintMethodStart($test_name);
         }
+    }
 
-        /**
-         *    Paints the start of a test method.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintMethodStart($test_name) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintMethodStart($test_name);
-            }
+    /**
+     *    Paints the end of a test method.
+     *    @param string $test_name     Name of test or other label.
+     *    @access public
+     */
+    function paintMethodEnd($test_name) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintMethodEnd($test_name);
         }
+    }
 
-        /**
-         *    Paints the end of a test method.
-         *    @param string $test_name     Name of test or other label.
-         *    @access public
-         */
-        function paintMethodEnd($test_name) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintMethodEnd($test_name);
-            }
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param string $message        Message is ignored.
+     *    @access public
+     */
+    function paintPass($message) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintPass($message);
         }
+    }
 
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param string $message        Message is ignored.
-         *    @access public
-         */
-        function paintPass($message) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintPass($message);
-            }
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param string $message        Message is ignored.
+     *    @access public
+     */
+    function paintFail($message) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintFail($message);
         }
+    }
 
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param string $message        Message is ignored.
-         *    @access public
-         */
-        function paintFail($message) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintFail($message);
-            }
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param string $message    Text of error formatted by
+     *                              the test case.
+     *    @access public
+     */
+    function paintError($message) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintError($message);
         }
-
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param string $message    Text of error formatted by
-         *                              the test case.
-         *    @access public
-         */
-        function paintError($message) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintError($message);
-            }
+    }
+    
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param Exception $exception    Exception to display.
+     *    @access public
+     */
+    function paintException($exception) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintException($exception);
         }
-               
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param Exception $exception    Exception to display.
-         *    @access public
-         */
-        function paintException($exception) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintException($exception);
-            }
+    }
+
+    /**
+     *    Prints the message for skipping tests.
+     *    @param string $message    Text of skip condition.
+     *    @access public
+     */
+    function paintSkip($message) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintSkip($message);
         }
+    }
 
-               /**
-                *    Prints the message for skipping tests.
-         *    @param string $message    Text of skip condition.
-         *    @access public
-         */
-               function paintSkip($message) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintSkip($message);
-            }
-               }
-
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param string $message        Text to display.
-         *    @access public
-         */
-        function paintMessage($message) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintMessage($message);
-            }
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param string $message        Text to display.
+     *    @access public
+     */
+    function paintMessage($message) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintMessage($message);
         }
+    }
 
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param string $message        Text to display.
-         *    @access public
-         */
-        function paintFormattedMessage($message) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintFormattedMessage($message);
-            }
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param string $message        Text to display.
+     *    @access public
+     */
+    function paintFormattedMessage($message) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintFormattedMessage($message);
         }
+    }
 
-        /**
-         *    Chains to the wrapped reporter.
-         *    @param string $type        Event type as text.
-         *    @param mixed $payload      Message or object.
-         *    @return boolean            Should return false if this
-         *                               type of signal should fail the
-         *                               test suite.
-         *    @access public
-         */
-        function paintSignal($type, &$payload) {
-            for ($i = 0; $i < count($this->_reporters); $i++) {
-                $this->_reporters[$i]->paintSignal($type, $payload);
-            }
+    /**
+     *    Chains to the wrapped reporter.
+     *    @param string $type        Event type as text.
+     *    @param mixed $payload      Message or object.
+     *    @return boolean            Should return false if this
+     *                               type of signal should fail the
+     *                               test suite.
+     *    @access public
+     */
+    function paintSignal($type, &$payload) {
+        for ($i = 0; $i < count($this->_reporters); $i++) {
+            $this->_reporters[$i]->paintSignal($type, $payload);
         }
     }
+}
 ?>
\ No newline at end of file
index ec8f354f1e7be308dbeceec4a0d8e6eada6dc511..7c5962e1a132f3b1765f467ab57ef8b6bd44ab61 100644 (file)
 <?php
+/**
+ *  Base include file for SimpleTest.
+ *  @package    SimpleTest
+ *  @subpackage WebTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ * include SimpleTest files
+ */
+require_once(dirname(__FILE__) . '/tag.php');
+require_once(dirname(__FILE__) . '/encoding.php');
+/**#@-*/
+
+/**
+ *    Used to extract form elements for testing against.
+ *    Searches by name attribute.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleByName {
+    var $_name;
+
     /**
-     * Base include file for SimpleTest.
-     * @package        SimpleTest
-     * @subpackage     WebTester
-     * @version        $Id$
+     *    Stashes the name for later comparison.
+     *    @param string $name     Name attribute to match.
      */
+    function SimpleByName($name) {
+        $this->_name = $name;
+    }
 
-    /**#@+
-     * include SimpleTest files
-     */
-    require_once(dirname(__FILE__) . '/tag.php');
-    require_once(dirname(__FILE__) . '/encoding.php');
-    /**#@-*/
+    function getName() {
+        return $this->_name;
+    }
 
     /**
-     *    Used to extract form elements for testing against.
-     *    Searches by name attribute.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Compares with name attribute of widget.
+     *    @param SimpleWidget $widget    Control to compare.
+     *    @access public
      */
-    class SimpleByName {
-        var $_name;
+    function isMatch($widget) {
+        return ($widget->getName() == $this->_name);
+    }
+}
 
-        /**
-         *    Stashes the name for later comparison.
-         *    @param string $name     Name attribute to match.
-         */
-        function SimpleByName($name) {
-            $this->_name = $name;
-        }
+/**
+ *    Used to extract form elements for testing against.
+ *    Searches by visible label or alt text.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleByLabel {
+    var $_label;
 
-        /**
-         *    Compares with name attribute of widget.
-         *    @param SimpleWidget $widget    Control to compare.
-         *    @access public
-         */
-        function isMatch($widget) {
-            return ($widget->getName() == $this->_name);
-        }
+    /**
+     *    Stashes the name for later comparison.
+     *    @param string $label     Visible text to match.
+     */
+    function SimpleByLabel($label) {
+        $this->_label = $label;
     }
 
     /**
-     *    Used to extract form elements for testing against.
-     *    Searches by visible label or alt text.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Comparison. Compares visible text of widget or
+     *    related label.
+     *    @param SimpleWidget $widget    Control to compare.
+     *    @access public
      */
-    class SimpleByLabel {
-        var $_label;
-
-        /**
-         *    Stashes the name for later comparison.
-         *    @param string $label     Visible text to match.
-         */
-        function SimpleByLabel($label) {
-            $this->_label = $label;
+    function isMatch($widget) {
+        if (! method_exists($widget, 'isLabel')) {
+            return false;
         }
+        return $widget->isLabel($this->_label);
+    }
+}
 
-        /**
-         *    Comparison. Compares visible text of widget or
-         *    related label.
-         *    @param SimpleWidget $widget    Control to compare.
-         *    @access public
-         */
-        function isMatch($widget) {
-            if (! method_exists($widget, 'isLabel')) {
-                return false;
-            }
-            return $widget->isLabel($this->_label);
-        }
+/**
+ *    Used to extract form elements for testing against.
+ *    Searches dy id attribute.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleById {
+    var $_id;
+
+    /**
+     *    Stashes the name for later comparison.
+     *    @param string $id     ID atribute to match.
+     */
+    function SimpleById($id) {
+        $this->_id = $id;
     }
 
     /**
-     *    Used to extract form elements for testing against.
-     *    Searches dy id attribute.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Comparison. Compares id attribute of widget.
+     *    @param SimpleWidget $widget    Control to compare.
+     *    @access public
      */
-    class SimpleById {
-        var $_id;
+    function isMatch($widget) {
+        return $widget->isId($this->_id);
+    }
+}
 
-        /**
-         *    Stashes the name for later comparison.
-         *    @param string $id     ID atribute to match.
-         */
-        function SimpleById($id) {
-            $this->_id = $id;
-        }
+/**
+ *    Used to extract form elements for testing against.
+ *    Searches by visible label, name or alt text.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleByLabelOrName {
+    var $_label;
 
-        /**
-         *    Comparison. Compares id attribute of widget.
-         *    @param SimpleWidget $widget    Control to compare.
-         *    @access public
-         */
-        function isMatch($widget) {
-            return $widget->isId($this->_id);
-        }
+    /**
+     *    Stashes the name/label for later comparison.
+     *    @param string $label     Visible text to match.
+     */
+    function SimpleByLabelOrName($label) {
+        $this->_label = $label;
     }
 
     /**
-     *    Used to extract form elements for testing against.
-     *    Searches by visible label, name or alt text.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Comparison. Compares visible text of widget or
+     *    related label or name.
+     *    @param SimpleWidget $widget    Control to compare.
+     *    @access public
      */
-    class SimpleByLabelOrName {
-        var $_label;
-
-        /**
-         *    Stashes the name/label for later comparison.
-         *    @param string $label     Visible text to match.
-         */
-        function SimpleByLabelOrName($label) {
-            $this->_label = $label;
-        }
-
-        /**
-         *    Comparison. Compares visible text of widget or
-         *    related label or name.
-         *    @param SimpleWidget $widget    Control to compare.
-         *    @access public
-         */
-        function isMatch($widget) {
-            if (method_exists($widget, 'isLabel')) {
-                if ($widget->isLabel($this->_label)) {
-                    return true;
-                }
+    function isMatch($widget) {
+        if (method_exists($widget, 'isLabel')) {
+            if ($widget->isLabel($this->_label)) {
+                return true;
             }
-            return ($widget->getName() == $this->_label);
         }
+        return ($widget->getName() == $this->_label);
     }
+}
 ?>
\ No newline at end of file
index d47d2d6dce912d7755fb9b701a8aa780cdb8af69..4aff96fc5bfc83c8f809e8e6132be2f3c8c1573c 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ *  include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/test_case.php');
+/**#@-*/
+
+/**
+ *    Wrapper for exec() functionality.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class SimpleShell {
+    var $_output;
+
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
+     *    Executes the shell comand and stashes the output.
+     *    @access public
      */
+    function SimpleShell() {
+        $this->_output = false;
+    }
 
-    /**#@+
-     * include other SimpleTest class files
+    /**
+     *    Actually runs the command. Does not trap the
+     *    error stream output as this need PHP 4.3+.
+     *    @param string $command    The actual command line
+     *                              to run.
+     *    @return integer           Exit code.
+     *    @access public
      */
-    require_once(dirname(__FILE__) . '/test_case.php');
-    /**#@-*/
+    function execute($command) {
+        $this->_output = false;
+        exec($command, $this->_output, $ret);
+        return $ret;
+    }
 
     /**
-     *    Wrapper for exec() functionality.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Accessor for the last output.
+     *    @return string        Output as text.
+     *    @access public
      */
-    class SimpleShell {
-        var $_output;
-
-        /**
-         *    Executes the shell comand and stashes the output.
-         *    @access public
-         */
-        function SimpleShell() {
-            $this->_output = false;
-        }
+    function getOutput() {
+        return implode("\n", $this->_output);
+    }
 
-        /**
-         *    Actually runs the command. Does not trap the
-         *    error stream output as this need PHP 4.3+.
-         *    @param string $command    The actual command line
-         *                              to run.
-         *    @return integer           Exit code.
-         *    @access public
-         */
-        function execute($command) {
-            $this->_output = false;
-            exec($command, $this->_output, $ret);
-            return $ret;
-        }
+    /**
+     *    Accessor for the last output.
+     *    @return array         Output as array of lines.
+     *    @access public
+     */
+    function getOutputAsList() {
+        return $this->_output;
+    }
+}
 
-        /**
-         *    Accessor for the last output.
-         *    @return string        Output as text.
-         *    @access public
-         */
-        function getOutput() {
-            return implode("\n", $this->_output);
-        }
+/**
+ *    Test case for testing of command line scripts and
+ *    utilities. Usually scripts that are external to the
+ *    PHP code, but support it in some way.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class ShellTestCase extends SimpleTestCase {
+    var $_current_shell;
+    var $_last_status;
+    var $_last_command;
 
-        /**
-         *    Accessor for the last output.
-         *    @return array         Output as array of lines.
-         *    @access public
-         */
-               function getOutputAsList() {
-                       return $this->_output;
-               }
+    /**
+     *    Creates an empty test case. Should be subclassed
+     *    with test methods for a functional test case.
+     *    @param string $label     Name of test case. Will use
+     *                             the class name if none specified.
+     *    @access public
+     */
+    function ShellTestCase($label = false) {
+        $this->SimpleTestCase($label);
+        $this->_current_shell = &$this->_createShell();
+        $this->_last_status = false;
+        $this->_last_command = '';
     }
 
     /**
-     *    Test case for testing of command line scripts and
-     *    utilities. Usually scripts that are external to the
-     *    PHP code, but support it in some way.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Executes a command and buffers the results.
+     *    @param string $command     Command to run.
+     *    @return boolean            True if zero exit code.
+     *    @access public
      */
-    class ShellTestCase extends SimpleTestCase {
-        var $_current_shell;
-        var $_last_status;
-        var $_last_command;
-
-        /**
-         *    Creates an empty test case. Should be subclassed
-         *    with test methods for a functional test case.
-         *    @param string $label     Name of test case. Will use
-         *                             the class name if none specified.
-         *    @access public
-         */
-        function ShellTestCase($label = false) {
-            $this->SimpleTestCase($label);
-            $this->_current_shell = &$this->_createShell();
-            $this->_last_status = false;
-            $this->_last_command = '';
-        }
-
-        /**
-         *    Executes a command and buffers the results.
-         *    @param string $command     Command to run.
-         *    @return boolean            True if zero exit code.
-         *    @access public
-         */
-        function execute($command) {
-            $shell = &$this->_getShell();
-            $this->_last_status = $shell->execute($command);
-            $this->_last_command = $command;
-            return ($this->_last_status === 0);
-        }
+    function execute($command) {
+        $shell = &$this->_getShell();
+        $this->_last_status = $shell->execute($command);
+        $this->_last_command = $command;
+        return ($this->_last_status === 0);
+    }
 
-        /**
-         *    Dumps the output of the last command.
-         *    @access public
-         */
-        function dumpOutput() {
-            $this->dump($this->getOutput());
-        }
+    /**
+     *    Dumps the output of the last command.
+     *    @access public
+     */
+    function dumpOutput() {
+        $this->dump($this->getOutput());
+    }
 
-        /**
-         *    Accessor for the last output.
-         *    @return string        Output as text.
-         *    @access public
-         */
-               function getOutput() {
-            $shell = &$this->_getShell();
-            return $shell->getOutput();
-               }
+    /**
+     *    Accessor for the last output.
+     *    @return string        Output as text.
+     *    @access public
+     */
+    function getOutput() {
+        $shell = &$this->_getShell();
+        return $shell->getOutput();
+    }
 
-        /**
-         *    Accessor for the last output.
-         *    @return array         Output as array of lines.
-         *    @access public
-         */
-               function getOutputAsList() {
-            $shell = &$this->_getShell();
-            return $shell->getOutputAsList();
-               }
+    /**
+     *    Accessor for the last output.
+     *    @return array         Output as array of lines.
+     *    @access public
+     */
+    function getOutputAsList() {
+        $shell = &$this->_getShell();
+        return $shell->getOutputAsList();
+    }
 
-        /**
-         *    Called from within the test methods to register
-         *    passes and failures.
-         *    @param boolean $result    Pass on true.
-         *    @param string $message    Message to display describing
-         *                              the test state.
-         *    @return boolean           True on pass
-         *    @access public
-         */
-        function assertTrue($result, $message = false) {
-            return $this->assert(new TrueExpectation(), $result, $message);
-        }
+    /**
+     *    Called from within the test methods to register
+     *    passes and failures.
+     *    @param boolean $result    Pass on true.
+     *    @param string $message    Message to display describing
+     *                              the test state.
+     *    @return boolean           True on pass
+     *    @access public
+     */
+    function assertTrue($result, $message = false) {
+        return $this->assert(new TrueExpectation(), $result, $message);
+    }
 
-        /**
-         *    Will be true on false and vice versa. False
-         *    is the PHP definition of false, so that null,
-         *    empty strings, zero and an empty array all count
-         *    as false.
-         *    @param boolean $result    Pass on false.
-         *    @param string $message    Message to display.
-         *    @return boolean           True on pass
-         *    @access public
-         */
-        function assertFalse($result, $message = '%s') {
-            return $this->assert(new FalseExpectation(), $result, $message);
-        }
-        
-        /**
-         *    Will trigger a pass if the two parameters have
-         *    the same value only. Otherwise a fail. This
-         *    is for testing hand extracted text, etc.
-         *    @param mixed $first          Value to compare.
-         *    @param mixed $second         Value to compare.
-         *    @param string $message       Message to display.
-         *    @return boolean              True on pass
-         *    @access public
-         */
-        function assertEqual($first, $second, $message = "%s") {
-            return $this->assert(
-                    new EqualExpectation($first),
-                    $second,
-                    $message);
-        }
-        
-        /**
-         *    Will trigger a pass if the two parameters have
-         *    a different value. Otherwise a fail. This
-         *    is for testing hand extracted text, etc.
-         *    @param mixed $first           Value to compare.
-         *    @param mixed $second          Value to compare.
-         *    @param string $message        Message to display.
-         *    @return boolean               True on pass
-         *    @access public
-         */
-        function assertNotEqual($first, $second, $message = "%s") {
-            return $this->assert(
-                    new NotEqualExpectation($first),
-                    $second,
-                    $message);
-        }
+    /**
+     *    Will be true on false and vice versa. False
+     *    is the PHP definition of false, so that null,
+     *    empty strings, zero and an empty array all count
+     *    as false.
+     *    @param boolean $result    Pass on false.
+     *    @param string $message    Message to display.
+     *    @return boolean           True on pass
+     *    @access public
+     */
+    function assertFalse($result, $message = '%s') {
+        return $this->assert(new FalseExpectation(), $result, $message);
+    }
+    
+    /**
+     *    Will trigger a pass if the two parameters have
+     *    the same value only. Otherwise a fail. This
+     *    is for testing hand extracted text, etc.
+     *    @param mixed $first          Value to compare.
+     *    @param mixed $second         Value to compare.
+     *    @param string $message       Message to display.
+     *    @return boolean              True on pass
+     *    @access public
+     */
+    function assertEqual($first, $second, $message = "%s") {
+        return $this->assert(
+                new EqualExpectation($first),
+                $second,
+                $message);
+    }
+    
+    /**
+     *    Will trigger a pass if the two parameters have
+     *    a different value. Otherwise a fail. This
+     *    is for testing hand extracted text, etc.
+     *    @param mixed $first           Value to compare.
+     *    @param mixed $second          Value to compare.
+     *    @param string $message        Message to display.
+     *    @return boolean               True on pass
+     *    @access public
+     */
+    function assertNotEqual($first, $second, $message = "%s") {
+        return $this->assert(
+                new NotEqualExpectation($first),
+                $second,
+                $message);
+    }
 
-        /**
-         *    Tests the last status code from the shell.
-         *    @param integer $status   Expected status of last
-         *                             command.
-         *    @param string $message   Message to display.
-         *    @return boolean          True if pass.
-         *    @access public
-         */
-        function assertExitCode($status, $message = "%s") {
-            $message = sprintf($message, "Expected status code of [$status] from [" .
-                    $this->_last_command . "], but got [" .
-                    $this->_last_status . "]");
-            return $this->assertTrue($status === $this->_last_status, $message);
-        }
+    /**
+     *    Tests the last status code from the shell.
+     *    @param integer $status   Expected status of last
+     *                             command.
+     *    @param string $message   Message to display.
+     *    @return boolean          True if pass.
+     *    @access public
+     */
+    function assertExitCode($status, $message = "%s") {
+        $message = sprintf($message, "Expected status code of [$status] from [" .
+                $this->_last_command . "], but got [" .
+                $this->_last_status . "]");
+        return $this->assertTrue($status === $this->_last_status, $message);
+    }
 
-        /**
-         *    Attempt to exactly match the combined STDERR and
-         *    STDOUT output.
-         *    @param string $expected  Expected output.
-         *    @param string $message   Message to display.
-         *    @return boolean          True if pass.
-         *    @access public
-         */
-        function assertOutput($expected, $message = "%s") {
-            $shell = &$this->_getShell();
-            return $this->assert(
-                    new EqualExpectation($expected),
-                    $shell->getOutput(),
-                    $message);
-        }
+    /**
+     *    Attempt to exactly match the combined STDERR and
+     *    STDOUT output.
+     *    @param string $expected  Expected output.
+     *    @param string $message   Message to display.
+     *    @return boolean          True if pass.
+     *    @access public
+     */
+    function assertOutput($expected, $message = "%s") {
+        $shell = &$this->_getShell();
+        return $this->assert(
+                new EqualExpectation($expected),
+                $shell->getOutput(),
+                $message);
+    }
 
-        /**
-         *    Scans the output for a Perl regex. If found
-         *    anywhere it passes, else it fails.
-         *    @param string $pattern    Regex to search for.
-         *    @param string $message    Message to display.
-         *    @return boolean           True if pass.
-         *    @access public
-         */
-        function assertOutputPattern($pattern, $message = "%s") {
-            $shell = &$this->_getShell();
-            return $this->assert(
-                    new PatternExpectation($pattern),
-                    $shell->getOutput(),
-                    $message);
-        }
+    /**
+     *    Scans the output for a Perl regex. If found
+     *    anywhere it passes, else it fails.
+     *    @param string $pattern    Regex to search for.
+     *    @param string $message    Message to display.
+     *    @return boolean           True if pass.
+     *    @access public
+     */
+    function assertOutputPattern($pattern, $message = "%s") {
+        $shell = &$this->_getShell();
+        return $this->assert(
+                new PatternExpectation($pattern),
+                $shell->getOutput(),
+                $message);
+    }
 
-        /**
-         *    If a Perl regex is found anywhere in the current
-         *    output then a failure is generated, else a pass.
-         *    @param string $pattern    Regex to search for.
-         *    @param $message           Message to display.
-         *    @return boolean           True if pass.
-         *    @access public
-         */
-        function assertNoOutputPattern($pattern, $message = "%s") {
-            $shell = &$this->_getShell();
-            return $this->assert(
-                    new NoPatternExpectation($pattern),
-                    $shell->getOutput(),
-                    $message);
-        }
+    /**
+     *    If a Perl regex is found anywhere in the current
+     *    output then a failure is generated, else a pass.
+     *    @param string $pattern    Regex to search for.
+     *    @param $message           Message to display.
+     *    @return boolean           True if pass.
+     *    @access public
+     */
+    function assertNoOutputPattern($pattern, $message = "%s") {
+        $shell = &$this->_getShell();
+        return $this->assert(
+                new NoPatternExpectation($pattern),
+                $shell->getOutput(),
+                $message);
+    }
 
-        /**
-         *    File existence check.
-         *    @param string $path      Full filename and path.
-         *    @param string $message   Message to display.
-         *    @return boolean          True if pass.
-         *    @access public
-         */
-        function assertFileExists($path, $message = "%s") {
-            $message = sprintf($message, "File [$path] should exist");
-            return $this->assertTrue(file_exists($path), $message);
-        }
+    /**
+     *    File existence check.
+     *    @param string $path      Full filename and path.
+     *    @param string $message   Message to display.
+     *    @return boolean          True if pass.
+     *    @access public
+     */
+    function assertFileExists($path, $message = "%s") {
+        $message = sprintf($message, "File [$path] should exist");
+        return $this->assertTrue(file_exists($path), $message);
+    }
 
-        /**
-         *    File non-existence check.
-         *    @param string $path      Full filename and path.
-         *    @param string $message   Message to display.
-         *    @return boolean          True if pass.
-         *    @access public
-         */
-        function assertFileNotExists($path, $message = "%s") {
-            $message = sprintf($message, "File [$path] should not exist");
-            return $this->assertFalse(file_exists($path), $message);
-        }
+    /**
+     *    File non-existence check.
+     *    @param string $path      Full filename and path.
+     *    @param string $message   Message to display.
+     *    @return boolean          True if pass.
+     *    @access public
+     */
+    function assertFileNotExists($path, $message = "%s") {
+        $message = sprintf($message, "File [$path] should not exist");
+        return $this->assertFalse(file_exists($path), $message);
+    }
 
-        /**
-         *    Scans a file for a Perl regex. If found
-         *    anywhere it passes, else it fails.
-         *    @param string $pattern    Regex to search for.
-         *    @param string $path       Full filename and path.
-         *    @param string $message    Message to display.
-         *    @return boolean           True if pass.
-         *    @access public
-         */
-        function assertFilePattern($pattern, $path, $message = "%s") {
-            $shell = &$this->_getShell();
-            return $this->assert(
-                    new PatternExpectation($pattern),
-                    implode('', file($path)),
-                    $message);
-        }
+    /**
+     *    Scans a file for a Perl regex. If found
+     *    anywhere it passes, else it fails.
+     *    @param string $pattern    Regex to search for.
+     *    @param string $path       Full filename and path.
+     *    @param string $message    Message to display.
+     *    @return boolean           True if pass.
+     *    @access public
+     */
+    function assertFilePattern($pattern, $path, $message = "%s") {
+        $shell = &$this->_getShell();
+        return $this->assert(
+                new PatternExpectation($pattern),
+                implode('', file($path)),
+                $message);
+    }
 
-        /**
-         *    If a Perl regex is found anywhere in the named
-         *    file then a failure is generated, else a pass.
-         *    @param string $pattern    Regex to search for.
-         *    @param string $path       Full filename and path.
-         *    @param string $message    Message to display.
-         *    @return boolean           True if pass.
-         *    @access public
-         */
-        function assertNoFilePattern($pattern, $path, $message = "%s") {
-            $shell = &$this->_getShell();
-            return $this->assert(
-                    new NoPatternExpectation($pattern),
-                    implode('', file($path)),
-                    $message);
-        }
+    /**
+     *    If a Perl regex is found anywhere in the named
+     *    file then a failure is generated, else a pass.
+     *    @param string $pattern    Regex to search for.
+     *    @param string $path       Full filename and path.
+     *    @param string $message    Message to display.
+     *    @return boolean           True if pass.
+     *    @access public
+     */
+    function assertNoFilePattern($pattern, $path, $message = "%s") {
+        $shell = &$this->_getShell();
+        return $this->assert(
+                new NoPatternExpectation($pattern),
+                implode('', file($path)),
+                $message);
+    }
 
-        /**
-         *    Accessor for current shell. Used for testing the
-         *    the tester itself.
-         *    @return Shell        Current shell.
-         *    @access protected
-         */
-        function &_getShell() {
-            return $this->_current_shell;
-        }
+    /**
+     *    Accessor for current shell. Used for testing the
+     *    the tester itself.
+     *    @return Shell        Current shell.
+     *    @access protected
+     */
+    function &_getShell() {
+        return $this->_current_shell;
+    }
 
-        /**
-         *    Factory for the shell to run the command on.
-         *    @return Shell        New shell object.
-         *    @access protected
-         */
-        function &_createShell() {
-            $shell = &new SimpleShell();
-            return $shell;
-        }
+    /**
+     *    Factory for the shell to run the command on.
+     *    @return Shell        New shell object.
+     *    @access protected
+     */
+    function &_createShell() {
+        $shell = &new SimpleShell();
+        return $shell;
     }
+}
 ?>
index 6464bdc36be3cc5069debe2d49cf2bc19cd093dd..e63e57464626b403b764a2026689c9739a9e172c 100644 (file)
 <?php
+/**
+ *  Global state for SimpleTest and kicker script in future versions.
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ * include SimpleTest files
+ */
+if (version_compare(phpversion(), '5') >= 0) {
+    require_once(dirname(__FILE__) . '/reflection_php5.php');
+} else {
+    require_once(dirname(__FILE__) . '/reflection_php4.php');
+}
+require_once(dirname(__FILE__) . '/default_reporter.php');
+require_once(dirname(__FILE__) . '/compatibility.php');
+/**#@-*/
+
+/**
+ *    Registry and test context. Includes a few
+ *    global options that I'm slowly getting rid of.
+ *    @package  SimpleTest
+ *    @subpackage   UnitTester
+ */
+class SimpleTest {
+
     /**
-     * Global state for SimpleTest and kicker script in future versions.
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
+     *    Reads the SimpleTest version from the release file.
+     *    @return string        Version string.
+     *    @static
+     *    @access public
      */
+    function getVersion() {
+        $content = file(dirname(__FILE__) . '/VERSION');
+        return trim($content[0]);
+    }
 
-    /**#@+
-     * include SimpleTest files
+    /**
+     *    Sets the name of a test case to ignore, usually
+     *    because the class is an abstract case that should
+     *    not be run. Once PHP4 is dropped this will disappear
+     *    as a public method and "abstract" will rule.
+     *    @param string $class        Add a class to ignore.
+     *    @static
+     *    @access public
      */
-    if (version_compare(phpversion(), '5') >= 0) {
-        require_once(dirname(__FILE__) . '/reflection_php5.php');
-    } else {
-        require_once(dirname(__FILE__) . '/reflection_php4.php');
+    function ignore($class) {
+        $registry = &SimpleTest::_getRegistry();
+        $registry['IgnoreList'][strtolower($class)] = true;
     }
-    /**#@-*/
 
     /**
-     *    Registry and test context. Includes a few
-     *    global options that I'm slowly getting rid of.
-     *   @package      SimpleTest
+     *    Scans the now complete ignore list, and adds
+     *    all parent classes to the list. If a class
+     *    is not a runnable test case, then it's parents
+     *    wouldn't be either. This is syntactic sugar
+     *    to cut down on ommissions of ignore()'s or
+     *    missing abstract declarations. This cannot
+     *    be done whilst loading classes wiithout forcing
+     *    a particular order on the class declarations and
+     *    the ignore() calls. It's just nice to have the ignore()
+     *    calls at the top of the file before the actual declarations.
+     *    @param array $classes     Class names of interest.
+     *    @static
+     *    @access public
      */
-    class SimpleTest {
-
-        /**
-         *    Reads the SimpleTest version from the release file.
-         *    @return string        Version string.
-         *    @static
-         *    @access public
-         */
-        function getVersion() {
-            $content = file(dirname(__FILE__) . '/VERSION');
-            return trim($content[0]);
+    function ignoreParentsIfIgnored($classes) {
+        $registry = &SimpleTest::_getRegistry();
+        foreach ($classes as $class) {
+            if (SimpleTest::isIgnored($class)) {
+                $reflection = new SimpleReflection($class);
+                if ($parent = $reflection->getParent()) {
+                    SimpleTest::ignore($parent);
+                }
+            }
         }
+    }
 
-        /**
-         *    Sets the name of a test case to ignore, usually
-         *    because the class is an abstract case that should
-         *    not be run. Once PHP4 is dropped this will disappear
-         *    as a public method and "abstract" will rule.
-         *    @param string $class        Add a class to ignore.
-         *    @static
-         *    @access public
-         */
-        function ignore($class) {
-            $registry = &SimpleTest::_getRegistry();
-            $registry['IgnoreList'][strtolower($class)] = true;
-        }
+    /**
+     *   Puts the object to the global pool of 'preferred' objects
+     *   which can be retrieved with SimpleTest :: preferred() method.
+     *   Instances of the same class are overwritten.
+     *   @param object $object      Preferred object
+     *   @static
+     *   @access public
+     *   @see preferred()
+     */
+    function prefer(&$object) {
+        $registry = &SimpleTest::_getRegistry();
+        $registry['Preferred'][] = &$object;
+    }
 
-        /**
-         *    Scans the now complete ignore list, and adds
-         *    all parent classes to the list. If a class
-         *    is not a runnable test case, then it's parents
-         *    wouldn't be either. This is syntactic sugar
-         *    to cut down on ommissions of ignore()'s or
-         *    missing abstract declarations. This cannot
-         *    be done whilst loading classes wiithout forcing
-         *    a particular order on the class declarations and
-         *    the ignore() calls. It's just nice to have the ignore()
-         *    calls at the top of the file before the actual declarations.
-         *    @param array $classes     Class names of interest.
-         *    @static
-         *    @access public
-         */
-        function ignoreParentsIfIgnored($classes) {
-            $registry = &SimpleTest::_getRegistry();
+    /**
+     *   Retrieves 'preferred' objects from global pool. Class filter
+     *   can be applied in order to retrieve the object of the specific
+     *   class
+     *   @param array|string $classes       Allowed classes or interfaces.
+     *   @static
+     *   @access public
+     *   @return array|object|null
+     *   @see prefer()
+     */
+    function &preferred($classes) {
+        if (! is_array($classes)) {
+            $classes = array($classes);
+        }
+        $registry = &SimpleTest::_getRegistry();
+        for ($i = count($registry['Preferred']) - 1; $i >= 0; $i--) {
             foreach ($classes as $class) {
-                if (SimpleTest::isIgnored($class)) {
-                    $reflection = new SimpleReflection($class);
-                    if ($parent = $reflection->getParent()) {
-                        SimpleTest::ignore($parent);
-                    }
+                if (SimpleTestCompatibility::isA($registry['Preferred'][$i], $class)) {
+                    return $registry['Preferred'][$i];
                 }
             }
         }
+        return null;
+    }
 
-        /**
-         *    Test to see if a test case is in the ignore
-         *    list. Quite obviously the ignore list should
-         *    be a separate object and will be one day.
-         *    This method is internal to SimpleTest. Don't
-         *    use it.
-         *    @param string $class        Class name to test.
-         *    @return boolean             True if should not be run.
-         *    @access public
-         *    @static
-         */
-        function isIgnored($class) {
-            $registry = &SimpleTest::_getRegistry();
-            return isset($registry['IgnoreList'][strtolower($class)]);
-        }
-
-        /**
-         *    @deprecated
-         */
-        function setMockBaseClass($mock_base) {
-            $registry = &SimpleTest::_getRegistry();
-            $registry['MockBaseClass'] = $mock_base;
-        }
+    /**
+     *    Test to see if a test case is in the ignore
+     *    list. Quite obviously the ignore list should
+     *    be a separate object and will be one day.
+     *    This method is internal to SimpleTest. Don't
+     *    use it.
+     *    @param string $class        Class name to test.
+     *    @return boolean             True if should not be run.
+     *    @access public
+     *    @static
+     */
+    function isIgnored($class) {
+        $registry = &SimpleTest::_getRegistry();
+        return isset($registry['IgnoreList'][strtolower($class)]);
+    }
 
-        /**
-         *    @deprecated
-         */
-        function getMockBaseClass() {
-            $registry = &SimpleTest::_getRegistry();
-            return $registry['MockBaseClass'];
-        }
+    /**
+     *    @deprecated
+     */
+    function setMockBaseClass($mock_base) {
+        $registry = &SimpleTest::_getRegistry();
+        $registry['MockBaseClass'] = $mock_base;
+    }
 
-        /**
-         *    Sets proxy to use on all requests for when
-         *    testing from behind a firewall. Set host
-         *    to false to disable. This will take effect
-         *    if there are no other proxy settings.
-         *    @param string $proxy     Proxy host as URL.
-         *    @param string $username  Proxy username for authentication.
-         *    @param string $password  Proxy password for authentication.
-         *    @access public
-         */
-        function useProxy($proxy, $username = false, $password = false) {
-            $registry = &SimpleTest::_getRegistry();
-            $registry['DefaultProxy'] = $proxy;
-            $registry['DefaultProxyUsername'] = $username;
-            $registry['DefaultProxyPassword'] = $password;
-        }
+    /**
+     *    @deprecated
+     */
+    function getMockBaseClass() {
+        $registry = &SimpleTest::_getRegistry();
+        return $registry['MockBaseClass'];
+    }
 
-        /**
-         *    Accessor for default proxy host.
-         *    @return string       Proxy URL.
-         *    @access public
-         */
-        function getDefaultProxy() {
-            $registry = &SimpleTest::_getRegistry();
-            return $registry['DefaultProxy'];
-        }
+    /**
+     *    Sets proxy to use on all requests for when
+     *    testing from behind a firewall. Set host
+     *    to false to disable. This will take effect
+     *    if there are no other proxy settings.
+     *    @param string $proxy     Proxy host as URL.
+     *    @param string $username  Proxy username for authentication.
+     *    @param string $password  Proxy password for authentication.
+     *    @access public
+     */
+    function useProxy($proxy, $username = false, $password = false) {
+        $registry = &SimpleTest::_getRegistry();
+        $registry['DefaultProxy'] = $proxy;
+        $registry['DefaultProxyUsername'] = $username;
+        $registry['DefaultProxyPassword'] = $password;
+    }
 
-        /**
-         *    Accessor for default proxy username.
-         *    @return string    Proxy username for authentication.
-         *    @access public
-         */
-        function getDefaultProxyUsername() {
-            $registry = &SimpleTest::_getRegistry();
-            return $registry['DefaultProxyUsername'];
-        }
+    /**
+     *    Accessor for default proxy host.
+     *    @return string       Proxy URL.
+     *    @access public
+     */
+    function getDefaultProxy() {
+        $registry = &SimpleTest::_getRegistry();
+        return $registry['DefaultProxy'];
+    }
 
-        /**
-         *    Accessor for default proxy password.
-         *    @return string    Proxy password for authentication.
-         *    @access public
-         */
-        function getDefaultProxyPassword() {
-            $registry = &SimpleTest::_getRegistry();
-            return $registry['DefaultProxyPassword'];
-        }
+    /**
+     *    Accessor for default proxy username.
+     *    @return string    Proxy username for authentication.
+     *    @access public
+     */
+    function getDefaultProxyUsername() {
+        $registry = &SimpleTest::_getRegistry();
+        return $registry['DefaultProxyUsername'];
+    }
 
-        /**
-         *    Accessor for global registry of options.
-         *    @return hash           All stored values.
-         *    @access private
-         *    @static
-         */
-        function &_getRegistry() {
-            static $registry = false;
-            if (! $registry) {
-                $registry = SimpleTest::_getDefaults();
-            }
-            return $registry;
-        }
+    /**
+     *    Accessor for default proxy password.
+     *    @return string    Proxy password for authentication.
+     *    @access public
+     */
+    function getDefaultProxyPassword() {
+        $registry = &SimpleTest::_getRegistry();
+        return $registry['DefaultProxyPassword'];
+    }
 
-        /**
-         *    Accessor for the context of the current
-         *    test run.
-         *    @return SimpleTestContext    Current test run.
-         *    @access public
-         *    @static
-         */
-        function &getContext() {
-            static $context = false;
-            if (! $context) {
-                $context = new SimpleTestContext();
-            }
-            return $context;
+    /**
+     *    Accessor for global registry of options.
+     *    @return hash           All stored values.
+     *    @access private
+     *    @static
+     */
+    function &_getRegistry() {
+        static $registry = false;
+        if (! $registry) {
+            $registry = SimpleTest::_getDefaults();
         }
+        return $registry;
+    }
 
-        /**
-         *    Constant default values.
-         *    @return hash       All registry defaults.
-         *    @access private
-         *    @static
-         */
-        function _getDefaults() {
-            return array(
-                    'StubBaseClass' => 'SimpleStub',
-                    'MockBaseClass' => 'SimpleMock',
-                    'IgnoreList' => array(),
-                    'DefaultProxy' => false,
-                    'DefaultProxyUsername' => false,
-                    'DefaultProxyPassword' => false);
+    /**
+     *    Accessor for the context of the current
+     *    test run.
+     *    @return SimpleTestContext    Current test run.
+     *    @access public
+     *    @static
+     */
+    function &getContext() {
+        static $context = false;
+        if (! $context) {
+            $context = new SimpleTestContext();
         }
+        return $context;
     }
 
     /**
-     *    Container for all components for a specific
-     *    test run. Makes things like error queues
-     *    available to PHP event handlers, and also
-     *    gets around some nasty reference issues in
-     *    the mocks.
-     *   @package      SimpleTest
+     *    Constant default values.
+     *    @return hash       All registry defaults.
+     *    @access private
+     *    @static
      */
-    class SimpleTestContext {
-        var $_test;
-        var $_reporter;
-        var $_resources;
+    function _getDefaults() {
+        return array(
+                'StubBaseClass' => 'SimpleStub',
+                'MockBaseClass' => 'SimpleMock',
+                'IgnoreList' => array(),
+                'DefaultProxy' => false,
+                'DefaultProxyUsername' => false,
+                'DefaultProxyPassword' => false,
+                'Preferred' => array(new HtmlReporter(), new TextReporter(), new XmlReporter()));
+    }
+}
+
+/**
+ *    Container for all components for a specific
+ *    test run. Makes things like error queues
+ *    available to PHP event handlers, and also
+ *    gets around some nasty reference issues in
+ *    the mocks.
+ *    @package  SimpleTest
+ */
+class SimpleTestContext {
+    var $_test;
+    var $_reporter;
+    var $_resources;
 
-        /**
-         *    Clears down the current context.
-         *    @access public
-         */
-        function clear() {
-            $this->_resources = array();
-        }
+    /**
+     *    Clears down the current context.
+     *    @access public
+     */
+    function clear() {
+        $this->_resources = array();
+    }
 
-        /**
-         *    Sets the current test case instance. This
-         *    global instance can be used by the mock objects
-         *    to send message to the test cases.
-         *    @param SimpleTestCase $test        Test case to register.
-         *    @access public
-         */
-        function setTest(&$test) {
-            $this->clear();
-            $this->_test = &$test;
-        }
+    /**
+     *    Sets the current test case instance. This
+     *    global instance can be used by the mock objects
+     *    to send message to the test cases.
+     *    @param SimpleTestCase $test        Test case to register.
+     *    @access public
+     */
+    function setTest(&$test) {
+        $this->clear();
+        $this->_test = &$test;
+    }
 
-        /**
-         *    Accessor for currently running test case.
-         *    @return SimpleTestCase    Current test.
-         *    @acess pubic
-         */
-        function &getTest() {
-            return $this->_test;
-        }
+    /**
+     *    Accessor for currently running test case.
+     *    @return SimpleTestCase    Current test.
+     *    @access public
+     */
+    function &getTest() {
+        return $this->_test;
+    }
 
-        /**
-         *    Sets the current reporter. This
-         *    global instance can be used by the mock objects
-         *    to send messages.
-         *    @param SimpleReporter $reporter     Reporter to register.
-         *    @access public
-         */
-        function setReporter(&$reporter) {
-            $this->clear();
-            $this->_reporter = &$reporter;
-        }
+    /**
+     *    Sets the current reporter. This
+     *    global instance can be used by the mock objects
+     *    to send messages.
+     *    @param SimpleReporter $reporter     Reporter to register.
+     *    @access public
+     */
+    function setReporter(&$reporter) {
+        $this->clear();
+        $this->_reporter = &$reporter;
+    }
 
-        /**
-         *    Accessor for current reporter.
-         *    @return SimpleReporter    Current reporter.
-         *    @acess pubic
-         */
-        function &getReporter() {
-            return $this->_reporter;
-        }
+    /**
+     *    Accessor for current reporter.
+     *    @return SimpleReporter    Current reporter.
+     *    @access public
+     */
+    function &getReporter() {
+        return $this->_reporter;
+    }
 
-        /**
-         *    Accessor for the Singleton resource.
-         *    @return object       Global resource.
-         *    @access public
-         *    @static
-         */
-        function &get($resource) {
-            if (! isset($this->_resources[$resource])) {
-                $this->_resources[$resource] = &new $resource();
-            }
-            return $this->_resources[$resource];
+    /**
+     *    Accessor for the Singleton resource.
+     *    @return object       Global resource.
+     *    @access public
+     *    @static
+     */
+    function &get($resource) {
+        if (! isset($this->_resources[$resource])) {
+            $this->_resources[$resource] = &new $resource();
         }
+        return $this->_resources[$resource];
     }
+}
+
+/**
+ *    Interrogates the stack trace to recover the
+ *    failure point.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class SimpleStackTrace {
+    var $_prefixes;
 
     /**
-     *    Interrogates the stack trace to recover the
-     *    failure point.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Stashes the list of target prefixes.
+     *    @param array $prefixes      List of method prefixes
+     *                                to search for.
      */
-    class SimpleStackTrace {
-        var $_prefixes;
-
-        /**
-         *    Stashes the list of target prefixes.
-         *    @param array $prefixes      List of method prefixes
-         *                                to search for.
-         */
-        function SimpleStackTrace($prefixes) {
-            $this->_prefixes = $prefixes;
-        }
+    function SimpleStackTrace($prefixes) {
+        $this->_prefixes = $prefixes;
+    }
 
-        /**
-         *    Extracts the last method name that was not within
-         *    Simpletest itself. Captures a stack trace if none given.
-         *    @param array $stack      List of stack frames.
-         *    @return string           Snippet of test report with line
-         *                             number and file.
-         *    @access public
-         */
-        function traceMethod($stack = false) {
-            $stack = $stack ? $stack : $this->_captureTrace();
-            foreach ($stack as $frame) {
-                if ($this->_frameLiesWithinSimpleTestFolder($frame)) {
-                    continue;
-                }
-                if ($this->_frameMatchesPrefix($frame)) {
-                    return ' at [' . $frame['file'] . ' line ' . $frame['line'] . ']';
-                }
+    /**
+     *    Extracts the last method name that was not within
+     *    Simpletest itself. Captures a stack trace if none given.
+     *    @param array $stack      List of stack frames.
+     *    @return string           Snippet of test report with line
+     *                             number and file.
+     *    @access public
+     */
+    function traceMethod($stack = false) {
+        $stack = $stack ? $stack : $this->_captureTrace();
+        foreach ($stack as $frame) {
+            if ($this->_frameLiesWithinSimpleTestFolder($frame)) {
+                continue;
             }
-            return '';
-        }
-
-        /**
-         *    Test to see if error is generated by SimpleTest itself.
-         *    @param array $frame     PHP stack frame.
-         *    @return boolean         True if a SimpleTest file.
-         *    @access private
-         */
-        function _frameLiesWithinSimpleTestFolder($frame) {
-            if (isset($frame['file'])) {
-                $path = substr(SIMPLE_TEST, 0, -1);
-                if (strpos($frame['file'], $path) === 0) {
-                    if (dirname($frame['file']) == $path) {
-                        return true;
-                    }
-                }
+            if ($this->_frameMatchesPrefix($frame)) {
+                return ' at [' . $frame['file'] . ' line ' . $frame['line'] . ']';
             }
-            return false;
         }
+        return '';
+    }
 
-        /**
-         *    Tries to determine if the method call is an assert, etc.
-         *    @param array $frame     PHP stack frame.
-         *    @return boolean         True if matches a target.
-         *    @access private
-         */
-        function _frameMatchesPrefix($frame) {
-            foreach ($this->_prefixes as $prefix) {
-                if (strncmp($frame['function'], $prefix, strlen($prefix)) == 0) {
+    /**
+     *    Test to see if error is generated by SimpleTest itself.
+     *    @param array $frame     PHP stack frame.
+     *    @return boolean         True if a SimpleTest file.
+     *    @access private
+     */
+    function _frameLiesWithinSimpleTestFolder($frame) {
+        if (isset($frame['file'])) {
+            $path = substr(SIMPLE_TEST, 0, -1);
+            if (strpos($frame['file'], $path) === 0) {
+                if (dirname($frame['file']) == $path) {
                     return true;
                 }
             }
-            return false;
         }
+        return false;
+    }
 
-        /**
-         *    Grabs a current stack trace.
-         *    @return array        Fulle trace.
-         *    @access private
-         */
-        function _captureTrace() {
-            if (function_exists('debug_backtrace')) {
-                return array_reverse(debug_backtrace());
+    /**
+     *    Tries to determine if the method call is an assert, etc.
+     *    @param array $frame     PHP stack frame.
+     *    @return boolean         True if matches a target.
+     *    @access private
+     */
+    function _frameMatchesPrefix($frame) {
+        foreach ($this->_prefixes as $prefix) {
+            if (strncmp($frame['function'], $prefix, strlen($prefix)) == 0) {
+                return true;
             }
-            return array();
         }
+        return false;
     }
 
     /**
-     *    @deprecated
+     *    Grabs a current stack trace.
+     *    @return array        Fulle trace.
+     *    @access private
      */
-    class SimpleTestOptions extends SimpleTest {
-
-        /**
-         *    @deprecated
-         */
-        function getVersion() {
-            return Simpletest::getVersion();
+    function _captureTrace() {
+        if (function_exists('debug_backtrace')) {
+            return array_reverse(debug_backtrace());
         }
+        return array();
+    }
+}
 
-        /**
-         *    @deprecated
-         */
-        function ignore($class) {
-            return Simpletest::ignore($class);
-        }
+/**
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ *    @deprecated
+ */
+class SimpleTestOptions extends SimpleTest {
 
-        /**
-         *    @deprecated
-         */
-        function isIgnored($class) {
-            return Simpletest::isIgnored($class);
-        }
+    /**
+     *    @deprecated
+     */
+    function getVersion() {
+        return Simpletest::getVersion();
+    }
 
-        /**
-         *    @deprecated
-         */
-        function setMockBaseClass($mock_base) {
-            return Simpletest::setMockBaseClass($mock_base);
-        }
+    /**
+     *    @deprecated
+     */
+    function ignore($class) {
+        return Simpletest::ignore($class);
+    }
 
-        /**
-         *    @deprecated
-         */
-        function getMockBaseClass() {
-            return Simpletest::getMockBaseClass();
-        }
+    /**
+     *    @deprecated
+     */
+    function isIgnored($class) {
+        return Simpletest::isIgnored($class);
+    }
 
-        /**
-         *    @deprecated
-         */
-        function useProxy($proxy, $username = false, $password = false) {
-            return Simpletest::useProxy($proxy, $username, $password);
-        }
+    /**
+     *    @deprecated
+     */
+    function setMockBaseClass($mock_base) {
+        return Simpletest::setMockBaseClass($mock_base);
+    }
 
-        /**
-         *    @deprecated
-         */
-        function getDefaultProxy() {
-            return Simpletest::getDefaultProxy();
-        }
+    /**
+     *    @deprecated
+     */
+    function getMockBaseClass() {
+        return Simpletest::getMockBaseClass();
+    }
 
-        /**
-         *    @deprecated
-         */
-        function getDefaultProxyUsername() {
-            return Simpletest::getDefaultProxyUsername();
-        }
+    /**
+     *    @deprecated
+     */
+    function useProxy($proxy, $username = false, $password = false) {
+        return Simpletest::useProxy($proxy, $username, $password);
+    }
 
-        /**
-         *    @deprecated
-         */
-        function getDefaultProxyPassword() {
-            return Simpletest::getDefaultProxyPassword();
-        }
+    /**
+     *    @deprecated
+     */
+    function getDefaultProxy() {
+        return Simpletest::getDefaultProxy();
+    }
+
+    /**
+     *    @deprecated
+     */
+    function getDefaultProxyUsername() {
+        return Simpletest::getDefaultProxyUsername();
+    }
+
+    /**
+     *    @deprecated
+     */
+    function getDefaultProxyPassword() {
+        return Simpletest::getDefaultProxyPassword();
     }
+}
 ?>
index 4205482080c142fc7495989f8904c66dffaa33cc..735a1986b34dc9a7290fb86765bc8a8dfd3d261d 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage MockObjects
+ *  @version    $Id$
+ */
+
+/**#@+
+ * include SimpleTest files
+ */
+require_once(dirname(__FILE__) . '/compatibility.php');
+/**#@-*/
+
+/**
+ *    Stashes an error for later. Useful for constructors
+ *    until PHP gets exceptions.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleStickyError {
+    var $_error = 'Constructor not chained';
+
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     MockObjects
-     * @version        $Id$
+     *    Sets the error to empty.
+     *    @access public
      */
+    function SimpleStickyError() {
+        $this->_clearError();
+    }
 
-    /**#@+
-     * include SimpleTest files
+    /**
+     *    Test for an outstanding error.
+     *    @return boolean           True if there is an error.
+     *    @access public
      */
-    require_once(dirname(__FILE__) . '/compatibility.php');
-    /**#@-*/
+    function isError() {
+        return ($this->_error != '');
+    }
 
     /**
-     *    Stashes an error for later. Useful for constructors
-     *    until PHP gets exceptions.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Accessor for an outstanding error.
+     *    @return string     Empty string if no error otherwise
+     *                       the error message.
+     *    @access public
      */
-    class SimpleStickyError {
-        var $_error = 'Constructor not chained';
-
-        /**
-         *    Sets the error to empty.
-         *    @access public
-         */
-        function SimpleStickyError() {
-            $this->_clearError();
-        }
-
-        /**
-         *    Test for an outstanding error.
-         *    @return boolean           True if there is an error.
-         *    @access public
-         */
-        function isError() {
-            return ($this->_error != '');
-        }
-
-        /**
-         *    Accessor for an outstanding error.
-         *    @return string     Empty string if no error otherwise
-         *                       the error message.
-         *    @access public
-         */
-        function getError() {
-            return $this->_error;
-        }
+    function getError() {
+        return $this->_error;
+    }
 
-        /**
-         *    Sets the internal error.
-         *    @param string       Error message to stash.
-         *    @access protected
-         */
-        function _setError($error) {
-            $this->_error = $error;
-        }
+    /**
+     *    Sets the internal error.
+     *    @param string       Error message to stash.
+     *    @access protected
+     */
+    function _setError($error) {
+        $this->_error = $error;
+    }
 
-        /**
-         *    Resets the error state to no error.
-         *    @access protected
-         */
-        function _clearError() {
-            $this->_setError('');
-        }
+    /**
+     *    Resets the error state to no error.
+     *    @access protected
+     */
+    function _clearError() {
+        $this->_setError('');
     }
+}
+
+/**
+ *    Wrapper for TCP/IP socket.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleSocket extends SimpleStickyError {
+    var $_handle;
+    var $_is_open = false;
+    var $_sent = '';
+    var $lock_size;
 
     /**
-     *    Wrapper for TCP/IP socket.
-     *    @package SimpleTest
-     *    @subpackage WebTester
+     *    Opens a socket for reading and writing.
+     *    @param string $host          Hostname to send request to.
+     *    @param integer $port         Port on remote machine to open.
+     *    @param integer $timeout      Connection timeout in seconds.
+     *    @param integer $block_size   Size of chunk to read.
+     *    @access public
      */
-    class SimpleSocket extends SimpleStickyError {
-        var $_handle;
-        var $_is_open = false;
-        var $_sent = '';
-        var $lock_size;
-
-        /**
-         *    Opens a socket for reading and writing.
-         *    @param string $host          Hostname to send request to.
-         *    @param integer $port         Port on remote machine to open.
-         *    @param integer $timeout      Connection timeout in seconds.
-         *    @param integer $block_size   Size of chunk to read.
-         *    @access public
-         */
-        function SimpleSocket($host, $port, $timeout, $block_size = 255) {
-            $this->SimpleStickyError();
-            if (! ($this->_handle = $this->_openSocket($host, $port, $error_number, $error, $timeout))) {
-                $this->_setError("Cannot open [$host:$port] with [$error] within [$timeout] seconds");
-                return;
-            }
-            $this->_is_open = true;
-            $this->_block_size = $block_size;
-            SimpleTestCompatibility::setTimeout($this->_handle, $timeout);
+    function SimpleSocket($host, $port, $timeout, $block_size = 255) {
+        $this->SimpleStickyError();
+        if (! ($this->_handle = $this->_openSocket($host, $port, $error_number, $error, $timeout))) {
+            $this->_setError("Cannot open [$host:$port] with [$error] within [$timeout] seconds");
+            return;
         }
+        $this->_is_open = true;
+        $this->_block_size = $block_size;
+        SimpleTestCompatibility::setTimeout($this->_handle, $timeout);
+    }
 
-        /**
-         *    Writes some data to the socket and saves alocal copy.
-         *    @param string $message       String to send to socket.
-         *    @return boolean              True if successful.
-         *    @access public
-         */
-        function write($message) {
-            if ($this->isError() || ! $this->isOpen()) {
-                return false;
-            }
-            $count = fwrite($this->_handle, $message);
-            if (! $count) {
-                if ($count === false) {
-                    $this->_setError('Cannot write to socket');
-                    $this->close();
-                }
-                return false;
-            }
-            fflush($this->_handle);
-            $this->_sent .= $message;
-            return true;
+    /**
+     *    Writes some data to the socket and saves alocal copy.
+     *    @param string $message       String to send to socket.
+     *    @return boolean              True if successful.
+     *    @access public
+     */
+    function write($message) {
+        if ($this->isError() || ! $this->isOpen()) {
+            return false;
         }
-
-        /**
-         *    Reads data from the socket. The error suppresion
-         *    is a workaround for PHP4 always throwing a warning
-         *    with a secure socket.
-         *    @return integer/boolean           Incoming bytes. False
-         *                                     on error.
-         *    @access public
-         */
-        function read() {
-            if ($this->isError() || ! $this->isOpen()) {
-                return false;
-            }
-            $raw = @fread($this->_handle, $this->_block_size);
-            if ($raw === false) {
-                $this->_setError('Cannot read from socket');
+        $count = fwrite($this->_handle, $message);
+        if (! $count) {
+            if ($count === false) {
+                $this->_setError('Cannot write to socket');
                 $this->close();
             }
-            return $raw;
+            return false;
         }
+        fflush($this->_handle);
+        $this->_sent .= $message;
+        return true;
+    }
 
-        /**
-         *    Accessor for socket open state.
-         *    @return boolean           True if open.
-         *    @access public
-         */
-        function isOpen() {
-            return $this->_is_open;
+    /**
+     *    Reads data from the socket. The error suppresion
+     *    is a workaround for PHP4 always throwing a warning
+     *    with a secure socket.
+     *    @return integer/boolean           Incoming bytes. False
+     *                                     on error.
+     *    @access public
+     */
+    function read() {
+        if ($this->isError() || ! $this->isOpen()) {
+            return false;
         }
-
-        /**
-         *    Closes the socket preventing further reads.
-         *    Cannot be reopened once closed.
-         *    @return boolean           True if successful.
-         *    @access public
-         */
-        function close() {
-            $this->_is_open = false;
-            return fclose($this->_handle);
+        $raw = @fread($this->_handle, $this->_block_size);
+        if ($raw === false) {
+            $this->_setError('Cannot read from socket');
+            $this->close();
         }
+        return $raw;
+    }
 
-        /**
-         *    Accessor for content so far.
-         *    @return string        Bytes sent only.
-         *    @access public
-         */
-        function getSent() {
-            return $this->_sent;
-        }
+    /**
+     *    Accessor for socket open state.
+     *    @return boolean           True if open.
+     *    @access public
+     */
+    function isOpen() {
+        return $this->_is_open;
+    }
 
-        /**
-         *    Actually opens the low level socket.
-         *    @param string $host          Host to connect to.
-         *    @param integer $port         Port on host.
-         *    @param integer $error_number Recipient of error code.
-         *    @param string $error         Recipoent of error message.
-         *    @param integer $timeout      Maximum time to wait for connection.
-         *    @access protected
-         */
-        function _openSocket($host, $port, &$error_number, &$error, $timeout) {
-            return @fsockopen($host, $port, $error_number, $error, $timeout);
-        }
+    /**
+     *    Closes the socket preventing further reads.
+     *    Cannot be reopened once closed.
+     *    @return boolean           True if successful.
+     *    @access public
+     */
+    function close() {
+        $this->_is_open = false;
+        return fclose($this->_handle);
     }
 
     /**
-     *    Wrapper for TCP/IP socket over TLS.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Accessor for content so far.
+     *    @return string        Bytes sent only.
+     *    @access public
      */
-    class SimpleSecureSocket extends SimpleSocket {
-
-        /**
-         *    Opens a secure socket for reading and writing.
-         *    @param string $host      Hostname to send request to.
-         *    @param integer $port     Port on remote machine to open.
-         *    @param integer $timeout  Connection timeout in seconds.
-         *    @access public
-         */
-        function SimpleSecureSocket($host, $port, $timeout) {
-            $this->SimpleSocket($host, $port, $timeout);
-        }
+    function getSent() {
+        return $this->_sent;
+    }
 
-        /**
-         *    Actually opens the low level socket.
-         *    @param string $host          Host to connect to.
-         *    @param integer $port         Port on host.
-         *    @param integer $error_number Recipient of error code.
-         *    @param string $error         Recipient of error message.
-         *    @param integer $timeout      Maximum time to wait for connection.
-         *    @access protected
-         */
-        function _openSocket($host, $port, &$error_number, &$error, $timeout) {
-            return parent::_openSocket("tls://$host", $port, $error_number, $error, $timeout);
-        }
+    /**
+     *    Actually opens the low level socket.
+     *    @param string $host          Host to connect to.
+     *    @param integer $port         Port on host.
+     *    @param integer $error_number Recipient of error code.
+     *    @param string $error         Recipoent of error message.
+     *    @param integer $timeout      Maximum time to wait for connection.
+     *    @access protected
+     */
+    function _openSocket($host, $port, &$error_number, &$error, $timeout) {
+        return @fsockopen($host, $port, $error_number, $error, $timeout);
+    }
+}
+
+/**
+ *    Wrapper for TCP/IP socket over TLS.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleSecureSocket extends SimpleSocket {
+
+    /**
+     *    Opens a secure socket for reading and writing.
+     *    @param string $host      Hostname to send request to.
+     *    @param integer $port     Port on remote machine to open.
+     *    @param integer $timeout  Connection timeout in seconds.
+     *    @access public
+     */
+    function SimpleSecureSocket($host, $port, $timeout) {
+        $this->SimpleSocket($host, $port, $timeout);
+    }
+
+    /**
+     *    Actually opens the low level socket.
+     *    @param string $host          Host to connect to.
+     *    @param integer $port         Port on host.
+     *    @param integer $error_number Recipient of error code.
+     *    @param string $error         Recipient of error message.
+     *    @param integer $timeout      Maximum time to wait for connection.
+     *    @access protected
+     */
+    function _openSocket($host, $port, &$error_number, &$error, $timeout) {
+        return parent::_openSocket("tls://$host", $port, $error_number, $error, $timeout);
     }
+}
 ?>
\ No newline at end of file
index c713d0a7b60a33c9ca22e3f15ee6111a1b2c3aa4..b0d037ab00fe14bbeb1ee3179306a16bd5886c7c 100644 (file)
 <?php
+/**
+ *  Base include file for SimpleTest.
+ *  @package    SimpleTest
+ *  @subpackage WebTester
+ *  @version    $Id$
+ */
+    
+/**#@+
+ * include SimpleTest files
+ */
+require_once(dirname(__FILE__) . '/parser.php');
+require_once(dirname(__FILE__) . '/encoding.php');
+/**#@-*/
+
+/**
+ *    HTML or XML tag.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleTag {
+    var $_name;
+    var $_attributes;
+    var $_content;
+    
+    /**
+     *    Starts with a named tag with attributes only.
+     *    @param string $name        Tag name.
+     *    @param hash $attributes    Attribute names and
+     *                               string values. Note that
+     *                               the keys must have been
+     *                               converted to lower case.
+     */
+    function SimpleTag($name, $attributes) {
+        $this->_name = strtolower(trim($name));
+        $this->_attributes = $attributes;
+        $this->_content = '';
+    }
+    
+    /**
+     *    Check to see if the tag can have both start and
+     *    end tags with content in between.
+     *    @return boolean        True if content allowed.
+     *    @access public
+     */
+    function expectEndTag() {
+        return true;
+    }
+    
+    /**
+     *    The current tag should not swallow all content for
+     *    itself as it's searchable page content. Private
+     *    content tags are usually widgets that contain default
+     *    values.
+     *    @return boolean        False as content is available
+     *                           to other tags by default.
+     *    @access public
+     */
+    function isPrivateContent() {
+        return false;
+    }
+
+    /**
+     *    Appends string content to the current content.
+     *    @param string $content        Additional text.
+     *    @access public
+     */
+    function addContent($content) {
+        $this->_content .= (string)$content;
+    }
+    
     /**
-     * Base include file for SimpleTest.
-     * @package        SimpleTest
-     * @subpackage     WebTester
-     * @version        $Id$
-     */
-     
-    /**#@+
-     * include SimpleTest files
-     */
-    require_once(dirname(__FILE__) . '/parser.php');
-    require_once(dirname(__FILE__) . '/encoding.php');
-    /**#@-*/
-   
-    /**
-     *    HTML or XML tag.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleTag {
-        var $_name;
-        var $_attributes;
-        var $_content;
-        
-        /**
-         *    Starts with a named tag with attributes only.
-         *    @param string $name        Tag name.
-         *    @param hash $attributes    Attribute names and
-         *                               string values. Note that
-         *                               the keys must have been
-         *                               converted to lower case.
-         */
-        function SimpleTag($name, $attributes) {
-            $this->_name = strtolower(trim($name));
-            $this->_attributes = $attributes;
-            $this->_content = '';
+     *    Adds an enclosed tag to the content.
+     *    @param SimpleTag $tag    New tag.
+     *    @access public
+     */
+    function addTag(&$tag) {
+    }
+    
+    /**
+     *    Accessor for tag name.
+     *    @return string       Name of tag.
+     *    @access public
+     */
+    function getTagName() {
+        return $this->_name;
+    }
+    
+    /**
+     *    List of legal child elements.
+     *    @return array        List of element names.
+     *    @access public
+     */
+    function getChildElements() {
+        return array();
+    }
+    
+    /**
+     *    Accessor for an attribute.
+     *    @param string $label    Attribute name.
+     *    @return string          Attribute value.
+     *    @access public
+     */
+    function getAttribute($label) {
+        $label = strtolower($label);
+        if (! isset($this->_attributes[$label])) {
+            return false;
         }
-        
-        /**
-         *    Check to see if the tag can have both start and
-         *    end tags with content in between.
-         *    @return boolean        True if content allowed.
-         *    @access public
-         */
-        function expectEndTag() {
-            return true;
+        return (string)$this->_attributes[$label];
+    }
+    
+    /**
+     *    Sets an attribute.
+     *    @param string $label    Attribute name.
+     *    @return string $value   New attribute value.
+     *    @access protected
+     */
+    function _setAttribute($label, $value) {
+        $this->_attributes[strtolower($label)] = $value;
+    }
+    
+    /**
+     *    Accessor for the whole content so far.
+     *    @return string       Content as big raw string.
+     *    @access public
+     */
+    function getContent() {
+        return $this->_content;
+    }
+    
+    /**
+     *    Accessor for content reduced to visible text. Acts
+     *    like a text mode browser, normalising space and
+     *    reducing images to their alt text.
+     *    @return string       Content as plain text.
+     *    @access public
+     */
+    function getText() {
+        return SimpleHtmlSaxParser::normalise($this->_content);
+    }
+    
+    /**
+     *    Test to see if id attribute matches.
+     *    @param string $id        ID to test against.
+     *    @return boolean          True on match.
+     *    @access public
+     */
+    function isId($id) {
+        return ($this->getAttribute('id') == $id);
+    }
+}
+
+/**
+ *    Base url.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleBaseTag extends SimpleTag {
+    
+    /**
+     *    Starts with a named tag with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function SimpleBaseTag($attributes) {
+        $this->SimpleTag('base', $attributes);
+    }
+
+    /**
+     *    Base tag is not a block tag.
+     *    @return boolean       false
+     *    @access public
+     */
+    function expectEndTag() {
+        return false;
+    }
+}
+
+/**
+ *    Page title.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleTitleTag extends SimpleTag {
+    
+    /**
+     *    Starts with a named tag with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function SimpleTitleTag($attributes) {
+        $this->SimpleTag('title', $attributes);
+    }
+}
+
+/**
+ *    Link.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleAnchorTag extends SimpleTag {
+    
+    /**
+     *    Starts with a named tag with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function SimpleAnchorTag($attributes) {
+        $this->SimpleTag('a', $attributes);
+    }
+    
+    /**
+     *    Accessor for URL as string.
+     *    @return string    Coerced as string.
+     *    @access public
+     */
+    function getHref() {
+        $url = $this->getAttribute('href');
+        if (is_bool($url)) {
+            $url = '';
+        }
+        return $url;
+    }
+}
+
+/**
+ *    Form element.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleWidget extends SimpleTag {
+    var $_value;
+    var $_label;
+    var $_is_set;
+    
+    /**
+     *    Starts with a named tag with attributes only.
+     *    @param string $name        Tag name.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function SimpleWidget($name, $attributes) {
+        $this->SimpleTag($name, $attributes);
+        $this->_value = false;
+        $this->_label = false;
+        $this->_is_set = false;
+    }
+    
+    /**
+     *    Accessor for name submitted as the key in
+     *    GET/POST variables hash.
+     *    @return string        Parsed value.
+     *    @access public
+     */
+    function getName() {
+        return $this->getAttribute('name');
+    }
+    
+    /**
+     *    Accessor for default value parsed with the tag.
+     *    @return string        Parsed value.
+     *    @access public
+     */
+    function getDefault() {
+        return $this->getAttribute('value');
+    }
+    
+    /**
+     *    Accessor for currently set value or default if
+     *    none.
+     *    @return string      Value set by form or default
+     *                        if none.
+     *    @access public
+     */
+    function getValue() {
+        if (! $this->_is_set) {
+            return $this->getDefault();
+        }
+        return $this->_value;
+    }
+    
+    /**
+     *    Sets the current form element value.
+     *    @param string $value       New value.
+     *    @return boolean            True if allowed.
+     *    @access public
+     */
+    function setValue($value) {
+        $this->_value = $value;
+        $this->_is_set = true;
+        return true;
+    }
+    
+    /**
+     *    Resets the form element value back to the
+     *    default.
+     *    @access public
+     */
+    function resetValue() {
+        $this->_is_set = false;
+    }
+    
+    /**
+     *    Allows setting of a label externally, say by a
+     *    label tag.
+     *    @param string $label    Label to attach.
+     *    @access public
+     */
+    function setLabel($label) {
+        $this->_label = trim($label);
+    }
+    
+    /**
+     *    Reads external or internal label.
+     *    @param string $label    Label to test.
+     *    @return boolean         True is match.
+     *    @access public
+     */
+    function isLabel($label) {
+        return $this->_label == trim($label);
+    }
+    
+    /**
+     *    Dispatches the value into the form encoded packet.
+     *    @param SimpleEncoding $encoding    Form packet.
+     *    @access public
+     */
+    function write(&$encoding) {
+        if ($this->getName()) {
+            $encoding->add($this->getName(), $this->getValue());
+        }
+    }
+}
+
+/**
+ *    Text, password and hidden field.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleTextTag extends SimpleWidget {
+    
+    /**
+     *    Starts with a named tag with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function SimpleTextTag($attributes) {
+        $this->SimpleWidget('input', $attributes);
+        if ($this->getAttribute('value') === false) {
+            $this->_setAttribute('value', '');
         }
-        
-        /**
-         *    The current tag should not swallow all content for
-         *    itself as it's searchable page content. Private
-         *    content tags are usually widgets that contain default
-         *    values.
-         *    @return boolean        False as content is available
-         *                           to other tags by default.
-         *    @access public
-         */
-        function isPrivateContent() {
+    }
+    
+    /**
+     *    Tag contains no content.
+     *    @return boolean        False.
+     *    @access public
+     */
+    function expectEndTag() {
+        return false;
+    }
+    
+    /**
+     *    Sets the current form element value. Cannot
+     *    change the value of a hidden field.
+     *    @param string $value       New value.
+     *    @return boolean            True if allowed.
+     *    @access public
+     */
+    function setValue($value) {
+        if ($this->getAttribute('type') == 'hidden') {
             return false;
         }
+        return parent::setValue($value);
+    }
+}
 
-        /**
-         *    Appends string content to the current content.
-         *    @param string $content        Additional text.
-         *    @access public
-         */
-        function addContent($content) {
-            $this->_content .= (string)$content;
+/**
+ *    Submit button as input tag.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleSubmitTag extends SimpleWidget {
+    
+    /**
+     *    Starts with a named tag with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function SimpleSubmitTag($attributes) {
+        $this->SimpleWidget('input', $attributes);
+        if ($this->getAttribute('value') === false) {
+            $this->_setAttribute('value', 'Submit');
         }
-        
-        /**
-         *    Adds an enclosed tag to the content.
-         *    @param SimpleTag $tag    New tag.
-         *    @access public
-         */
-        function addTag(&$tag) {
+    }
+    
+    /**
+     *    Tag contains no end element.
+     *    @return boolean        False.
+     *    @access public
+     */
+    function expectEndTag() {
+        return false;
+    }
+    
+    /**
+     *    Disables the setting of the button value.
+     *    @param string $value       Ignored.
+     *    @return boolean            True if allowed.
+     *    @access public
+     */
+    function setValue($value) {
+        return false;
+    }
+    
+    /**
+     *    Value of browser visible text.
+     *    @return string        Visible label.
+     *    @access public
+     */
+    function getLabel() {
+        return $this->getValue();
+    }
+    
+    /**
+     *    Test for a label match when searching.
+     *    @param string $label     Label to test.
+     *    @return boolean          True on match.
+     *    @access public
+     */
+    function isLabel($label) {
+        return trim($label) == trim($this->getLabel());
+    }
+}
+    
+/**
+ *    Image button as input tag.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleImageSubmitTag extends SimpleWidget {
+    
+    /**
+     *    Starts with a named tag with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function SimpleImageSubmitTag($attributes) {
+        $this->SimpleWidget('input', $attributes);
+    }
+    
+    /**
+     *    Tag contains no end element.
+     *    @return boolean        False.
+     *    @access public
+     */
+    function expectEndTag() {
+        return false;
+    }
+    
+    /**
+     *    Disables the setting of the button value.
+     *    @param string $value       Ignored.
+     *    @return boolean            True if allowed.
+     *    @access public
+     */
+    function setValue($value) {
+        return false;
+    }
+    
+    /**
+     *    Value of browser visible text.
+     *    @return string        Visible label.
+     *    @access public
+     */
+    function getLabel() {
+        if ($this->getAttribute('title')) {
+            return $this->getAttribute('title');
         }
-        
-        /**
-         *    Accessor for tag name.
-         *    @return string       Name of tag.
-         *    @access public
-         */
-        function getTagName() {
-            return $this->_name;
+        return $this->getAttribute('alt');
+    }
+    
+    /**
+     *    Test for a label match when searching.
+     *    @param string $label     Label to test.
+     *    @return boolean          True on match.
+     *    @access public
+     */
+    function isLabel($label) {
+        return trim($label) == trim($this->getLabel());
+    }
+    
+    /**
+     *    Dispatches the value into the form encoded packet.
+     *    @param SimpleEncoding $encoding    Form packet.
+     *    @param integer $x                  X coordinate of click.
+     *    @param integer $y                  Y coordinate of click.
+     *    @access public
+     */
+    function write(&$encoding, $x, $y) {
+        if ($this->getName()) {
+            $encoding->add($this->getName() . '.x', $x);
+            $encoding->add($this->getName() . '.y', $y);
+        } else {
+            $encoding->add('x', $x);
+            $encoding->add('y', $y);
         }
-        
-        /**
-         *    List of legal child elements.
-         *    @return array        List of element names.
-         *    @access public
-         */
-        function getChildElements() {
-            return array();
+    }
+}
+    
+/**
+ *    Submit button as button tag.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleButtonTag extends SimpleWidget {
+    
+    /**
+     *    Starts with a named tag with attributes only.
+     *    Defaults are very browser dependent.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function SimpleButtonTag($attributes) {
+        $this->SimpleWidget('button', $attributes);
+    }
+    
+    /**
+     *    Check to see if the tag can have both start and
+     *    end tags with content in between.
+     *    @return boolean        True if content allowed.
+     *    @access public
+     */
+    function expectEndTag() {
+        return true;
+    }
+    
+    /**
+     *    Disables the setting of the button value.
+     *    @param string $value       Ignored.
+     *    @return boolean            True if allowed.
+     *    @access public
+     */
+    function setValue($value) {
+        return false;
+    }
+    
+    /**
+     *    Value of browser visible text.
+     *    @return string        Visible label.
+     *    @access public
+     */
+    function getLabel() {
+        return $this->getContent();
+    }
+    
+    /**
+     *    Test for a label match when searching.
+     *    @param string $label     Label to test.
+     *    @return boolean          True on match.
+     *    @access public
+     */
+    function isLabel($label) {
+        return trim($label) == trim($this->getLabel());
+    }
+}
+
+/**
+ *    Content tag for text area.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleTextAreaTag extends SimpleWidget {
+    
+    /**
+     *    Starts with a named tag with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function SimpleTextAreaTag($attributes) {
+        $this->SimpleWidget('textarea', $attributes);
+    }
+    
+    /**
+     *    Accessor for starting value.
+     *    @return string        Parsed value.
+     *    @access public
+     */
+    function getDefault() {
+        return $this->_wrap(SimpleHtmlSaxParser::decodeHtml($this->getContent()));
+    }
+    
+    /**
+     *    Applies word wrapping if needed.
+     *    @param string $value      New value.
+     *    @return boolean            True if allowed.
+     *    @access public
+     */
+    function setValue($value) {
+        return parent::setValue($this->_wrap($value));
+    }
+    
+    /**
+     *    Test to see if text should be wrapped.
+     *    @return boolean        True if wrapping on.
+     *    @access private
+     */
+    function _wrapIsEnabled() {
+        if ($this->getAttribute('cols')) {
+            $wrap = $this->getAttribute('wrap');
+            if (($wrap == 'physical') || ($wrap == 'hard')) {
+                return true;
+            }
         }
-        
-        /**
-         *    Accessor for an attribute.
-         *    @param string $label    Attribute name.
-         *    @return string          Attribute value.
-         *    @access public
-         */
-        function getAttribute($label) {
-            $label = strtolower($label);
-            if (! isset($this->_attributes[$label])) {
+        return false;
+    }
+    
+    /**
+     *    Performs the formatting that is peculiar to
+     *    this tag. There is strange behaviour in this
+     *    one, including stripping a leading new line.
+     *    Go figure. I am using Firefox as a guide.
+     *    @param string $text    Text to wrap.
+     *    @return string         Text wrapped with carriage
+     *                           returns and line feeds
+     *    @access private
+     */
+    function _wrap($text) {
+        $text = str_replace("\r\r\n", "\r\n", str_replace("\n", "\r\n", $text));
+        $text = str_replace("\r\n\n", "\r\n", str_replace("\r", "\r\n", $text));
+        if (strncmp($text, "\r\n", strlen("\r\n")) == 0) {
+            $text = substr($text, strlen("\r\n"));
+        }
+        if ($this->_wrapIsEnabled()) {
+            return wordwrap(
+                    $text,
+                    (integer)$this->getAttribute('cols'),
+                    "\r\n");
+        }
+        return $text;
+    }
+    
+    /**
+     *    The content of textarea is not part of the page.
+     *    @return boolean        True.
+     *    @access public
+     */
+    function isPrivateContent() {
+        return true;
+    }
+}
+
+/**
+ *    File upload widget.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleUploadTag extends SimpleWidget {
+    
+    /**
+     *    Starts with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function SimpleUploadTag($attributes) {
+        $this->SimpleWidget('input', $attributes);
+    }
+    
+    /**
+     *    Tag contains no content.
+     *    @return boolean        False.
+     *    @access public
+     */
+    function expectEndTag() {
+        return false;
+    }
+    
+    /**
+     *    Dispatches the value into the form encoded packet.
+     *    @param SimpleEncoding $encoding    Form packet.
+     *    @access public
+     */
+    function write(&$encoding) {
+        if (! file_exists($this->getValue())) {
+            return;
+        }
+        $encoding->attach(
+                $this->getName(),
+                implode('', file($this->getValue())),
+                basename($this->getValue()));
+    }
+}
+
+/**
+ *    Drop down widget.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleSelectionTag extends SimpleWidget {
+    var $_options;
+    var $_choice;
+    
+    /**
+     *    Starts with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function SimpleSelectionTag($attributes) {
+        $this->SimpleWidget('select', $attributes);
+        $this->_options = array();
+        $this->_choice = false;
+    }
+    
+    /**
+     *    Adds an option tag to a selection field.
+     *    @param SimpleOptionTag $tag     New option.
+     *    @access public
+     */
+    function addTag(&$tag) {
+        if ($tag->getTagName() == 'option') {
+            $this->_options[] = &$tag;
+        }
+    }
+    
+    /**
+     *    Text within the selection element is ignored.
+     *    @param string $content        Ignored.
+     *    @access public
+     */
+    function addContent($content) {
+    }
+    
+    /**
+     *    Scans options for defaults. If none, then
+     *    the first option is selected.
+     *    @return string        Selected field.
+     *    @access public
+     */
+    function getDefault() {
+        for ($i = 0, $count = count($this->_options); $i < $count; $i++) {
+            if ($this->_options[$i]->getAttribute('selected') !== false) {
+                return $this->_options[$i]->getDefault();
+            }
+        }
+        if ($count > 0) {
+            return $this->_options[0]->getDefault();
+        }
+        return '';
+    }
+    
+    /**
+     *    Can only set allowed values.
+     *    @param string $value       New choice.
+     *    @return boolean            True if allowed.
+     *    @access public
+     */
+    function setValue($value) {
+        for ($i = 0, $count = count($this->_options); $i < $count; $i++) {
+            if ($this->_options[$i]->isValue($value)) {
+                $this->_choice = $i;
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    /**
+     *    Accessor for current selection value.
+     *    @return string      Value attribute or
+     *                        content of opton.
+     *    @access public
+     */
+    function getValue() {
+        if ($this->_choice === false) {
+            return $this->getDefault();
+        }
+        return $this->_options[$this->_choice]->getValue();
+    }
+}
+
+/**
+ *    Drop down widget.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class MultipleSelectionTag extends SimpleWidget {
+    var $_options;
+    var $_values;
+    
+    /**
+     *    Starts with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function MultipleSelectionTag($attributes) {
+        $this->SimpleWidget('select', $attributes);
+        $this->_options = array();
+        $this->_values = false;
+    }
+    
+    /**
+     *    Adds an option tag to a selection field.
+     *    @param SimpleOptionTag $tag     New option.
+     *    @access public
+     */
+    function addTag(&$tag) {
+        if ($tag->getTagName() == 'option') {
+            $this->_options[] = &$tag;
+        }
+    }
+    
+    /**
+     *    Text within the selection element is ignored.
+     *    @param string $content        Ignored.
+     *    @access public
+     */
+    function addContent($content) {
+    }
+    
+    /**
+     *    Scans options for defaults to populate the
+     *    value array().
+     *    @return array        Selected fields.
+     *    @access public
+     */
+    function getDefault() {
+        $default = array();
+        for ($i = 0, $count = count($this->_options); $i < $count; $i++) {
+            if ($this->_options[$i]->getAttribute('selected') !== false) {
+                $default[] = $this->_options[$i]->getDefault();
+            }
+        }
+        return $default;
+    }
+    
+    /**
+     *    Can only set allowed values. Any illegal value
+     *    will result in a failure, but all correct values
+     *    will be set.
+     *    @param array $desired      New choices.
+     *    @return boolean            True if all allowed.
+     *    @access public
+     */
+    function setValue($desired) {
+        $achieved = array();
+        foreach ($desired as $value) {
+            $success = false;
+            for ($i = 0, $count = count($this->_options); $i < $count; $i++) {
+                if ($this->_options[$i]->isValue($value)) {
+                    $achieved[] = $this->_options[$i]->getValue();
+                    $success = true;
+                    break;
+                }
+            }
+            if (! $success) {
                 return false;
             }
-            return (string)$this->_attributes[$label];
-        }
-        
-        /**
-         *    Sets an attribute.
-         *    @param string $label    Attribute name.
-         *    @return string $value   New attribute value.
-         *    @access protected
-         */
-        function _setAttribute($label, $value) {
-            $this->_attributes[strtolower($label)] = $value;
-        }
-        
-        /**
-         *    Accessor for the whole content so far.
-         *    @return string       Content as big raw string.
-         *    @access public
-         */
-        function getContent() {
-            return $this->_content;
-        }
-        
-        /**
-         *    Accessor for content reduced to visible text. Acts
-         *    like a text mode browser, normalising space and
-         *    reducing images to their alt text.
-         *    @return string       Content as plain text.
-         *    @access public
-         */
-        function getText() {
-            return SimpleHtmlSaxParser::normalise($this->_content);
-        }
-        
-        /**
-         *    Test to see if id attribute matches.
-         *    @param string $id        ID to test against.
-         *    @return boolean          True on match.
-         *    @access public
-         */
-        function isId($id) {
-            return ($this->getAttribute('id') == $id);
         }
+        $this->_values = $achieved;
+        return true;
     }
     
     /**
-     *    Page title.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Accessor for current selection value.
+     *    @return array      List of currently set options.
+     *    @access public
      */
-    class SimpleTitleTag extends SimpleTag {
-        
-        /**
-         *    Starts with a named tag with attributes only.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleTitleTag($attributes) {
-            $this->SimpleTag('title', $attributes);
+    function getValue() {
+        if ($this->_values === false) {
+            return $this->getDefault();
         }
+        return $this->_values;
     }
+}
+
+/**
+ *    Option for selection field.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleOptionTag extends SimpleWidget {
     
     /**
-     *    Link.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Stashes the attributes.
      */
-    class SimpleAnchorTag extends SimpleTag {
-        
-        /**
-         *    Starts with a named tag with attributes only.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleAnchorTag($attributes) {
-            $this->SimpleTag('a', $attributes);
-        }
-        
-        /**
-         *    Accessor for URL as string.
-         *    @return string    Coerced as string.
-         *    @access public
-         */
-        function getHref() {
-            $url = $this->getAttribute('href');
-            if (is_bool($url)) {
-                $url = '';
-            }
-            return $url;
-        }
+    function SimpleOptionTag($attributes) {
+        $this->SimpleWidget('option', $attributes);
     }
     
     /**
-     *    Form element.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleWidget extends SimpleTag {
-        var $_value;
-        var $_label;
-        var $_is_set;
-        
-        /**
-         *    Starts with a named tag with attributes only.
-         *    @param string $name        Tag name.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleWidget($name, $attributes) {
-            $this->SimpleTag($name, $attributes);
-            $this->_value = false;
-            $this->_label = false;
-            $this->_is_set = false;
-        }
-        
-        /**
-         *    Accessor for name submitted as the key in
-         *    GET/POST variables hash.
-         *    @return string        Parsed value.
-         *    @access public
-         */
-        function getName() {
-            return $this->getAttribute('name');
-        }
-        
-        /**
-         *    Accessor for default value parsed with the tag.
-         *    @return string        Parsed value.
-         *    @access public
-         */
-        function getDefault() {
-            return $this->getAttribute('value');
-        }
-        
-        /**
-         *    Accessor for currently set value or default if
-         *    none.
-         *    @return string      Value set by form or default
-         *                        if none.
-         *    @access public
-         */
-        function getValue() {
-            if (! $this->_is_set) {
-                return $this->getDefault();
-            }
-            return $this->_value;
-        }
-        
-        /**
-         *    Sets the current form element value.
-         *    @param string $value       New value.
-         *    @return boolean            True if allowed.
-         *    @access public
-         */
-        function setValue($value) {
-            $this->_value = $value;
-            $this->_is_set = true;
+     *    Does nothing.
+     *    @param string $value      Ignored.
+     *    @return boolean           Not allowed.
+     *    @access public
+     */
+    function setValue($value) {
+        return false;
+    }
+    
+    /**
+     *    Test to see if a value matches the option.
+     *    @param string $compare    Value to compare with.
+     *    @return boolean           True if possible match.
+     *    @access public
+     */
+    function isValue($compare) {
+        $compare = trim($compare);
+        if (trim($this->getValue()) == $compare) {
             return true;
         }
-        
-        /**
-         *    Resets the form element value back to the
-         *    default.
-         *    @access public
-         */
-        function resetValue() {
-            $this->_is_set = false;
-        }
-        
-        /**
-         *    Allows setting of a label externally, say by a
-         *    label tag.
-         *    @param string $label    Label to attach.
-         *    @access public
-         */
-        function setLabel($label) {
-            $this->_label = trim($label);
-        }
-        
-        /**
-         *    Reads external or internal label.
-         *    @param string $label    Label to test.
-         *    @return boolean         True is match.
-         *    @access public
-         */
-        function isLabel($label) {
-            return $this->_label == trim($label);
-        }
-        
-        /**
-         *    Dispatches the value into the form encoded packet.
-         *    @param SimpleEncoding $encoding    Form packet.
-         *    @access public
-         */
-        function write(&$encoding) {
-            if ($this->getName()) {
-                $encoding->add($this->getName(), $this->getValue());
-            }
-        }
+        return trim($this->getContent()) == $compare;
     }
     
     /**
-     *    Text, password and hidden field.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Accessor for starting value. Will be set to
+     *    the option label if no value exists.
+     *    @return string        Parsed value.
+     *    @access public
      */
-    class SimpleTextTag extends SimpleWidget {
-        
-        /**
-         *    Starts with a named tag with attributes only.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleTextTag($attributes) {
-            $this->SimpleWidget('input', $attributes);
-            if ($this->getAttribute('value') === false) {
-                $this->_setAttribute('value', '');
-            }
-        }
-        
-        /**
-         *    Tag contains no content.
-         *    @return boolean        False.
-         *    @access public
-         */
-        function expectEndTag() {
-            return false;
-        }
-        
-        /**
-         *    Sets the current form element value. Cannot
-         *    change the value of a hidden field.
-         *    @param string $value       New value.
-         *    @return boolean            True if allowed.
-         *    @access public
-         */
-        function setValue($value) {
-            if ($this->getAttribute('type') == 'hidden') {
-                return false;
-            }
-            return parent::setValue($value);
+    function getDefault() {
+        if ($this->getAttribute('value') === false) {
+            return $this->getContent();
         }
+        return $this->getAttribute('value');
     }
     
     /**
-     *    Submit button as input tag.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    The content of options is not part of the page.
+     *    @return boolean        True.
+     *    @access public
      */
-    class SimpleSubmitTag extends SimpleWidget {
-        
-        /**
-         *    Starts with a named tag with attributes only.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleSubmitTag($attributes) {
-            $this->SimpleWidget('input', $attributes);
-            if ($this->getAttribute('value') === false) {
-                $this->_setAttribute('value', 'Submit');
-            }
-        }
-        
-        /**
-         *    Tag contains no end element.
-         *    @return boolean        False.
-         *    @access public
-         */
-        function expectEndTag() {
-            return false;
-        }
-        
-        /**
-         *    Disables the setting of the button value.
-         *    @param string $value       Ignored.
-         *    @return boolean            True if allowed.
-         *    @access public
-         */
-        function setValue($value) {
-            return false;
-        }
-        
-        /**
-         *    Value of browser visible text.
-         *    @return string        Visible label.
-         *    @access public
-         */
-        function getLabel() {
-            return $this->getValue();
-        }
-        
-        /**
-         *    Test for a label match when searching.
-         *    @param string $label     Label to test.
-         *    @return boolean          True on match.
-         *    @access public
-         */
-        function isLabel($label) {
-            return trim($label) == trim($this->getLabel());
-        }
+    function isPrivateContent() {
+        return true;
     }
-      
-    /**
-     *    Image button as input tag.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleImageSubmitTag extends SimpleWidget {
-        
-        /**
-         *    Starts with a named tag with attributes only.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleImageSubmitTag($attributes) {
-            $this->SimpleWidget('input', $attributes);
-        }
-        
-        /**
-         *    Tag contains no end element.
-         *    @return boolean        False.
-         *    @access public
-         */
-        function expectEndTag() {
-            return false;
-        }
-        
-        /**
-         *    Disables the setting of the button value.
-         *    @param string $value       Ignored.
-         *    @return boolean            True if allowed.
-         *    @access public
-         */
-        function setValue($value) {
-            return false;
-        }
-        
-        /**
-         *    Value of browser visible text.
-         *    @return string        Visible label.
-         *    @access public
-         */
-        function getLabel() {
-            if ($this->getAttribute('title')) {
-                return $this->getAttribute('title');
-            }
-            return $this->getAttribute('alt');
-        }
-        
-        /**
-         *    Test for a label match when searching.
-         *    @param string $label     Label to test.
-         *    @return boolean          True on match.
-         *    @access public
-         */
-        function isLabel($label) {
-            return trim($label) == trim($this->getLabel());
-        }
-        
-        /**
-         *    Dispatches the value into the form encoded packet.
-         *    @param SimpleEncoding $encoding    Form packet.
-         *    @param integer $x                  X coordinate of click.
-         *    @param integer $y                  Y coordinate of click.
-         *    @access public
-         */
-        function write(&$encoding, $x, $y) {
-            if ($this->getName()) {
-                $encoding->add($this->getName() . '.x', $x);
-                $encoding->add($this->getName() . '.y', $y);
-            } else {
-                $encoding->add('x', $x);
-                $encoding->add('y', $y);
-            }
+}
+
+/**
+ *    Radio button.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleRadioButtonTag extends SimpleWidget {
+    
+    /**
+     *    Stashes the attributes.
+     *    @param array $attributes        Hash of attributes.
+     */
+    function SimpleRadioButtonTag($attributes) {
+        $this->SimpleWidget('input', $attributes);
+        if ($this->getAttribute('value') === false) {
+            $this->_setAttribute('value', 'on');
         }
     }
-      
-    /**
-     *    Submit button as button tag.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleButtonTag extends SimpleWidget {
-        
-        /**
-         *    Starts with a named tag with attributes only.
-         *    Defaults are very browser dependent.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleButtonTag($attributes) {
-            $this->SimpleWidget('button', $attributes);
-        }
-        
-        /**
-         *    Check to see if the tag can have both start and
-         *    end tags with content in between.
-         *    @return boolean        True if content allowed.
-         *    @access public
-         */
-        function expectEndTag() {
-            return true;
-        }
-        
-        /**
-         *    Disables the setting of the button value.
-         *    @param string $value       Ignored.
-         *    @return boolean            True if allowed.
-         *    @access public
-         */
-        function setValue($value) {
-            return false;
-        }
-        
-        /**
-         *    Value of browser visible text.
-         *    @return string        Visible label.
-         *    @access public
-         */
-        function getLabel() {
-            return $this->getContent();
-        }
-        
-        /**
-         *    Test for a label match when searching.
-         *    @param string $label     Label to test.
-         *    @return boolean          True on match.
-         *    @access public
-         */
-        function isLabel($label) {
-            return trim($label) == trim($this->getLabel());
-        }
+    
+    /**
+     *    Tag contains no content.
+     *    @return boolean        False.
+     *    @access public
+     */
+    function expectEndTag() {
+        return false;
     }
-  
-    /**
-     *    Content tag for text area.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleTextAreaTag extends SimpleWidget {
-        
-        /**
-         *    Starts with a named tag with attributes only.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleTextAreaTag($attributes) {
-            $this->SimpleWidget('textarea', $attributes);
-        }
-        
-        /**
-         *    Accessor for starting value.
-         *    @return string        Parsed value.
-         *    @access public
-         */
-        function getDefault() {
-            return $this->_wrap(SimpleHtmlSaxParser::decodeHtml($this->getContent()));
-        }
-        
-        /**
-         *    Applies word wrapping if needed.
-         *    @param string $value      New value.
-         *    @return boolean            True if allowed.
-         *    @access public
-         */
-        function setValue($value) {
-            return parent::setValue($this->_wrap($value));
+    
+    /**
+     *    The only allowed value sn the one in the
+     *    "value" attribute.
+     *    @param string $value      New value.
+     *    @return boolean           True if allowed.
+     *    @access public
+     */
+    function setValue($value) {
+        if ($value === false) {
+            return parent::setValue($value);
         }
-        
-        /**
-         *    Test to see if text should be wrapped.
-         *    @return boolean        True if wrapping on.
-         *    @access private
-         */
-        function _wrapIsEnabled() {
-            if ($this->getAttribute('cols')) {
-                $wrap = $this->getAttribute('wrap');
-                if (($wrap == 'physical') || ($wrap == 'hard')) {
-                    return true;
-                }
-            }
+        if ($value != $this->getAttribute('value')) {
             return false;
         }
-        
-        /**
-         *    Performs the formatting that is peculiar to
-         *    this tag. There is strange behaviour in this
-         *    one, including stripping a leading new line.
-         *    Go figure. I am using Firefox as a guide.
-         *    @param string $text    Text to wrap.
-         *    @return string         Text wrapped with carriage
-         *                           returns and line feeds
-         *    @access private
-         */
-        function _wrap($text) {
-            $text = str_replace("\r\r\n", "\r\n", str_replace("\n", "\r\n", $text));
-            $text = str_replace("\r\n\n", "\r\n", str_replace("\r", "\r\n", $text));
-            if (strncmp($text, "\r\n", strlen("\r\n")) == 0) {
-                $text = substr($text, strlen("\r\n"));
-            }
-            if ($this->_wrapIsEnabled()) {
-                return wordwrap(
-                        $text,
-                        (integer)$this->getAttribute('cols'),
-                        "\r\n");
-            }
-            return $text;
-        }
-        
-        /**
-         *    The content of textarea is not part of the page.
-         *    @return boolean        True.
-         *    @access public
-         */
-        function isPrivateContent() {
-            return true;
-        }
+        return parent::setValue($value);
     }
     
     /**
-     *    File upload widget.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Accessor for starting value.
+     *    @return string        Parsed value.
+     *    @access public
      */
-    class SimpleUploadTag extends SimpleWidget {
-        
-        /**
-         *    Starts with attributes only.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleUploadTag($attributes) {
-            $this->SimpleWidget('input', $attributes);
-        }
-        
-        /**
-         *    Tag contains no content.
-         *    @return boolean        False.
-         *    @access public
-         */
-        function expectEndTag() {
-            return false;
-        }
-        
-        /**
-         *    Dispatches the value into the form encoded packet.
-         *    @param SimpleEncoding $encoding    Form packet.
-         *    @access public
-         */
-        function write(&$encoding) {
-            if (! file_exists($this->getValue())) {
-                return;
-            }
-            $encoding->attach(
-                    $this->getName(),
-                    implode('', file($this->getValue())),
-                    basename($this->getValue()));
+    function getDefault() {
+        if ($this->getAttribute('checked') !== false) {
+            return $this->getAttribute('value');
         }
+        return false;
     }
+}
+
+/**
+ *    Checkbox widget.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleCheckboxTag extends SimpleWidget {
     
     /**
-     *    Drop down widget.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleSelectionTag extends SimpleWidget {
-        var $_options;
-        var $_choice;
-        
-        /**
-         *    Starts with attributes only.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleSelectionTag($attributes) {
-            $this->SimpleWidget('select', $attributes);
-            $this->_options = array();
-            $this->_choice = false;
-        }
-        
-        /**
-         *    Adds an option tag to a selection field.
-         *    @param SimpleOptionTag $tag     New option.
-         *    @access public
-         */
-        function addTag(&$tag) {
-            if ($tag->getTagName() == 'option') {
-                $this->_options[] = &$tag;
-            }
+     *    Starts with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function SimpleCheckboxTag($attributes) {
+        $this->SimpleWidget('input', $attributes);
+        if ($this->getAttribute('value') === false) {
+            $this->_setAttribute('value', 'on');
         }
-        
-        /**
-         *    Text within the selection element is ignored.
-         *    @param string $content        Ignored.
-         *    @access public
-         */
-        function addContent($content) {
+    }
+    
+    /**
+     *    Tag contains no content.
+     *    @return boolean        False.
+     *    @access public
+     */
+    function expectEndTag() {
+        return false;
+    }
+    
+    /**
+     *    The only allowed value in the one in the
+     *    "value" attribute. The default for this
+     *    attribute is "on". If this widget is set to
+     *    true, then the usual value will be taken.
+     *    @param string $value      New value.
+     *    @return boolean           True if allowed.
+     *    @access public
+     */
+    function setValue($value) {
+        if ($value === false) {
+            return parent::setValue($value);
         }
-        
-        /**
-         *    Scans options for defaults. If none, then
-         *    the first option is selected.
-         *    @return string        Selected field.
-         *    @access public
-         */
-        function getDefault() {
-            for ($i = 0, $count = count($this->_options); $i < $count; $i++) {
-                if ($this->_options[$i]->getAttribute('selected') !== false) {
-                    return $this->_options[$i]->getDefault();
-                }
-            }
-            if ($count > 0) {
-                return $this->_options[0]->getDefault();
-            }
-            return '';
+        if ($value === true) {
+            return parent::setValue($this->getAttribute('value'));
         }
-        
-        /**
-         *    Can only set allowed values.
-         *    @param string $value       New choice.
-         *    @return boolean            True if allowed.
-         *    @access public
-         */
-        function setValue($value) {
-            for ($i = 0, $count = count($this->_options); $i < $count; $i++) {
-                if ($this->_options[$i]->isValue($value)) {
-                    $this->_choice = $i;
-                    return true;
-                }
-            }
+        if ($value != $this->getAttribute('value')) {
             return false;
         }
-        
-        /**
-         *    Accessor for current selection value.
-         *    @return string      Value attribute or
-         *                        content of opton.
-         *    @access public
-         */
-        function getValue() {
-            if ($this->_choice === false) {
-                return $this->getDefault();
-            }
-            return $this->_options[$this->_choice]->getValue();
-        }
+        return parent::setValue($value);
     }
     
     /**
-     *    Drop down widget.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class MultipleSelectionTag extends SimpleWidget {
-        var $_options;
-        var $_values;
-        
-        /**
-         *    Starts with attributes only.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function MultipleSelectionTag($attributes) {
-            $this->SimpleWidget('select', $attributes);
-            $this->_options = array();
-            $this->_values = false;
-        }
-        
-        /**
-         *    Adds an option tag to a selection field.
-         *    @param SimpleOptionTag $tag     New option.
-         *    @access public
-         */
-        function addTag(&$tag) {
-            if ($tag->getTagName() == 'option') {
-                $this->_options[] = &$tag;
-            }
-        }
-        
-        /**
-         *    Text within the selection element is ignored.
-         *    @param string $content        Ignored.
-         *    @access public
-         */
-        function addContent($content) {
-        }
-        
-        /**
-         *    Scans options for defaults to populate the
-         *    value array().
-         *    @return array        Selected fields.
-         *    @access public
-         */
-        function getDefault() {
-            $default = array();
-            for ($i = 0, $count = count($this->_options); $i < $count; $i++) {
-                if ($this->_options[$i]->getAttribute('selected') !== false) {
-                    $default[] = $this->_options[$i]->getDefault();
-                }
-            }
-            return $default;
+     *    Accessor for starting value. The default
+     *    value is "on".
+     *    @return string        Parsed value.
+     *    @access public
+     */
+    function getDefault() {
+        if ($this->getAttribute('checked') !== false) {
+            return $this->getAttribute('value');
         }
-        
-        /**
-         *    Can only set allowed values. Any illegal value
-         *    will result in a failure, but all correct values
-         *    will be set.
-         *    @param array $desired      New choices.
-         *    @return boolean            True if all allowed.
-         *    @access public
-         */
-        function setValue($desired) {
-            $achieved = array();
-            foreach ($desired as $value) {
-                $success = false;
-                for ($i = 0, $count = count($this->_options); $i < $count; $i++) {
-                    if ($this->_options[$i]->isValue($value)) {
-                        $achieved[] = $this->_options[$i]->getValue();
-                        $success = true;
-                        break;
-                    }
-                }
-                if (! $success) {
-                    return false;
-                }
-            }
-            $this->_values = $achieved;
-            return true;
+        return false;
+    }
+}
+
+/**
+ *    A group of multiple widgets with some shared behaviour.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleTagGroup {
+    var $_widgets = array();
+
+    /**
+     *    Adds a tag to the group.
+     *    @param SimpleWidget $widget
+     *    @access public
+     */
+    function addWidget(&$widget) {
+        $this->_widgets[] = &$widget;
+    }
+    
+    /**
+     *    Accessor to widget set.
+     *    @return array        All widgets.
+     *    @access protected
+     */
+    function &_getWidgets() {
+        return $this->_widgets;
+    }
+
+    /**
+     *    Accessor for an attribute.
+     *    @param string $label    Attribute name.
+     *    @return boolean         Always false.
+     *    @access public
+     */
+    function getAttribute($label) {
+        return false;
+    }
+    
+    /**
+     *    Fetches the name for the widget from the first
+     *    member.
+     *    @return string        Name of widget.
+     *    @access public
+     */
+    function getName() {
+        if (count($this->_widgets) > 0) {
+            return $this->_widgets[0]->getName();
         }
-        
-        /**
-         *    Accessor for current selection value.
-         *    @return array      List of currently set options.
-         *    @access public
-         */
-        function getValue() {
-            if ($this->_values === false) {
-                return $this->getDefault();
+    }
+    
+    /**
+     *    Scans the widgets for one with the appropriate
+     *    ID field.
+     *    @param string $id        ID value to try.
+     *    @return boolean          True if matched.
+     *    @access public
+     */
+    function isId($id) {
+        for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
+            if ($this->_widgets[$i]->isId($id)) {
+                return true;
             }
-            return $this->_values;
         }
+        return false;
     }
     
     /**
-     *    Option for selection field.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Scans the widgets for one with the appropriate
+     *    attached label.
+     *    @param string $label     Attached label to try.
+     *    @return boolean          True if matched.
+     *    @access public
      */
-    class SimpleOptionTag extends SimpleWidget {
-        
-        /**
-         *    Stashes the attributes.
-         */
-        function SimpleOptionTag($attributes) {
-            $this->SimpleWidget('option', $attributes);
-        }
-        
-        /**
-         *    Does nothing.
-         *    @param string $value      Ignored.
-         *    @return boolean           Not allowed.
-         *    @access public
-         */
-        function setValue($value) {
-            return false;
-        }
-        
-        /**
-         *    Test to see if a value matches the option.
-         *    @param string $compare    Value to compare with.
-         *    @return boolean           True if possible match.
-         *    @access public
-         */
-        function isValue($compare) {
-            $compare = trim($compare);
-            if (trim($this->getValue()) == $compare) {
+    function isLabel($label) {
+        for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
+            if ($this->_widgets[$i]->isLabel($label)) {
                 return true;
             }
-            return trim($this->getContent()) == $compare;
         }
-        
-        /**
-         *    Accessor for starting value. Will be set to
-         *    the option label if no value exists.
-         *    @return string        Parsed value.
-         *    @access public
-         */
-        function getDefault() {
-            if ($this->getAttribute('value') === false) {
-                return $this->getContent();
+        return false;
+    }
+    
+    /**
+     *    Dispatches the value into the form encoded packet.
+     *    @param SimpleEncoding $encoding    Form packet.
+     *    @access public
+     */
+    function write(&$encoding) {
+        $encoding->add($this->getName(), $this->getValue());
+    }
+}
+
+/**
+ *    A group of tags with the same name within a form.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleCheckboxGroup extends SimpleTagGroup {
+    
+    /**
+     *    Accessor for current selected widget or false
+     *    if none.
+     *    @return string/array     Widget values or false if none.
+     *    @access public
+     */
+    function getValue() {
+        $values = array();
+        $widgets = &$this->_getWidgets();
+        for ($i = 0, $count = count($widgets); $i < $count; $i++) {
+            if ($widgets[$i]->getValue() !== false) {
+                $values[] = $widgets[$i]->getValue();
             }
-            return $this->getAttribute('value');
-        }
-        
-        /**
-         *    The content of options is not part of the page.
-         *    @return boolean        True.
-         *    @access public
-         */
-        function isPrivateContent() {
-            return true;
         }
+        return $this->_coerceValues($values);
     }
     
     /**
-     *    Radio button.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Accessor for starting value that is active.
+     *    @return string/array      Widget values or false if none.
+     *    @access public
      */
-    class SimpleRadioButtonTag extends SimpleWidget {
-        
-        /**
-         *    Stashes the attributes.
-         *    @param array $attributes        Hash of attributes.
-         */
-        function SimpleRadioButtonTag($attributes) {
-            $this->SimpleWidget('input', $attributes);
-            if ($this->getAttribute('value') === false) {
-                $this->_setAttribute('value', 'on');
+    function getDefault() {
+        $values = array();
+        $widgets = &$this->_getWidgets();
+        for ($i = 0, $count = count($widgets); $i < $count; $i++) {
+            if ($widgets[$i]->getDefault() !== false) {
+                $values[] = $widgets[$i]->getDefault();
             }
         }
-        
-        /**
-         *    Tag contains no content.
-         *    @return boolean        False.
-         *    @access public
-         */
-        function expectEndTag() {
+        return $this->_coerceValues($values);
+    }
+    
+    /**
+     *    Accessor for current set values.
+     *    @param string/array/boolean $values   Either a single string, a
+     *                                          hash or false for nothing set.
+     *    @return boolean                       True if all values can be set.
+     *    @access public
+     */
+    function setValue($values) {
+        $values = $this->_makeArray($values);
+        if (! $this->_valuesArePossible($values)) {
             return false;
         }
-        
-        /**
-         *    The only allowed value sn the one in the
-         *    "value" attribute.
-         *    @param string $value      New value.
-         *    @return boolean           True if allowed.
-         *    @access public
-         */
-        function setValue($value) {
-            if ($value === false) {
-                return parent::setValue($value);
-            }
-            if ($value !== $this->getAttribute('value')) {
-                return false;
-            }
-            return parent::setValue($value);
-        }
-        
-        /**
-         *    Accessor for starting value.
-         *    @return string        Parsed value.
-         *    @access public
-         */
-        function getDefault() {
-            if ($this->getAttribute('checked') !== false) {
-                return $this->getAttribute('value');
+        $widgets = &$this->_getWidgets();
+        for ($i = 0, $count = count($widgets); $i < $count; $i++) {
+            $possible = $widgets[$i]->getAttribute('value');
+            if (in_array($widgets[$i]->getAttribute('value'), $values)) {
+                $widgets[$i]->setValue($possible);
+            } else {
+                $widgets[$i]->setValue(false);
             }
-            return false;
         }
+        return true;
     }
     
     /**
-     *    Checkbox widget.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Tests to see if a possible value set is legal.
+     *    @param string/array/boolean $values   Either a single string, a
+     *                                          hash or false for nothing set.
+     *    @return boolean                       False if trying to set a
+     *                                          missing value.
+     *    @access private
      */
-    class SimpleCheckboxTag extends SimpleWidget {
-        
-        /**
-         *    Starts with attributes only.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleCheckboxTag($attributes) {
-            $this->SimpleWidget('input', $attributes);
-            if ($this->getAttribute('value') === false) {
-                $this->_setAttribute('value', 'on');
-            }
-        }
-        
-        /**
-         *    Tag contains no content.
-         *    @return boolean        False.
-         *    @access public
-         */
-        function expectEndTag() {
-            return false;
-        }
-        
-        /**
-         *    The only allowed value in the one in the
-         *    "value" attribute. The default for this
-         *    attribute is "on". If this widget is set to
-         *    true, then the usual value will be taken.
-         *    @param string $value      New value.
-         *    @return boolean           True if allowed.
-         *    @access public
-         */
-        function setValue($value) {
-            if ($value === false) {
-                return parent::setValue($value);
-            }
-            if ($value === true) {
-                return parent::setValue($this->getAttribute('value'));
+    function _valuesArePossible($values) {
+        $matches = array();
+        $widgets = &$this->_getWidgets();
+        for ($i = 0, $count = count($widgets); $i < $count; $i++) {
+            $possible = $widgets[$i]->getAttribute('value');
+            if (in_array($possible, $values)) {
+                $matches[] = $possible;
             }
-            if ($value != $this->getAttribute('value')) {
-                return false;
-            }
-            return parent::setValue($value);
         }
-        
-        /**
-         *    Accessor for starting value. The default
-         *    value is "on".
-         *    @return string        Parsed value.
-         *    @access public
-         */
-        function getDefault() {
-            if ($this->getAttribute('checked') !== false) {
-                return $this->getAttribute('value');
-            }
+        return ($values == $matches);
+    }
+    
+    /**
+     *    Converts the output to an appropriate format. This means
+     *    that no values is false, a single value is just that
+     *    value and only two or more are contained in an array.
+     *    @param array $values           List of values of widgets.
+     *    @return string/array/boolean   Expected format for a tag.
+     *    @access private
+     */
+    function _coerceValues($values) {
+        if (count($values) == 0) {
             return false;
+        } elseif (count($values) == 1) {
+            return $values[0];
+        } else {
+            return $values;
         }
     }
     
     /**
-     *    A group of multiple widgets with some shared behaviour.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Converts false or string into array. The opposite of
+     *    the coercian method.
+     *    @param string/array/boolean $value  A single item is converted
+     *                                        to a one item list. False
+     *                                        gives an empty list.
+     *    @return array                       List of values, possibly empty.
+     *    @access private
      */
-    class SimpleTagGroup {
-        var $_widgets = array();
-
-        /**
-         *    Adds a tag to the group.
-         *    @param SimpleWidget $widget
-         *    @access public
-         */
-        function addWidget(&$widget) {
-            $this->_widgets[] = &$widget;
+    function _makeArray($value) {
+        if ($value === false) {
+            return array();
         }
-        
-        /**
-         *    Accessor to widget set.
-         *    @return array        All widgets.
-         *    @access protected
-         */
-        function &_getWidgets() {
-            return $this->_widgets;
+        if (is_string($value)) {
+            return array($value);
         }
+        return $value;
+    }
+}
 
-        /**
-         *    Accessor for an attribute.
-         *    @param string $label    Attribute name.
-         *    @return boolean         Always false.
-         *    @access public
-         */
-        function getAttribute($label) {
-            return false;
-        }
-        
-        /**
-         *    Fetches the name for the widget from the first
-         *    member.
-         *    @return string        Name of widget.
-         *    @access public
-         */
-        function getName() {
-            if (count($this->_widgets) > 0) {
-                return $this->_widgets[0]->getName();
-            }
-        }
-        
-        /**
-         *    Scans the widgets for one with the appropriate
-         *    ID field.
-         *    @param string $id        ID value to try.
-         *    @return boolean          True if matched.
-         *    @access public
-         */
-        function isId($id) {
-            for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
-                if ($this->_widgets[$i]->isId($id)) {
-                    return true;
-                }
-            }
+/**
+ *    A group of tags with the same name within a form.
+ *    Used for radio buttons.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleRadioGroup extends SimpleTagGroup {
+    
+    /**
+     *    Each tag is tried in turn until one is
+     *    successfully set. The others will be
+     *    unchecked if successful.
+     *    @param string $value      New value.
+     *    @return boolean           True if any allowed.
+     *    @access public
+     */
+    function setValue($value) {
+        if (! $this->_valueIsPossible($value)) {
             return false;
         }
-        
-        /**
-         *    Scans the widgets for one with the appropriate
-         *    attached label.
-         *    @param string $label     Attached label to try.
-         *    @return boolean          True if matched.
-         *    @access public
-         */
-        function isLabel($label) {
-            for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) {
-                if ($this->_widgets[$i]->isLabel($label)) {
-                    return true;
-                }
+        $index = false;
+        $widgets = &$this->_getWidgets();
+        for ($i = 0, $count = count($widgets); $i < $count; $i++) {
+            if (! $widgets[$i]->setValue($value)) {
+                $widgets[$i]->setValue(false);
             }
-            return false;
-        }
-        
-        /**
-         *    Dispatches the value into the form encoded packet.
-         *    @param SimpleEncoding $encoding    Form packet.
-         *    @access public
-         */
-        function write(&$encoding) {
-            $encoding->add($this->getName(), $this->getValue());
         }
+        return true;
     }
-
+    
     /**
-     *    A group of tags with the same name within a form.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleCheckboxGroup extends SimpleTagGroup {
-        
-        /**
-         *    Accessor for current selected widget or false
-         *    if none.
-         *    @return string/array     Widget values or false if none.
-         *    @access public
-         */
-        function getValue() {
-            $values = array();
-            $widgets = &$this->_getWidgets();
-            for ($i = 0, $count = count($widgets); $i < $count; $i++) {
-                if ($widgets[$i]->getValue() !== false) {
-                    $values[] = $widgets[$i]->getValue();
-                }
-            }
-            return $this->_coerceValues($values);
-        }
-        
-        /**
-         *    Accessor for starting value that is active.
-         *    @return string/array      Widget values or false if none.
-         *    @access public
-         */
-        function getDefault() {
-            $values = array();
-            $widgets = &$this->_getWidgets();
-            for ($i = 0, $count = count($widgets); $i < $count; $i++) {
-                if ($widgets[$i]->getDefault() !== false) {
-                    $values[] = $widgets[$i]->getDefault();
-                }
-            }
-            return $this->_coerceValues($values);
-        }
-        
-        /**
-         *    Accessor for current set values.
-         *    @param string/array/boolean $values   Either a single string, a
-         *                                          hash or false for nothing set.
-         *    @return boolean                       True if all values can be set.
-         *    @access public
-         */
-        function setValue($values) {
-            $values = $this->_makeArray($values);
-            if (! $this->_valuesArePossible($values)) {
-                return false;
-            }
-            $widgets = &$this->_getWidgets();
-            for ($i = 0, $count = count($widgets); $i < $count; $i++) {
-                $possible = $widgets[$i]->getAttribute('value');
-                if (in_array($widgets[$i]->getAttribute('value'), $values)) {
-                    $widgets[$i]->setValue($possible);
-                } else {
-                    $widgets[$i]->setValue(false);
-                }
-            }
-            return true;
-        }
-        
-        /**
-         *    Tests to see if a possible value set is legal.
-         *    @param string/array/boolean $values   Either a single string, a
-         *                                          hash or false for nothing set.
-         *    @return boolean                       False if trying to set a
-         *                                          missing value.
-         *    @access private
-         */
-        function _valuesArePossible($values) {
-            $matches = array();
-            $widgets = &$this->_getWidgets();
-            for ($i = 0, $count = count($widgets); $i < $count; $i++) {
-                $possible = $widgets[$i]->getAttribute('value');
-                if (in_array($possible, $values)) {
-                    $matches[] = $possible;
-                }
+     *    Tests to see if a value is allowed.
+     *    @param string    Attempted value.
+     *    @return boolean  True if a valid value.
+     *    @access private
+     */
+    function _valueIsPossible($value) {
+        $widgets = &$this->_getWidgets();
+        for ($i = 0, $count = count($widgets); $i < $count; $i++) {
+            if ($widgets[$i]->getAttribute('value') == $value) {
+                return true;
             }
-            return ($values == $matches);
         }
-        
-        /**
-         *    Converts the output to an appropriate format. This means
-         *    that no values is false, a single value is just that
-         *    value and only two or more are contained in an array.
-         *    @param array $values           List of values of widgets.
-         *    @return string/array/boolean   Expected format for a tag.
-         *    @access private
-         */
-        function _coerceValues($values) {
-            if (count($values) == 0) {
-                return false;
-            } elseif (count($values) == 1) {
-                return $values[0];
-            } else {
-                return $values;
+        return false;
+    }
+    
+    /**
+     *    Accessor for current selected widget or false
+     *    if none.
+     *    @return string/boolean   Value attribute or
+     *                             content of opton.
+     *    @access public
+     */
+    function getValue() {
+        $widgets = &$this->_getWidgets();
+        for ($i = 0, $count = count($widgets); $i < $count; $i++) {
+            if ($widgets[$i]->getValue() !== false) {
+                return $widgets[$i]->getValue();
             }
         }
-        
-        /**
-         *    Converts false or string into array. The opposite of
-         *    the coercian method.
-         *    @param string/array/boolean $value  A single item is converted
-         *                                        to a one item list. False
-         *                                        gives an empty list.
-         *    @return array                       List of values, possibly empty.
-         *    @access private
-         */
-        function _makeArray($value) {
-            if ($value === false) {
-                return array();
-            }
-            if (is_string($value)) {
-                return array($value);
+        return false;
+    }
+    
+    /**
+     *    Accessor for starting value that is active.
+     *    @return string/boolean      Value of first checked
+     *                                widget or false if none.
+     *    @access public
+     */
+    function getDefault() {
+        $widgets = &$this->_getWidgets();
+        for ($i = 0, $count = count($widgets); $i < $count; $i++) {
+            if ($widgets[$i]->getDefault() !== false) {
+                return $widgets[$i]->getDefault();
             }
-            return $value;
         }
+        return false;
     }
+}
 
+/**
+ *    Tag to keep track of labels.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleLabelTag extends SimpleTag {
+    
     /**
-     *    A group of tags with the same name within a form.
-     *    Used for radio buttons.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleRadioGroup extends SimpleTagGroup {
-        
-        /**
-         *    Each tag is tried in turn until one is
-         *    successfully set. The others will be
-         *    unchecked if successful.
-         *    @param string $value      New value.
-         *    @return boolean           True if any allowed.
-         *    @access public
-         */
-        function setValue($value) {
-            if (! $this->_valueIsPossible($value)) {
-                return false;
-            }
-            $index = false;
-            $widgets = &$this->_getWidgets();
-            for ($i = 0, $count = count($widgets); $i < $count; $i++) {
-                if (! $widgets[$i]->setValue($value)) {
-                    $widgets[$i]->setValue(false);
-                }
-            }
-            return true;
-        }
-        
-        /**
-         *    Tests to see if a value is allowed.
-         *    @param string    Attempted value.
-         *    @return boolean  True if a valid value.
-         *    @access private
-         */
-        function _valueIsPossible($value) {
-            $widgets = &$this->_getWidgets();
-            for ($i = 0, $count = count($widgets); $i < $count; $i++) {
-                if ($widgets[$i]->getAttribute('value') == $value) {
-                    return true;
-                }
-            }
-            return false;
-        }
-        
-        /**
-         *    Accessor for current selected widget or false
-         *    if none.
-         *    @return string/boolean   Value attribute or
-         *                             content of opton.
-         *    @access public
-         */
-        function getValue() {
-            $widgets = &$this->_getWidgets();
-            for ($i = 0, $count = count($widgets); $i < $count; $i++) {
-                if ($widgets[$i]->getValue() !== false) {
-                    return $widgets[$i]->getValue();
-                }
-            }
-            return false;
-        }
-        
-        /**
-         *    Accessor for starting value that is active.
-         *    @return string/boolean      Value of first checked
-         *                                widget or false if none.
-         *    @access public
-         */
-        function getDefault() {
-            $widgets = &$this->_getWidgets();
-            for ($i = 0, $count = count($widgets); $i < $count; $i++) {
-                if ($widgets[$i]->getDefault() !== false) {
-                    return $widgets[$i]->getDefault();
-                }
-            }
-            return false;
-        }
+     *    Starts with a named tag with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
+     */
+    function SimpleLabelTag($attributes) {
+        $this->SimpleTag('label', $attributes);
     }
     
     /**
-     *    Tag to keep track of labels.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Access for the ID to attach the label to.
+     *    @return string        For attribute.
+     *    @access public
      */
-    class SimpleLabelTag extends SimpleTag {
-        
-        /**
-         *    Starts with a named tag with attributes only.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleLabelTag($attributes) {
-            $this->SimpleTag('label', $attributes);
-        }
-        
-        /**
-         *    Access for the ID to attach the label to.
-         *    @return string        For attribute.
-         *    @access public
-         */
-        function getFor() {
-            return $this->getAttribute('for');
-        }
+    function getFor() {
+        return $this->getAttribute('for');
     }
+}
+
+/**
+ *    Tag to aid parsing the form.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleFormTag extends SimpleTag {
     
     /**
-     *    Tag to aid parsing the form.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Starts with a named tag with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
      */
-    class SimpleFormTag extends SimpleTag {
-        
-        /**
-         *    Starts with a named tag with attributes only.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleFormTag($attributes) {
-            $this->SimpleTag('form', $attributes);
-        }
+    function SimpleFormTag($attributes) {
+        $this->SimpleTag('form', $attributes);
     }
+}
+
+/**
+ *    Tag to aid parsing the frames in a page.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleFrameTag extends SimpleTag {
     
     /**
-     *    Tag to aid parsing the frames in a page.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Starts with a named tag with attributes only.
+     *    @param hash $attributes    Attribute names and
+     *                               string values.
      */
-    class SimpleFrameTag extends SimpleTag {
-        
-        /**
-         *    Starts with a named tag with attributes only.
-         *    @param hash $attributes    Attribute names and
-         *                               string values.
-         */
-        function SimpleFrameTag($attributes) {
-            $this->SimpleTag('frame', $attributes);
-        }
-        
-        /**
-         *    Tag contains no content.
-         *    @return boolean        False.
-         *    @access public
-         */
-        function expectEndTag() {
-            return false;
-        }
+    function SimpleFrameTag($attributes) {
+        $this->SimpleTag('frame', $attributes);
+    }
+    
+    /**
+     *    Tag contains no content.
+     *    @return boolean        False.
+     *    @access public
+     */
+    function expectEndTag() {
+        return false;
     }
+}
 ?>
\ No newline at end of file
index 91835a31096f263d33dcb82d5fdfc7845e167f42..ae27c8b255811a48797307e459b939e77e2ae11f 100644 (file)
 <?php
+/**
+ *  Base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ * Includes SimpleTest files and defined the root constant
+ * for dependent libraries.
+ */
+require_once(dirname(__FILE__) . '/invoker.php');
+require_once(dirname(__FILE__) . '/errors.php');
+require_once(dirname(__FILE__) . '/compatibility.php');
+require_once(dirname(__FILE__) . '/scorer.php');
+require_once(dirname(__FILE__) . '/expectation.php');
+require_once(dirname(__FILE__) . '/dumper.php');
+require_once(dirname(__FILE__) . '/simpletest.php');
+if (version_compare(phpversion(), '5') >= 0) {
+    require_once(dirname(__FILE__) . '/exceptions.php');
+    require_once(dirname(__FILE__) . '/reflection_php5.php');
+} else {
+    require_once(dirname(__FILE__) . '/reflection_php4.php');
+}
+if (! defined('SIMPLE_TEST')) {
     /**
-     * Base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
-     */
-
-    /**#@+
-     * Includes SimpleTest files and defined the root constant
-     * for dependent libraries.
-     */
-    require_once(dirname(__FILE__) . '/invoker.php');
-    require_once(dirname(__FILE__) . '/errors.php');
-    require_once(dirname(__FILE__) . '/compatibility.php');
-    require_once(dirname(__FILE__) . '/scorer.php');
-    require_once(dirname(__FILE__) . '/expectation.php');
-    require_once(dirname(__FILE__) . '/dumper.php');
-    require_once(dirname(__FILE__) . '/simpletest.php');
-    if (version_compare(phpversion(), '5') >= 0) {
-        require_once(dirname(__FILE__) . '/exceptions.php');
-        require_once(dirname(__FILE__) . '/reflection_php5.php');
-    } else {
-        require_once(dirname(__FILE__) . '/reflection_php4.php');
-    }
-    if (! defined('SIMPLE_TEST')) {
-        /** @ignore */
-        define('SIMPLE_TEST', dirname(__FILE__) . DIRECTORY_SEPARATOR);
-    }
-    /**#@-*/
-
-    /**
-     *    Basic test case. This is the smallest unit of a test
-     *    suite. It searches for
-     *    all methods that start with the the string "test" and
-     *    runs them. Working test cases extend this class.
-        *    @package          SimpleTest
-        *    @subpackage       UnitTester
-     */
-    class SimpleTestCase {
-        var $_label = false;
-        var $_reporter;
-        var $_observers;
-               var $_should_skip = false;
-
-        /**
-         *    Sets up the test with no display.
-         *    @param string $label    If no test name is given then
-         *                            the class name is used.
-         *    @access public
-         */
-        function SimpleTestCase($label = false) {
-            if ($label) {
-                $this->_label = $label;
-            }
-        }
+     * @ignore
+     */
+    define('SIMPLE_TEST', dirname(__FILE__) . DIRECTORY_SEPARATOR);
+}
+/**#@-*/
+
+/**
+ *    Basic test case. This is the smallest unit of a test
+ *    suite. It searches for
+ *    all methods that start with the the string "test" and
+ *    runs them. Working test cases extend this class.
+ *    @package      SimpleTest
+ *    @subpackage   UnitTester
+ */
+class SimpleTestCase {
+    var $_label = false;
+    var $_reporter;
+    var $_observers;
+    var $_should_skip = false;
 
-        /**
-         *    Accessor for the test name for subclasses.
-         *    @return string           Name of the test.
-         *    @access public
-         */
-        function getLabel() {
-            return $this->_label ? $this->_label : get_class($this);
+    /**
+     *    Sets up the test with no display.
+     *    @param string $label    If no test name is given then
+     *                            the class name is used.
+     *    @access public
+     */
+    function SimpleTestCase($label = false) {
+        if ($label) {
+            $this->_label = $label;
         }
+    }
 
-        /**
-         *    This is a placeholder for skipping tests. In this
-         *    method you place skipIf() and skipUnless() calls to
-         *    set the skipping state.
-         *    @access public
-         */
-        function skip() {
-        }
-               
-        /**
-         *    Will issue a message to the reporter and tell the test
-         *    case to skip if the incoming flag is true.
-         *    @param string $should_skip    Condition causing the tests to be skipped.
-         *    @param string $message           Text of skip condition.
-         *    @access public
-         */
-        function skipIf($should_skip, $message = '%s') {
-                       if ($should_skip && ! $this->_should_skip) {
-                               $this->_should_skip = true;
-                               $message = sprintf($message, 'Skipping [' . get_class($this) . ']');
-                               $this->_reporter->paintSkip($message . $this->getAssertionLine());
-                       }
-        }
-               
-        /**
-         *    Will issue a message to the reporter and tell the test
-         *    case to skip if the incoming flag is false.
-         *    @param string $shouldnt_skip  Condition causing the tests to be run.
-         *    @param string $message           Text of skip condition.
-         *    @access public
-         */
-        function skipUnless($shouldnt_skip, $message = false) {
-                       $this->skipIf(! $shouldnt_skip, $message);
+    /**
+     *    Accessor for the test name for subclasses.
+     *    @return string           Name of the test.
+     *    @access public
+     */
+    function getLabel() {
+        return $this->_label ? $this->_label : get_class($this);
+    }
+
+    /**
+     *    This is a placeholder for skipping tests. In this
+     *    method you place skipIf() and skipUnless() calls to
+     *    set the skipping state.
+     *    @access public
+     */
+    function skip() {
+    }
+
+    /**
+     *    Will issue a message to the reporter and tell the test
+     *    case to skip if the incoming flag is true.
+     *    @param string $should_skip    Condition causing the tests to be skipped.
+     *    @param string $message        Text of skip condition.
+     *    @access public
+     */
+    function skipIf($should_skip, $message = '%s') {
+        if ($should_skip && ! $this->_should_skip) {
+            $this->_should_skip = true;
+            $message = sprintf($message, 'Skipping [' . get_class($this) . ']');
+            $this->_reporter->paintSkip($message . $this->getAssertionLine());
         }
-               
-        /**
-         *    Used to invoke the single tests.
-         *    @return SimpleInvoker        Individual test runner.
-         *    @access public
-         */
-        function &createInvoker() {
-            $invoker = &new SimpleErrorTrappingInvoker(new SimpleInvoker($this));
-            if (version_compare(phpversion(), '5') >= 0) {
-                $invoker = &new SimpleExceptionTrappingInvoker($invoker);
-            }
-            return $invoker;
+    }
+
+    /**
+     *    Will issue a message to the reporter and tell the test
+     *    case to skip if the incoming flag is false.
+     *    @param string $shouldnt_skip  Condition causing the tests to be run.
+     *    @param string $message        Text of skip condition.
+     *    @access public
+     */
+    function skipUnless($shouldnt_skip, $message = false) {
+        $this->skipIf(! $shouldnt_skip, $message);
+    }
+
+    /**
+     *    Used to invoke the single tests.
+     *    @return SimpleInvoker        Individual test runner.
+     *    @access public
+     */
+    function &createInvoker() {
+        $invoker = &new SimpleErrorTrappingInvoker(new SimpleInvoker($this));
+        if (version_compare(phpversion(), '5') >= 0) {
+            $invoker = &new SimpleExceptionTrappingInvoker($invoker);
         }
+        return $invoker;
+    }
 
-        /**
-         *    Uses reflection to run every method within itself
-         *    starting with the string "test" unless a method
-         *    is specified.
-         *    @param SimpleReporter $reporter    Current test reporter.
-         *    @return boolean                    True if all tests passed.
-         *    @access public
-         */
-        function run(&$reporter) {
-                       $context = &SimpleTest::getContext();
-                       $context->setTest($this);
-                       $context->setReporter($reporter);
-            $this->_reporter = &$reporter;
-            $reporter->paintCaseStart($this->getLabel());
-                       $this->skip();
-            if (! $this->_should_skip) {
-                foreach ($this->getTests() as $method) {
-                    if ($reporter->shouldInvoke($this->getLabel(), $method)) {
-                        $invoker = &$this->_reporter->createInvoker($this->createInvoker());
-                        $invoker->before($method);
-                        $invoker->invoke($method);
-                        $invoker->after($method);
-                    }
+    /**
+     *    Uses reflection to run every method within itself
+     *    starting with the string "test" unless a method
+     *    is specified.
+     *    @param SimpleReporter $reporter    Current test reporter.
+     *    @return boolean                    True if all tests passed.
+     *    @access public
+     */
+    function run(&$reporter) {
+        $context = &SimpleTest::getContext();
+        $context->setTest($this);
+        $context->setReporter($reporter);
+        $this->_reporter = &$reporter;
+        $started = false;
+        foreach ($this->getTests() as $method) {
+            if ($reporter->shouldInvoke($this->getLabel(), $method)) {
+                $this->skip();
+                if ($this->_should_skip) {
+                    break;
                 }
+                if (! $started) {
+                    $reporter->paintCaseStart($this->getLabel());
+                    $started = true;
+                }
+                $invoker = &$this->_reporter->createInvoker($this->createInvoker());
+                $invoker->before($method);
+                $invoker->invoke($method);
+                $invoker->after($method);
             }
+        }
+        if ($started) {
             $reporter->paintCaseEnd($this->getLabel());
-            unset($this->_reporter);
-            return $reporter->getStatus();
         }
+        unset($this->_reporter);
+        return $reporter->getStatus();
+    }
 
-        /**
-         *    Gets a list of test names. Normally that will
-         *    be all internal methods that start with the
-         *    name "test". This method should be overridden
-         *    if you want a different rule.
-         *    @return array        List of test names.
-         *    @access public
-         */
-        function getTests() {
-            $methods = array();
-            foreach (get_class_methods(get_class($this)) as $method) {
-                if ($this->_isTest($method)) {
-                    $methods[] = $method;
-                }
+    /**
+     *    Gets a list of test names. Normally that will
+     *    be all internal methods that start with the
+     *    name "test". This method should be overridden
+     *    if you want a different rule.
+     *    @return array        List of test names.
+     *    @access public
+     */
+    function getTests() {
+        $methods = array();
+        foreach (get_class_methods(get_class($this)) as $method) {
+            if ($this->_isTest($method)) {
+                $methods[] = $method;
             }
-            return $methods;
         }
+        return $methods;
+    }
 
-        /**
-         *    Tests to see if the method is a test that should
-         *    be run. Currently any method that starts with 'test'
-         *    is a candidate unless it is the constructor.
-         *    @param string $method        Method name to try.
-         *    @return boolean              True if test method.
-         *    @access protected
-         */
-        function _isTest($method) {
-            if (strtolower(substr($method, 0, 4)) == 'test') {
-                return ! SimpleTestCompatibility::isA($this, strtolower($method));
-            }
-            return false;
+    /**
+     *    Tests to see if the method is a test that should
+     *    be run. Currently any method that starts with 'test'
+     *    is a candidate unless it is the constructor.
+     *    @param string $method        Method name to try.
+     *    @return boolean              True if test method.
+     *    @access protected
+     */
+    function _isTest($method) {
+        if (strtolower(substr($method, 0, 4)) == 'test') {
+            return ! SimpleTestCompatibility::isA($this, strtolower($method));
         }
+        return false;
+    }
 
-        /**
-         *    Announces the start of the test.
-         *    @param string $method    Test method just started.
-         *    @access public
-         */
-        function before($method) {
-            $this->_reporter->paintMethodStart($method);
-            $this->_observers = array();
-        }
+    /**
+     *    Announces the start of the test.
+     *    @param string $method    Test method just started.
+     *    @access public
+     */
+    function before($method) {
+        $this->_reporter->paintMethodStart($method);
+        $this->_observers = array();
+    }
 
-        /**
-         *    Sets up unit test wide variables at the start
-         *    of each test method. To be overridden in
-         *    actual user test cases.
-         *    @access public
-         */
-        function setUp() {
-        }
+    /**
+     *    Sets up unit test wide variables at the start
+     *    of each test method. To be overridden in
+     *    actual user test cases.
+     *    @access public
+     */
+    function setUp() {
+    }
 
-        /**
-         *    Clears the data set in the setUp() method call.
-         *    To be overridden by the user in actual user test cases.
-         *    @access public
-         */
-        function tearDown() {
-        }
+    /**
+     *    Clears the data set in the setUp() method call.
+     *    To be overridden by the user in actual user test cases.
+     *    @access public
+     */
+    function tearDown() {
+    }
 
-        /**
-         *    Announces the end of the test. Includes private clean up.
-         *    @param string $method    Test method just finished.
-         *    @access public
-         */
-        function after($method) {
-            for ($i = 0; $i < count($this->_observers); $i++) {
-                $this->_observers[$i]->atTestEnd($method, $this);
-            }
-            $this->_reporter->paintMethodEnd($method);
+    /**
+     *    Announces the end of the test. Includes private clean up.
+     *    @param string $method    Test method just finished.
+     *    @access public
+     */
+    function after($method) {
+        for ($i = 0; $i < count($this->_observers); $i++) {
+            $this->_observers[$i]->atTestEnd($method, $this);
         }
+        $this->_reporter->paintMethodEnd($method);
+    }
 
-        /**
-         *    Sets up an observer for the test end.
-         *    @param object $observer    Must have atTestEnd()
-         *                               method.
-         *    @access public
-         */
-        function tell(&$observer) {
-            $this->_observers[] = &$observer;
-        }
+    /**
+     *    Sets up an observer for the test end.
+     *    @param object $observer    Must have atTestEnd()
+     *                               method.
+     *    @access public
+     */
+    function tell(&$observer) {
+        $this->_observers[] = &$observer;
+    }
 
-        /**
-         *    @deprecated
-         */
-        function pass($message = "Pass") {
-            if (! isset($this->_reporter)) {
-                trigger_error('Can only make assertions within test methods');
-            }
-            $this->_reporter->paintPass(
-                    $message . $this->getAssertionLine());
-            return true;
+    /**
+     *    @deprecated
+     */
+    function pass($message = "Pass") {
+        if (! isset($this->_reporter)) {
+            trigger_error('Can only make assertions within test methods');
         }
+        $this->_reporter->paintPass(
+                $message . $this->getAssertionLine());
+        return true;
+    }
 
-        /**
-         *    Sends a fail event with a message.
-         *    @param string $message        Message to send.
-         *    @access public
-         */
-        function fail($message = "Fail") {
-            if (! isset($this->_reporter)) {
-                trigger_error('Can only make assertions within test methods');
-            }
-            $this->_reporter->paintFail(
-                    $message . $this->getAssertionLine());
-            return false;
+    /**
+     *    Sends a fail event with a message.
+     *    @param string $message        Message to send.
+     *    @access public
+     */
+    function fail($message = "Fail") {
+        if (! isset($this->_reporter)) {
+            trigger_error('Can only make assertions within test methods');
         }
+        $this->_reporter->paintFail(
+                $message . $this->getAssertionLine());
+        return false;
+    }
 
-        /**
-         *    Formats a PHP error and dispatches it to the
-         *    reporter.
-         *    @param integer $severity  PHP error code.
-         *    @param string $message    Text of error.
-         *    @param string $file       File error occoured in.
-         *    @param integer $line      Line number of error.
-         *    @access public
-         */
-        function error($severity, $message, $file, $line) {
-            if (! isset($this->_reporter)) {
-                trigger_error('Can only make assertions within test methods');
-            }
-            $this->_reporter->paintError(
-                    "Unexpected PHP error [$message] severity [$severity] in [$file line $line]");
+    /**
+     *    Formats a PHP error and dispatches it to the
+     *    reporter.
+     *    @param integer $severity  PHP error code.
+     *    @param string $message    Text of error.
+     *    @param string $file       File error occoured in.
+     *    @param integer $line      Line number of error.
+     *    @access public
+     */
+    function error($severity, $message, $file, $line) {
+        if (! isset($this->_reporter)) {
+            trigger_error('Can only make assertions within test methods');
         }
+        $this->_reporter->paintError(
+                "Unexpected PHP error [$message] severity [$severity] in [$file line $line]");
+    }
 
-        /**
-         *    Formats an exception and dispatches it to the
-         *    reporter.
-         *    @param Exception $exception    Object thrown.
-         *    @access public
-         */
-        function exception($exception) {
-            $this->_reporter->paintException($exception);
-        }
+    /**
+     *    Formats an exception and dispatches it to the
+     *    reporter.
+     *    @param Exception $exception    Object thrown.
+     *    @access public
+     */
+    function exception($exception) {
+        $this->_reporter->paintException($exception);
+    }
 
-        /**
-         *    @deprecated
-         */
-        function signal($type, &$payload) {
-            if (! isset($this->_reporter)) {
-                trigger_error('Can only make assertions within test methods');
-            }
-            $this->_reporter->paintSignal($type, $payload);
+    /**
+     *    @deprecated
+     */
+    function signal($type, &$payload) {
+        if (! isset($this->_reporter)) {
+            trigger_error('Can only make assertions within test methods');
         }
+        $this->_reporter->paintSignal($type, $payload);
+    }
 
-        /**
-         *    Runs an expectation directly, for extending the
-         *    tests with new expectation classes.
-         *    @param SimpleExpectation $expectation  Expectation subclass.
-         *    @param mixed $compare               Value to compare.
-         *    @param string $message                 Message to display.
-         *    @return boolean                        True on pass
-         *    @access public
-         */
-        function assert(&$expectation, $compare, $message = '%s') {
-            if ($expectation->test($compare)) {
-                return $this->pass(sprintf(
-                        $message,
-                        $expectation->overlayMessage($compare, $this->_reporter->getDumper())));
-            } else {
-                return $this->fail(sprintf(
-                        $message,
-                        $expectation->overlayMessage($compare, $this->_reporter->getDumper())));
-            }
+    /**
+     *    Runs an expectation directly, for extending the
+     *    tests with new expectation classes.
+     *    @param SimpleExpectation $expectation  Expectation subclass.
+     *    @param mixed $compare               Value to compare.
+     *    @param string $message                 Message to display.
+     *    @return boolean                        True on pass
+     *    @access public
+     */
+    function assert(&$expectation, $compare, $message = '%s') {
+        if ($expectation->test($compare)) {
+            return $this->pass(sprintf(
+                    $message,
+                    $expectation->overlayMessage($compare, $this->_reporter->getDumper())));
+        } else {
+            return $this->fail(sprintf(
+                    $message,
+                    $expectation->overlayMessage($compare, $this->_reporter->getDumper())));
         }
+    }
 
-        /**
-         *       @deprecated
-         */
-        function assertExpectation(&$expectation, $compare, $message = '%s') {
-               return $this->assert($expectation, $compare, $message);
-        }
+    /**
+     *    @deprecated
+     */
+    function assertExpectation(&$expectation, $compare, $message = '%s') {
+        return $this->assert($expectation, $compare, $message);
+    }
 
-        /**
-         *    Uses a stack trace to find the line of an assertion.
-         *    @return string           Line number of first assert*
-         *                             method embedded in format string.
-         *    @access public
-         */
-        function getAssertionLine() {
-            $trace = new SimpleStackTrace(array('assert', 'expect', 'pass', 'fail', 'skip'));
-            return $trace->traceMethod();
-        }
+    /**
+     *    Uses a stack trace to find the line of an assertion.
+     *    @return string           Line number of first assert*
+     *                             method embedded in format string.
+     *    @access public
+     */
+    function getAssertionLine() {
+        $trace = new SimpleStackTrace(array('assert', 'expect', 'pass', 'fail', 'skip'));
+        return $trace->traceMethod();
+    }
 
-        /**
-         *    Sends a formatted dump of a variable to the
-         *    test suite for those emergency debugging
-         *    situations.
-         *    @param mixed $variable    Variable to display.
-         *    @param string $message    Message to display.
-         *    @return mixed             The original variable.
-         *    @access public
-         */
-        function dump($variable, $message = false) {
-            $dumper = $this->_reporter->getDumper();
-            $formatted = $dumper->dump($variable);
-            if ($message) {
-                $formatted = $message . "\n" . $formatted;
-            }
-            $this->_reporter->paintFormattedMessage($formatted);
-            return $variable;
-        }
+    /**
+     *    Sends a formatted dump of a variable to the
+     *    test suite for those emergency debugging
+     *    situations.
+     *    @param mixed $variable    Variable to display.
+     *    @param string $message    Message to display.
+     *    @return mixed             The original variable.
+     *    @access public
+     */
+    function dump($variable, $message = false) {
+        $dumper = $this->_reporter->getDumper();
+        $formatted = $dumper->dump($variable);
+        if ($message) {
+            $formatted = $message . "\n" . $formatted;
+        }
+        $this->_reporter->paintFormattedMessage($formatted);
+        return $variable;
+    }
 
-        /**
-         *    @deprecated
-         */
-        function sendMessage($message) {
-            $this->_reporter->PaintMessage($message);
-        }
+    /**
+     *    @deprecated
+     */
+    function sendMessage($message) {
+        $this->_reporter->PaintMessage($message);
+    }
 
-        /**
-         *    Accessor for the number of subtests.
-         *    @return integer           Number of test cases.
-         *    @access public
-         *    @static
-         */
-        function getSize() {
-            return 1;
-        }
+    /**
+     *    Accessor for the number of subtests including myelf.
+     *    @return integer           Number of test cases.
+     *    @access public
+     *    @static
+     */
+    function getSize() {
+        return 1;
     }
+}
+
+/**
+ *  Helps to extract test cases automatically from a file.
+ */
+class SimpleFileLoader {
 
     /**
-     *    This is a composite test class for combining
-     *    test cases and other RunnableTest classes into
-     *    a group test.
-        *    @package          SimpleTest
-        *    @subpackage       UnitTester
-     */
-    class TestSuite {
-        var $_label;
-        var $_test_cases;
-        var $_old_track_errors;
-        var $_xdebug_is_enabled;
-
-        /**
-         *    Sets the name of the test suite.
-         *    @param string $label    Name sent at the start and end
-         *                            of the test.
-         *    @access public
-         */
-        function TestSuite($label = false) {
-            $this->_label = $label ? $label : get_class($this);
-            $this->_test_cases = array();
-            $this->_old_track_errors = ini_get('track_errors');
-            $this->_xdebug_is_enabled = function_exists('xdebug_is_enabled') ?
-                    xdebug_is_enabled() : false;
-        }
+     *    Builds a test suite from a library of test cases.
+     *    The new suite is composed into this one.
+     *    @param string $test_file        File name of library with
+     *                                    test case classes.
+     *    @return TestSuite               The new test suite.
+     *    @access public
+     */
+    function &load($test_file) {
+        $existing_classes = get_declared_classes();
+        $existing_globals = get_defined_vars();
+        global $CFG;
+        include_once($test_file);
+        $new_globals = get_defined_vars();
+        $this->_makeFileVariablesGlobal($existing_globals, $new_globals);
+        $new_classes = array_diff(get_declared_classes(), $existing_classes);
+        if (empty($new_classes)) {
+            $new_classes = $this->_scrapeClassesFromFile($test_file);
+        }
+        $classes = $this->selectRunnableTests($new_classes);
+        $suite = &$this->createSuiteFromClasses($test_file, $classes);
+        return $suite;
+    }
 
-        /**
-         *    Accessor for the test name for subclasses.
-         *    @return string           Name of the test.
-         *    @access public
-         */
-        function getLabel() {
-            return $this->_label;
+    /**
+     *    Imports new variables into the global namespace.
+     *    @param hash $existing   Variables before the file was loaded.
+     *    @param hash $new        Variables after the file was loaded.
+     *    @access private
+     */
+    function _makeFileVariablesGlobal($existing, $new) {
+        $globals = array_diff(array_keys($new), array_keys($existing));
+        foreach ($globals as $global) {
+            $_GLOBALS[$global] = $new[$global];
         }
+    }
 
-        /**
-         *    Adds a test into the suite. Can be either a group
-         *    test or some other unit test.
-         *    @param SimpleTestCase $test_case  Suite or individual test
-         *                                      case implementing the
-         *                                      runnable test interface.
-         *    @access public
-         */
-        function addTestCase(&$test_case) {
-            $this->_test_cases[] = &$test_case;
-        }
+    /**
+     *    Lookup classnames from file contents, in case the
+     *    file may have been included before.
+     *    Note: This is probably too clever by half. Figuring this
+     *    out after a failed test case is going to be tricky for us,
+     *    never mind the user. A test case should not be included
+     *    twice anyway.
+     *    @param string $test_file        File name with classes.
+     *    @access private
+     */
+    function _scrapeClassesFromFile($test_file) {
+        preg_match_all('~^\s*class\s+(\w+)(\s+(extends|implements)\s+\w+)*\s*\{~mi',
+                        file_get_contents($test_file),
+                        $matches );
+        return $matches[1];
+    }
 
-        /**
-         *    Adds a test into the suite by class name. The class will
-         *    be instantiated as needed.
-         *    @param SimpleTestCase $test_case  Suite or individual test
-         *                                      case implementing the
-         *                                      runnable test interface.
-         *    @access public
-         */
-        function addTestClass($class) {
-            if ($this->_getBaseTestCase($class) == 'testsuite' || $this->_getBaseTestCase($class) == 'grouptest') {
-                $this->_test_cases[] = &new $class();
-            } else {
-                $this->_test_cases[] = $class;
+    /**
+     *    Calculates the incoming test cases. Skips abstract
+     *    and ignored classes.
+     *    @param array $candidates   Candidate classes.
+     *    @return array              New classes which are test
+     *                               cases that shouldn't be ignored.
+     *    @access public
+     */
+    function selectRunnableTests($candidates) {
+        $classes = array();
+        foreach ($candidates as $class) {
+            if (TestSuite::getBaseTestCase($class)) {
+                $reflection = new SimpleReflection($class);
+                if ($reflection->isAbstract()) {
+                    SimpleTest::ignore($class);
+                } else {
+                    $classes[] = $class;
+                }
             }
         }
+        return $classes;
+    }
 
-        /**
-         *    Builds a group test from a library of test cases.
-         *    The new group is composed into this one.
-         *    @param string $test_file        File name of library with
-         *                                    test case classes.
-         *    @access public
-         */
-        function addTestFile($test_file) {
-            $existing_classes = get_declared_classes();
-            if ($error = $this->_requireWithError($test_file)) {
-                $this->addTestCase(new BadTestSuite($test_file, $error));
-                return;
-            }
-            $classes = $this->_selectRunnableTests($existing_classes, get_declared_classes());
-            if (count($classes) == 0) {
-                $this->addTestCase(new BadTestSuite($test_file, "No runnable test cases in [$test_file]"));
-                return;
+    /**
+     *    Builds a test suite from a class list.
+     *    @param string $title       Title of new group.
+     *    @param array $classes      Test classes.
+     *    @return TestSuite          Group loaded with the new
+     *                               test cases.
+     *    @access public
+     */
+    function &createSuiteFromClasses($title, $classes) {
+        if (count($classes) == 0) {
+            $suite = &new BadTestSuite($title, "No runnable test cases in [$title]");
+            return $suite;
+        }
+        SimpleTest::ignoreParentsIfIgnored($classes);
+        $suite = &new TestSuite($title);
+        foreach ($classes as $class) {
+            if (! SimpleTest::isIgnored($class)) {
+                $suite->addTestClass($class);
             }
-            $group = &$this->_createGroupFromClasses($test_file, $classes);
-            $this->addTestCase($group);
         }
+        return $suite;
+    }
+}
+
+/**
+ *    This is a composite test class for combining
+ *    test cases and other RunnableTest classes into
+ *    a group test.
+ *    @package      SimpleTest
+ *    @subpackage   UnitTester
+ */
+class TestSuite {
+    var $_label;
+    var $_test_cases;
 
-        /**
-         *    Requires a source file recording any syntax errors.
-         *    @param string $file        File name to require in.
-         *    @return string/boolean     An error message on failure or false
-         *                               if no errors.
-         *    @access private
-         */
-        function _requireWithError($file) {
-            $this->_enableErrorReporting();
-            global $CFG; // Moodle patch for $CFG global in unit test files
-            include($file);
-            $error = isset($php_errormsg) ? $php_errormsg : false;
-            $this->_disableErrorReporting();
-            $self_inflicted_errors = array(
-                    '/Assigning the return value of new by reference/i',
-                    '/var: Deprecated/i',
-                                       '/Non-static method/i');
-            foreach ($self_inflicted_errors as $pattern) {
-                               if (preg_match($pattern, $error)) {
-                                       return false;
-                               }
-                       }
-            return $error;
-        }
+    /**
+     *    Sets the name of the test suite.
+     *    @param string $label    Name sent at the start and end
+     *                            of the test.
+     *    @access public
+     */
+    function TestSuite($label = false) {
+        $this->_label = $label;
+        $this->_test_cases = array();
+    }
 
-        /**
-         *    Sets up detection of parse errors. Note that XDebug
-         *    interferes with this and has to be disabled. This is
-         *    to make sure the correct error code is returned
-         *    from unattended scripts.
-         *    @access private
-         */
-        function _enableErrorReporting() {
-            if ($this->_xdebug_is_enabled) {
-                xdebug_disable();
-            }
-            ini_set('track_errors', true);
+    /**
+     *    Accessor for the test name for subclasses. If the suite
+     *    wraps a single test case the label defaults to the name of that test.
+     *    @return string           Name of the test.
+     *    @access public
+     */
+    function getLabel() {
+        if (! $this->_label) {
+            return ($this->getSize() == 1) ?
+                    get_class($this->_test_cases[0]) : get_class($this);
+        } else {
+            return $this->_label;
         }
+    }
 
-        /**
-         *    Resets detection of parse errors to their old values.
-         *    This is to make sure the correct error code is returned
-         *    from unattended scripts.
-         *    @access private
-         */
-        function _disableErrorReporting() {
-            ini_set('track_errors', $this->_old_track_errors);
-            if ($this->_xdebug_is_enabled) {
-                xdebug_enable();
-            }
-        }
+    /**
+     *    @deprecated
+     */
+    function addTestCase(&$test_case) {
+        $this->_test_cases[] = &$test_case;
+    }
 
-        /**
-         *    Calculates the incoming test cases from a before
-         *    and after list of loaded classes. Skips abstract
-         *    classes.
-         *    @param array $existing_classes   Classes before require().
-         *    @param array $new_classes        Classes after require().
-         *    @return array                    New classes which are test
-         *                                     cases that shouldn't be ignored.
-         *    @access private
-         */
-        function _selectRunnableTests($existing_classes, $new_classes) {
-            $classes = array();
-            foreach ($new_classes as $class) {
-                if (in_array($class, $existing_classes)) {
-                    continue;
-                }
-                if ($this->_getBaseTestCase($class)) {
-                    $reflection = new SimpleReflection($class);
-                    if ($reflection->isAbstract()) {
-                        SimpleTest::ignore($class);
-                    }
-                    $classes[] = $class;
-                }
-            }
-            return $classes;
+    /**
+     *    @deprecated
+     */
+    function addTestClass($class) {
+        if (TestSuite::getBaseTestCase($class) == 'testsuite') {
+            $this->_test_cases[] = &new $class();
+        } else {
+            $this->_test_cases[] = $class;
         }
+    }
 
-        /**
-         *    Builds a group test from a class list.
-         *    @param string $title       Title of new group.
-         *    @param array $classes      Test classes.
-         *    @return TestSuite          Group loaded with the new
-         *                               test cases.
-         *    @access private
-         */
-        function &_createGroupFromClasses($title, $classes) {
-            SimpleTest::ignoreParentsIfIgnored($classes);
-            $group = &new TestSuite($title);
-            foreach ($classes as $class) {
-                if (! SimpleTest::isIgnored($class)) {
-                    $group->addTestClass($class);
-                }
-            }
-            return $group;
+    /**
+     *    Adds a test into the suite by instance or class. The class will
+     *    be instantiated if it's a test suite.
+     *    @param SimpleTestCase $test_case  Suite or individual test
+     *                                      case implementing the
+     *                                      runnable test interface.
+     *    @access public
+     */
+    function add(&$test_case) {
+        if (! is_string($test_case)) {
+            $this->_test_cases[] = &$test_case;
+        } elseif (TestSuite::getBaseTestCase($class) == 'testsuite') {
+            $this->_test_cases[] = &new $class();
+        } else {
+            $this->_test_cases[] = $class;
         }
+    }
 
-        /**
-         *    Test to see if a class is derived from the
-         *    SimpleTestCase class.
-         *    @param string $class     Class name.
-         *    @access private
-         */
-        function _getBaseTestCase($class) {
-            while ($class = get_parent_class($class)) {
-                $class = strtolower($class);
-                if ($class == 'simpletestcase' || $class == 'testsuite' || $class == 'grouptest') {
-                    return $class;
-                }
-            }
-            return false;
-        }
+    /**
+     *    @deprecated
+     */
+    function addTestFile($test_file) {
+        $this->addFile($test_file);
+    }
 
-        /**
-         *    Delegates to a visiting collector to add test
-         *    files.
-         *    @param string $path                  Path to scan from.
-         *    @param SimpleCollector $collector    Directory scanner.
-         *    @access public
-         */
-        function collect($path, &$collector) {
-            $collector->collect($this, $path);
-        }
+    /**
+     *    Builds a test suite from a library of test cases.
+     *    The new suite is composed into this one.
+     *    @param string $test_file        File name of library with
+     *                                    test case classes.
+     *    @access public
+     */
+    function addFile($test_file) {
+        $extractor = new SimpleFileLoader();
+        $this->add($extractor->load($test_file));
+    }
 
-        /**
-         *    Invokes run() on all of the held test cases, instantiating
-         *    them if necessary.
-         *    @param SimpleReporter $reporter    Current test reporter.
-         *    @access public
-         */
-        function run(&$reporter) {
-            $reporter->paintGroupStart($this->getLabel(), $this->getSize());
-            for ($i = 0, $count = count($this->_test_cases); $i < $count; $i++) {
-                if (is_string($this->_test_cases[$i])) {
-                    $class = $this->_test_cases[$i];
-                    $test = &new $class();
-                    $test->run($reporter);
-                    unset($test);
-                } else {
-                    $this->_test_cases[$i]->run($reporter);
-                }
+    /**
+     *    Delegates to a visiting collector to add test
+     *    files.
+     *    @param string $path                  Path to scan from.
+     *    @param SimpleCollector $collector    Directory scanner.
+     *    @access public
+     */
+    function collect($path, &$collector) {
+        $collector->collect($this, $path);
+    }
+
+    /**
+     *    Invokes run() on all of the held test cases, instantiating
+     *    them if necessary.
+     *    @param SimpleReporter $reporter    Current test reporter.
+     *    @access public
+     */
+    function run(&$reporter) {
+        $reporter->paintGroupStart($this->getLabel(), $this->getSize());
+        for ($i = 0, $count = count($this->_test_cases); $i < $count; $i++) {
+            if (is_string($this->_test_cases[$i])) {
+                $class = $this->_test_cases[$i];
+                $test = &new $class();
+                $test->run($reporter);
+                unset($test);
+            } else {
+                $this->_test_cases[$i]->run($reporter);
             }
-            $reporter->paintGroupEnd($this->getLabel());
-            return $reporter->getStatus();
         }
+        $reporter->paintGroupEnd($this->getLabel());
+        return $reporter->getStatus();
+    }
 
-        /**
-         *    Number of contained test cases.
-         *    @return integer     Total count of cases in the group.
-         *    @access public
-         */
-        function getSize() {
-            $count = 0;
-            foreach ($this->_test_cases as $case) {
-                if (is_string($case)) {
+    /**
+     *    Number of contained test cases.
+     *    @return integer     Total count of cases in the group.
+     *    @access public
+     */
+    function getSize() {
+        $count = 0;
+        foreach ($this->_test_cases as $case) {
+            if (is_string($case)) {
+                if (! SimpleTest::isIgnored($case)) {
                     $count++;
-                } else {
-                    $count += $case->getSize();
                 }
+            } else {
+                $count += $case->getSize();
             }
-            return $count;
         }
+        return $count;
     }
-    
-    /**
-     *    @deprecated
-     */
-    class GroupTest extends TestSuite { }
 
     /**
-     *    This is a failing group test for when a test suite hasn't
-     *    loaded properly.
-        *    @package          SimpleTest
-        *    @subpackage       UnitTester
+     *    Test to see if a class is derived from the
+     *    SimpleTestCase class.
+     *    @param string $class     Class name.
+     *    @access public
+     *    @static
      */
-    class BadTestSuite {
-        var $_label;
-        var $_error;
-
-        /**
-         *    Sets the name of the test suite and error message.
-         *    @param string $label    Name sent at the start and end
-         *                            of the test.
-         *    @access public
-         */
-        function BadTestSuite($label, $error) {
-            $this->_label = $label;
-            $this->_error = $error;
+    function getBaseTestCase($class) {
+        while ($class = get_parent_class($class)) {
+            $class = strtolower($class);
+            if ($class == 'simpletestcase' || $class == 'testsuite') {
+                return $class;
+            }
         }
+        return false;
+    }
+}
+
+/**
+ *    @package      SimpleTest
+ *    @subpackage   UnitTester
+ *    @deprecated
+ */
+class GroupTest extends TestSuite { }
+
+/**
+ *    This is a failing group test for when a test suite hasn't
+ *    loaded properly.
+ *    @package      SimpleTest
+ *    @subpackage   UnitTester
+ */
+class BadTestSuite {
+    var $_label;
+    var $_error;
 
-        /**
-         *    Accessor for the test name for subclasses.
-         *    @return string           Name of the test.
-         *    @access public
-         */
-        function getLabel() {
-            return $this->_label;
-        }
+    /**
+     *    Sets the name of the test suite and error message.
+     *    @param string $label    Name sent at the start and end
+     *                            of the test.
+     *    @access public
+     */
+    function BadTestSuite($label, $error) {
+        $this->_label = $label;
+        $this->_error = $error;
+    }
 
-        /**
-         *    Sends a single error to the reporter.
-         *    @param SimpleReporter $reporter    Current test reporter.
-         *    @access public
-         */
-        function run(&$reporter) {
-            $reporter->paintGroupStart($this->getLabel(), $this->getSize());
-            $reporter->paintFail('Bad TestSuite [' . $this->getLabel() .
-                    '] with error [' . $this->_error . ']');
-            $reporter->paintGroupEnd($this->getLabel());
-            return $reporter->getStatus();
-        }
+    /**
+     *    Accessor for the test name for subclasses.
+     *    @return string           Name of the test.
+     *    @access public
+     */
+    function getLabel() {
+        return $this->_label;
+    }
 
-        /**
-         *    Number of contained test cases. Always zero.
-         *    @return integer     Total count of cases in the group.
-         *    @access public
-         */
-        function getSize() {
-            return 0;
-        }
+    /**
+     *    Sends a single error to the reporter.
+     *    @param SimpleReporter $reporter    Current test reporter.
+     *    @access public
+     */
+    function run(&$reporter) {
+        $reporter->paintGroupStart($this->getLabel(), $this->getSize());
+        $reporter->paintFail('Bad TestSuite [' . $this->getLabel() .
+                '] with error [' . $this->_error . ']');
+        $reporter->paintGroupEnd($this->getLabel());
+        return $reporter->getStatus();
     }
-    
+
     /**
-     *    @deprecated
+     *    Number of contained test cases. Always zero.
+     *    @return integer     Total count of cases in the group.
+     *    @access public
      */
-    class BadGroupTest extends BadTestSuite { }
-?>
\ No newline at end of file
+    function getSize() {
+        return 0;
+    }
+}
+
+/**
+ *    @package      SimpleTest
+ *    @subpackage   UnitTester
+ *    @deprecated
+ */
+class BadGroupTest extends BadTestSuite { }
+?>
index 6efaf60d55fe19a2b5e0d91c0127a1e401a9a7c0..54a13480d144af31f859de534a5d04cdd7694f5d 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ *  include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/test_case.php');
+require_once(dirname(__FILE__) . '/dumper.php');
+/**#@-*/
+
+/**
+ *    Standard unit test class for day to day testing
+ *    of PHP code XP style. Adds some useful standard
+ *    assertions.
+ *    @package  SimpleTest
+ *    @subpackage   UnitTester
+ */
+class UnitTestCase extends SimpleTestCase {
+
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
+     *    Creates an empty test case. Should be subclassed
+     *    with test methods for a functional test case.
+     *    @param string $label     Name of test case. Will use
+     *                             the class name if none specified.
+     *    @access public
      */
+    function UnitTestCase($label = false) {
+        if (! $label) {
+            $label = get_class($this);
+        }
+        $this->SimpleTestCase($label);
+    }
 
-    /**#@+
-     * include other SimpleTest class files
+    /**
+     *    Called from within the test methods to register
+     *    passes and failures.
+     *    @param boolean $result    Pass on true.
+     *    @param string $message    Message to display describing
+     *                              the test state.
+     *    @return boolean           True on pass
+     *    @access public
      */
-    require_once(dirname(__FILE__) . '/test_case.php');
-    require_once(dirname(__FILE__) . '/dumper.php');
-    /**#@-*/
+    function assertTrue($result, $message = false) {
+        return $this->assert(new TrueExpectation(), $result, $message);
+    }
 
     /**
-     *    Standard unit test class for day to day testing
-     *    of PHP code XP style. Adds some useful standard
-     *    assertions.
-        *        @package      SimpleTest
-        *        @subpackage   UnitTester
+     *    Will be true on false and vice versa. False
+     *    is the PHP definition of false, so that null,
+     *    empty strings, zero and an empty array all count
+     *    as false.
+     *    @param boolean $result    Pass on false.
+     *    @param string $message    Message to display.
+     *    @return boolean           True on pass
+     *    @access public
      */
-    class UnitTestCase extends SimpleTestCase {
-
-        /**
-         *    Creates an empty test case. Should be subclassed
-         *    with test methods for a functional test case.
-         *    @param string $label     Name of test case. Will use
-         *                             the class name if none specified.
-         *    @access public
-         */
-        function UnitTestCase($label = false) {
-            if (! $label) {
-                $label = get_class($this);
-            }
-            $this->SimpleTestCase($label);
-        }
-
-        /**
-         *    Called from within the test methods to register
-         *    passes and failures.
-         *    @param boolean $result    Pass on true.
-         *    @param string $message    Message to display describing
-         *                              the test state.
-         *    @return boolean           True on pass
-         *    @access public
-         */
-        function assertTrue($result, $message = false) {
-            return $this->assert(new TrueExpectation(), $result, $message);
-        }
+    function assertFalse($result, $message = '%s') {
+        return $this->assert(new FalseExpectation(), $result, $message);
+    }
 
-        /**
-         *    Will be true on false and vice versa. False
-         *    is the PHP definition of false, so that null,
-         *    empty strings, zero and an empty array all count
-         *    as false.
-         *    @param boolean $result    Pass on false.
-         *    @param string $message    Message to display.
-         *    @return boolean           True on pass
-         *    @access public
-         */
-        function assertFalse($result, $message = '%s') {
-            return $this->assert(new FalseExpectation(), $result, $message);
-        }
+    /**
+     *    Will be true if the value is null.
+     *    @param null $value       Supposedly null value.
+     *    @param string $message   Message to display.
+     *    @return boolean                        True on pass
+     *    @access public
+     */
+    function assertNull($value, $message = '%s') {
+        $dumper = &new SimpleDumper();
+        $message = sprintf(
+                $message,
+                '[' . $dumper->describeValue($value) . '] should be null');
+        return $this->assertTrue(! isset($value), $message);
+    }
 
-        /**
-         *    Will be true if the value is null.
-         *    @param null $value       Supposedly null value.
-         *    @param string $message   Message to display.
-         *    @return boolean                        True on pass
-         *    @access public
-         */
-        function assertNull($value, $message = '%s') {
-            $dumper = &new SimpleDumper();
-            $message = sprintf(
-                    $message,
-                    '[' . $dumper->describeValue($value) . '] should be null');
-            return $this->assertTrue(! isset($value), $message);
-        }
+    /**
+     *    Will be true if the value is set.
+     *    @param mixed $value           Supposedly set value.
+     *    @param string $message        Message to display.
+     *    @return boolean               True on pass.
+     *    @access public
+     */
+    function assertNotNull($value, $message = '%s') {
+        $dumper = &new SimpleDumper();
+        $message = sprintf(
+                $message,
+                '[' . $dumper->describeValue($value) . '] should not be null');
+        return $this->assertTrue(isset($value), $message);
+    }
 
-        /**
-         *    Will be true if the value is set.
-         *    @param mixed $value           Supposedly set value.
-         *    @param string $message        Message to display.
-         *    @return boolean               True on pass.
-         *    @access public
-         */
-        function assertNotNull($value, $message = '%s') {
-            $dumper = &new SimpleDumper();
-            $message = sprintf(
-                    $message,
-                    '[' . $dumper->describeValue($value) . '] should not be null');
-            return $this->assertTrue(isset($value), $message);
-        }
+    /**
+     *    Type and class test. Will pass if class
+     *    matches the type name or is a subclass or
+     *    if not an object, but the type is correct.
+     *    @param mixed $object         Object to test.
+     *    @param string $type          Type name as string.
+     *    @param string $message       Message to display.
+     *    @return boolean              True on pass.
+     *    @access public
+     */
+    function assertIsA($object, $type, $message = '%s') {
+        return $this->assert(
+                new IsAExpectation($type),
+                $object,
+                $message);
+    }
 
-        /**
-         *    Type and class test. Will pass if class
-         *    matches the type name or is a subclass or
-         *    if not an object, but the type is correct.
-         *    @param mixed $object         Object to test.
-         *    @param string $type          Type name as string.
-         *    @param string $message       Message to display.
-         *    @return boolean              True on pass.
-         *    @access public
-         */
-        function assertIsA($object, $type, $message = '%s') {
-            return $this->assert(
-                    new IsAExpectation($type),
-                    $object,
-                    $message);
-        }
+    /**
+     *    Type and class mismatch test. Will pass if class
+     *    name or underling type does not match the one
+     *    specified.
+     *    @param mixed $object         Object to test.
+     *    @param string $type          Type name as string.
+     *    @param string $message       Message to display.
+     *    @return boolean              True on pass.
+     *    @access public
+     */
+    function assertNotA($object, $type, $message = '%s') {
+        return $this->assert(
+                new NotAExpectation($type),
+                $object,
+                $message);
+    }
 
-        /**
-         *    Type and class mismatch test. Will pass if class
-         *    name or underling type does not match the one
-         *    specified.
-         *    @param mixed $object         Object to test.
-         *    @param string $type          Type name as string.
-         *    @param string $message       Message to display.
-         *    @return boolean              True on pass.
-         *    @access public
-         */
-        function assertNotA($object, $type, $message = '%s') {
-            return $this->assert(
-                    new NotAExpectation($type),
-                    $object,
-                    $message);
-        }
+    /**
+     *    Will trigger a pass if the two parameters have
+     *    the same value only. Otherwise a fail.
+     *    @param mixed $first          Value to compare.
+     *    @param mixed $second         Value to compare.
+     *    @param string $message       Message to display.
+     *    @return boolean              True on pass
+     *    @access public
+     */
+    function assertEqual($first, $second, $message = '%s') {
+        return $this->assert(
+                new EqualExpectation($first),
+                $second,
+                $message);
+    }
 
-        /**
-         *    Will trigger a pass if the two parameters have
-         *    the same value only. Otherwise a fail.
-         *    @param mixed $first          Value to compare.
-         *    @param mixed $second         Value to compare.
-         *    @param string $message       Message to display.
-         *    @return boolean              True on pass
-         *    @access public
-         */
-        function assertEqual($first, $second, $message = '%s') {
-            return $this->assert(
-                    new EqualExpectation($first),
-                    $second,
-                    $message);
-        }
+    /**
+     *    Will trigger a pass if the two parameters have
+     *    a different value. Otherwise a fail.
+     *    @param mixed $first           Value to compare.
+     *    @param mixed $second          Value to compare.
+     *    @param string $message        Message to display.
+     *    @return boolean               True on pass
+     *    @access public
+     */
+    function assertNotEqual($first, $second, $message = '%s') {
+        return $this->assert(
+                new NotEqualExpectation($first),
+                $second,
+                $message);
+    }
 
-        /**
-         *    Will trigger a pass if the two parameters have
-         *    a different value. Otherwise a fail.
-         *    @param mixed $first           Value to compare.
-         *    @param mixed $second          Value to compare.
-         *    @param string $message        Message to display.
-         *    @return boolean               True on pass
-         *    @access public
-         */
-        function assertNotEqual($first, $second, $message = '%s') {
-            return $this->assert(
-                    new NotEqualExpectation($first),
-                    $second,
-                    $message);
-        }
+    /**
+     *    Will trigger a pass if the if the first parameter
+     *    is near enough to the second by the margin.
+     *    @param mixed $first          Value to compare.
+     *    @param mixed $second         Value to compare.
+     *    @param mixed $margin         Fuzziness of match.
+     *    @param string $message       Message to display.
+     *    @return boolean              True on pass
+     *    @access public
+     */
+    function assertWithinMargin($first, $second, $margin, $message = '%s') {
+        return $this->assert(
+                new WithinMarginExpectation($first, $margin),
+                $second,
+                $message);
+    }
 
-        /**
-         *    Will trigger a pass if the if the first parameter
-         *    is near enough to the second by the margin.
-         *    @param mixed $first          Value to compare.
-         *    @param mixed $second         Value to compare.
-         *    @param mixed $margin         Fuzziness of match.
-         *    @param string $message       Message to display.
-         *    @return boolean              True on pass
-         *    @access public
-         */
-        function assertWithinMargin($first, $second, $margin, $message = '%s') {
-            return $this->assert(
-                    new WithinMarginExpectation($first, $margin),
-                    $second,
-                    $message);
-        }
+    /**
+     *    Will trigger a pass if the two parameters differ
+     *    by more than the margin.
+     *    @param mixed $first          Value to compare.
+     *    @param mixed $second         Value to compare.
+     *    @param mixed $margin         Fuzziness of match.
+     *    @param string $message       Message to display.
+     *    @return boolean              True on pass
+     *    @access public
+     */
+    function assertOutsideMargin($first, $second, $margin, $message = '%s') {
+        return $this->assert(
+                new OutsideMarginExpectation($first, $margin),
+                $second,
+                $message);
+    }
 
-        /**
-         *    Will trigger a pass if the two parameters differ
-         *    by more than the margin.
-         *    @param mixed $first          Value to compare.
-         *    @param mixed $second         Value to compare.
-         *    @param mixed $margin         Fuzziness of match.
-         *    @param string $message       Message to display.
-         *    @return boolean              True on pass
-         *    @access public
-         */
-        function assertOutsideMargin($first, $second, $margin, $message = '%s') {
-            return $this->assert(
-                    new OutsideMarginExpectation($first, $margin),
-                    $second,
-                    $message);
-        }
+    /**
+     *    Will trigger a pass if the two parameters have
+     *    the same value and same type. Otherwise a fail.
+     *    @param mixed $first           Value to compare.
+     *    @param mixed $second          Value to compare.
+     *    @param string $message        Message to display.
+     *    @return boolean               True on pass
+     *    @access public
+     */
+    function assertIdentical($first, $second, $message = '%s') {
+        return $this->assert(
+                new IdenticalExpectation($first),
+                $second,
+                $message);
+    }
 
-        /**
-         *    Will trigger a pass if the two parameters have
-         *    the same value and same type. Otherwise a fail.
-         *    @param mixed $first           Value to compare.
-         *    @param mixed $second          Value to compare.
-         *    @param string $message        Message to display.
-         *    @return boolean               True on pass
-         *    @access public
-         */
-        function assertIdentical($first, $second, $message = '%s') {
-            return $this->assert(
-                    new IdenticalExpectation($first),
-                    $second,
-                    $message);
-        }
+    /**
+     *    Will trigger a pass if the two parameters have
+     *    the different value or different type.
+     *    @param mixed $first           Value to compare.
+     *    @param mixed $second          Value to compare.
+     *    @param string $message        Message to display.
+     *    @return boolean               True on pass
+     *    @access public
+     */
+    function assertNotIdentical($first, $second, $message = '%s') {
+        return $this->assert(
+                new NotIdenticalExpectation($first),
+                $second,
+                $message);
+    }
 
-        /**
-         *    Will trigger a pass if the two parameters have
-         *    the different value or different type.
-         *    @param mixed $first           Value to compare.
-         *    @param mixed $second          Value to compare.
-         *    @param string $message        Message to display.
-         *    @return boolean               True on pass
-         *    @access public
-         */
-        function assertNotIdentical($first, $second, $message = '%s') {
-            return $this->assert(
-                    new NotIdenticalExpectation($first),
-                    $second,
-                    $message);
-        }
+    /**
+     *    Will trigger a pass if both parameters refer
+     *    to the same object. Fail otherwise.
+     *    @param mixed $first           Object reference to check.
+     *    @param mixed $second          Hopefully the same object.
+     *    @param string $message        Message to display.
+     *    @return boolean               True on pass
+     *    @access public
+     */
+    function assertReference(&$first, &$second, $message = '%s') {
+        $dumper = &new SimpleDumper();
+        $message = sprintf(
+                $message,
+                '[' . $dumper->describeValue($first) .
+                        '] and [' . $dumper->describeValue($second) .
+                        '] should reference the same object');
+        return $this->assertTrue(
+                SimpleTestCompatibility::isReference($first, $second),
+                $message);
+    }
 
-        /**
-         *    Will trigger a pass if both parameters refer
-         *    to the same object. Fail otherwise.
-         *    @param mixed $first           Object reference to check.
-         *    @param mixed $second          Hopefully the same object.
-         *    @param string $message        Message to display.
-         *    @return boolean               True on pass
-         *    @access public
-         */
-        function assertReference(&$first, &$second, $message = '%s') {
-            $dumper = &new SimpleDumper();
-            $message = sprintf(
-                    $message,
-                    '[' . $dumper->describeValue($first) .
-                            '] and [' . $dumper->describeValue($second) .
-                            '] should reference the same object');
-            return $this->assertTrue(
-                    SimpleTestCompatibility::isReference($first, $second),
-                    $message);
-        }
+    /**
+     *    Will trigger a pass if both parameters refer
+     *    to different objects. Fail otherwise. The objects
+     *    have to be identical though.
+     *    @param mixed $first           Object reference to check.
+     *    @param mixed $second          Hopefully not the same object.
+     *    @param string $message        Message to display.
+     *    @return boolean               True on pass
+     *    @access public
+     */
+    function assertClone(&$first, &$second, $message = '%s') {
+        $dumper = &new SimpleDumper();
+        $message = sprintf(
+                $message,
+                '[' . $dumper->describeValue($first) .
+                        '] and [' . $dumper->describeValue($second) .
+                        '] should not be the same object');
+        $identical = &new IdenticalExpectation($first);
+        return $this->assertTrue(
+                $identical->test($second) &&
+                        ! SimpleTestCompatibility::isReference($first, $second),
+                $message);
+    }
 
-        /**
-         *    Will trigger a pass if both parameters refer
-         *    to different objects. Fail otherwise. The objects
-         *    have to be identical though.
-         *    @param mixed $first           Object reference to check.
-         *    @param mixed $second          Hopefully not the same object.
-         *    @param string $message        Message to display.
-         *    @return boolean               True on pass
-         *    @access public
-         */
-        function assertClone(&$first, &$second, $message = '%s') {
-            $dumper = &new SimpleDumper();
-            $message = sprintf(
-                    $message,
-                    '[' . $dumper->describeValue($first) .
-                            '] and [' . $dumper->describeValue($second) .
-                            '] should not be the same object');
-            $identical = &new IdenticalExpectation($first);
-            return $this->assertTrue(
-                    $identical->test($second) &&
-                            ! SimpleTestCompatibility::isReference($first, $second),
-                    $message);
-        }
+    /**
+     *    @deprecated
+     */
+    function assertCopy(&$first, &$second, $message = "%s") {
+        $dumper = &new SimpleDumper();
+        $message = sprintf(
+                $message,
+                "[" . $dumper->describeValue($first) .
+                        "] and [" . $dumper->describeValue($second) .
+                        "] should not be the same object");
+        return $this->assertFalse(
+                SimpleTestCompatibility::isReference($first, $second),
+                $message);
+    }
 
-        /**
-         *    @deprecated
-         */
-        function assertCopy(&$first, &$second, $message = "%s") {
-            $dumper = &new SimpleDumper();
-            $message = sprintf(
-                    $message,
-                    "[" . $dumper->describeValue($first) .
-                            "] and [" . $dumper->describeValue($second) .
-                            "] should not be the same object");
-            return $this->assertFalse(
-                    SimpleTestCompatibility::isReference($first, $second),
-                    $message);
-        }
+    /**
+     *    Will trigger a pass if the Perl regex pattern
+     *    is found in the subject. Fail otherwise.
+     *    @param string $pattern    Perl regex to look for including
+     *                              the regex delimiters.
+     *    @param string $subject    String to search in.
+     *    @param string $message    Message to display.
+     *    @return boolean           True on pass
+     *    @access public
+     */
+    function assertPattern($pattern, $subject, $message = '%s') {
+        return $this->assert(
+                new PatternExpectation($pattern),
+                $subject,
+                $message);
+    }
 
-        /**
-         *    Will trigger a pass if the Perl regex pattern
-         *    is found in the subject. Fail otherwise.
-         *    @param string $pattern    Perl regex to look for including
-         *                              the regex delimiters.
-         *    @param string $subject    String to search in.
-         *    @param string $message    Message to display.
-         *    @return boolean           True on pass
-         *    @access public
-         */
-        function assertPattern($pattern, $subject, $message = '%s') {
-            return $this->assert(
-                    new PatternExpectation($pattern),
-                    $subject,
-                    $message);
-        }
+    /**
+     *    @deprecated
+     */
+    function assertWantedPattern($pattern, $subject, $message = '%s') {
+        return $this->assertPattern($pattern, $subject, $message);
+    }
 
-        /**
-         *       @deprecated
-         */
-        function assertWantedPattern($pattern, $subject, $message = '%s') {
-               return $this->assertPattern($pattern, $subject, $message);
-        }
+    /**
+     *    Will trigger a pass if the perl regex pattern
+     *    is not present in subject. Fail if found.
+     *    @param string $pattern    Perl regex to look for including
+     *                              the regex delimiters.
+     *    @param string $subject    String to search in.
+     *    @param string $message    Message to display.
+     *    @return boolean           True on pass
+     *    @access public
+     */
+    function assertNoPattern($pattern, $subject, $message = '%s') {
+        return $this->assert(
+                new NoPatternExpectation($pattern),
+                $subject,
+                $message);
+    }
 
-        /**
-         *    Will trigger a pass if the perl regex pattern
-         *    is not present in subject. Fail if found.
-         *    @param string $pattern    Perl regex to look for including
-         *                              the regex delimiters.
-         *    @param string $subject    String to search in.
-         *    @param string $message    Message to display.
-         *    @return boolean           True on pass
-         *    @access public
-         */
-        function assertNoPattern($pattern, $subject, $message = '%s') {
-            return $this->assert(
-                    new NoPatternExpectation($pattern),
-                    $subject,
-                    $message);
-        }
+    /**
+     *    @deprecated
+     */
+    function assertNoUnwantedPattern($pattern, $subject, $message = '%s') {
+        return $this->assertNoPattern($pattern, $subject, $message);
+    }
 
-        /**
-         *       @deprecated
-         */
-        function assertNoUnwantedPattern($pattern, $subject, $message = '%s') {
-               return $this->assertNoPattern($pattern, $subject, $message);
-        }
+    /**
+     *    @deprecated
+     */
+    function swallowErrors() {
+        $context = &SimpleTest::getContext();
+        $queue = &$context->get('SimpleErrorQueue');
+        $queue->clear();
+    }
 
-        /**
-         *    @deprecated
-         */
-        function swallowErrors() {
-            $context = &SimpleTest::getContext();
-            $queue = &$context->get('SimpleErrorQueue');
-            $queue->clear();
-        }
+    /**
+     *    @deprecated
+     */
+    function assertNoErrors($message = '%s') {
+        $context = &SimpleTest::getContext();
+        $queue = &$context->get('SimpleErrorQueue');
+        return $queue->assertNoErrors($message);
+    }
 
-        /**
-         *    @deprecated
-         */
-        function assertNoErrors($message = '%s') {
-            $context = &SimpleTest::getContext();
-            $queue = &$context->get('SimpleErrorQueue');
-            return $queue->assertNoErrors($message);
-        }
+    /**
+     *    @deprecated
+     */
+    function assertError($expected = false, $message = '%s') {
+        $context = &SimpleTest::getContext();
+        $queue = &$context->get('SimpleErrorQueue');
+        return $queue->assertError($this->_coerceExpectation($expected), $message);
+    }
 
-        /**
-         *    @deprecated
-         */
-        function assertError($expected = false, $message = '%s') {
-            $context = &SimpleTest::getContext();
-            $queue = &$context->get('SimpleErrorQueue');
-            return $queue->assertError($this->_coerceExpectation($expected), $message);
-        }
+    /**
+     *    Prepares for an error. If the error mismatches it
+     *    passes through, otherwise it is swallowed. Any
+     *    left over errors trigger failures.
+     *    @param SimpleExpectation/string $expected   The error to match.
+     *    @param string $message                      Message on failure.
+     *    @access public
+     */
+    function expectError($expected = false, $message = '%s') {
+        $context = &SimpleTest::getContext();
+        $queue = &$context->get('SimpleErrorQueue');
+        $queue->expectError($this->_coerceExpectation($expected), $message);
+    }
 
-        /**
-         *    Prepares for an error. If the error mismatches it
-         *    passes through, otherwise it is swallowed. Any
-         *    left over errors trigger failures.
-         *    @param SimpleExpectation/string $expected   The error to match.
-         *    @param string $message                      Message on failure.
-         *    @access public
-         */
-        function expectError($expected = false, $message = '%s') {
-            $context = &SimpleTest::getContext();
-            $queue = &$context->get('SimpleErrorQueue');
-            $queue->expectError($this->_coerceExpectation($expected), $message);
-        }
+    /**
+     *    Prepares for an exception. If the error mismatches it
+     *    passes through, otherwise it is swallowed. Any
+     *    left over errors trigger failures.
+     *    @param SimpleExpectation/Exception $expected  The error to match.
+     *    @param string $message                        Message on failure.
+     *    @access public
+     */
+    function expectException($expected = false, $message = '%s') {
+        $context = &SimpleTest::getContext();
+        $queue = &$context->get('SimpleExceptionTrap');
+        // :HACK: Directly substituting in seems to cause a segfault with
+        // Zend Optimizer on some systems
+        $line = $this->getAssertionLine();
+        $queue->expectException($expected, $message . $line);
+    }
 
-        /**
-         *    Prepares for an exception. If the error mismatches it
-         *    passes through, otherwise it is swallowed. Any
-         *    left over errors trigger failures.
-         *    @param SimpleExpectation/Exception $expected  The error to match.
-         *    @param string $message                        Message on failure.
-         *    @access public
-         */
-        function expectException($expected = false, $message = '%s') {
-            $context = &SimpleTest::getContext();
-            $queue = &$context->get('SimpleExceptionTrap');
-            $queue->expectException($expected, $message . $this->getAssertionLine());
+    /**
+     *    Creates an equality expectation if the
+     *    object/value is not already some type
+     *    of expectation.
+     *    @param mixed $expected      Expected value.
+     *    @return SimpleExpectation   Expectation object.
+     *    @access private
+     */
+    function _coerceExpectation($expected) {
+        if ($expected == false) {
+            return new TrueExpectation();
         }
-
-        /**
-         *    Creates an equality expectation if the
-         *    object/value is not already some type
-         *    of expectation.
-         *    @param mixed $expected      Expected value.
-         *    @return SimpleExpectation   Expectation object.
-         *    @access private
-         */
-        function _coerceExpectation($expected) {
-            if ($expected == false) {
-                return new AnythingExpectation();
-            }
-            if (SimpleTestCompatibility::isA($expected, 'SimpleExpectation')) {
-                return $expected;
-            }
-                       if(is_string($expected)) {
-                               $expected = str_replace('%', '%%', $expected);
-                       }
-            return new EqualExpectation($expected);
+        if (SimpleTestCompatibility::isA($expected, 'SimpleExpectation')) {
+            return $expected;
         }
+        return new EqualExpectation(
+                is_string($expected) ? str_replace('%', '%%', $expected) : $expected);
+    }
 
-        /**
-         *    @deprecated
-         */
-        function assertErrorPattern($pattern, $message = '%s') {
-            return $this->assertError(new PatternExpectation($pattern), $message);
-        }
+    /**
+     *    @deprecated
+     */
+    function assertErrorPattern($pattern, $message = '%s') {
+        return $this->assertError(new PatternExpectation($pattern), $message);
     }
+}
 ?>
\ No newline at end of file
index b66b31f43c0ca137384f16c3ba5d815b162dcd94..ce3765ea368d6171b97bf07fbe7bbe39822c445f 100644 (file)
 <?php
-    /**
    * base include file for SimpleTest
    * @package        SimpleTest
    * @subpackage     WebTester
    * @version        $Id$
    */
+/**
 base include file for SimpleTest
*  @package    SimpleTest
*  @subpackage WebTester
*  @version    $Id$
+ */
 
-    /**#@+
    * include other SimpleTest class files
    */
-    require_once(dirname(__FILE__) . '/encoding.php');
-    /**#@-*/
+/**#@+
 include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/encoding.php');
+/**#@-*/
 
+/**
+ *    URL parser to replace parse_url() PHP function which
+ *    got broken in PHP 4.3.0. Adds some browser specific
+ *    functionality such as expandomatics.
+ *    Guesses a bit trying to separate the host from
+ *    the path and tries to keep a raw, possibly unparsable,
+ *    request string as long as possible.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleUrl {
+    var $_scheme;
+    var $_username;
+    var $_password;
+    var $_host;
+    var $_port;
+    var $_path;
+    var $_request;
+    var $_fragment;
+    var $_x;
+    var $_y;
+    var $_target;
+    var $_raw = false;
+    
     /**
-     *    URL parser to replace parse_url() PHP function which
-     *    got broken in PHP 4.3.0. Adds some browser specific
-     *    functionality such as expandomatics.
-     *    Guesses a bit trying to separate the host from
-     *    the path and tries to keep a raw, possibly unparsable,
-     *    request string as long as possible.
-        *    @package SimpleTest
-        *    @subpackage WebTester
-     */
-    class SimpleUrl {
-        var $_scheme;
-        var $_username;
-        var $_password;
-        var $_host;
-        var $_port;
-        var $_path;
-        var $_request;
-        var $_fragment;
-        var $_x;
-        var $_y;
-        var $_target;
-        var $_raw = false;
-        
-        /**
-         *    Constructor. Parses URL into sections.
-         *    @param string $url        Incoming URL.
-         *    @access public
-         */
-        function SimpleUrl($url) {
-            list($x, $y) = $this->_chompCoordinates($url);
-            $this->setCoordinates($x, $y);
-            $this->_scheme = $this->_chompScheme($url);
-            list($this->_username, $this->_password) = $this->_chompLogin($url);
-            $this->_host = $this->_chompHost($url);
-            $this->_port = false;
-            if (preg_match('/(.*?):(.*)/', $this->_host, $host_parts)) {
-                $this->_host = $host_parts[1];
-                $this->_port = (integer)$host_parts[2];
-            }
-            $this->_path = $this->_chompPath($url);
-            $this->_request = $this->_parseRequest($this->_chompRequest($url));
-            $this->_fragment = (strncmp($url, "#", 1) == 0 ? substr($url, 1) : false);
-            $this->_target = false;
+     *    Constructor. Parses URL into sections.
+     *    @param string $url        Incoming URL.
+     *    @access public
+     */
+    function SimpleUrl($url = '') {
+        list($x, $y) = $this->_chompCoordinates($url);
+        $this->setCoordinates($x, $y);
+        $this->_scheme = $this->_chompScheme($url);
+        list($this->_username, $this->_password) = $this->_chompLogin($url);
+        $this->_host = $this->_chompHost($url);
+        $this->_port = false;
+        if (preg_match('/(.*?):(.*)/', $this->_host, $host_parts)) {
+            $this->_host = $host_parts[1];
+            $this->_port = (integer)$host_parts[2];
+        }
+        $this->_path = $this->_chompPath($url);
+        $this->_request = $this->_parseRequest($this->_chompRequest($url));
+        $this->_fragment = (strncmp($url, "#", 1) == 0 ? substr($url, 1) : false);
+        $this->_target = false;
+    }
+    
+    /**
+     *    Extracts the X, Y coordinate pair from an image map.
+     *    @param string $url   URL so far. The coordinates will be
+     *                         removed.
+     *    @return array        X, Y as a pair of integers.
+     *    @access private
+     */
+    function _chompCoordinates(&$url) {
+        if (preg_match('/(.*)\?(\d+),(\d+)$/', $url, $matches)) {
+            $url = $matches[1];
+            return array((integer)$matches[2], (integer)$matches[3]);
         }
-        
-        /**
-         *    Extracts the X, Y coordinate pair from an image map.
-         *    @param string $url   URL so far. The coordinates will be
-         *                         removed.
-         *    @return array        X, Y as a pair of integers.
-         *    @access private
-         */
-        function _chompCoordinates(&$url) {
-            if (preg_match('/(.*)\?(\d+),(\d+)$/', $url, $matches)) {
-                $url = $matches[1];
-                return array((integer)$matches[2], (integer)$matches[3]);
-            }
-            return array(false, false);
+        return array(false, false);
+    }
+    
+    /**
+     *    Extracts the scheme part of an incoming URL.
+     *    @param string $url   URL so far. The scheme will be
+     *                         removed.
+     *    @return string       Scheme part or false.
+     *    @access private
+     */
+    function _chompScheme(&$url) {
+        if (preg_match('/^([^\/:]*):(\/\/)(.*)/', $url, $matches)) {
+            $url = $matches[2] . $matches[3];
+            return $matches[1];
         }
-        
-        /**
-         *    Extracts the scheme part of an incoming URL.
-         *    @param string $url   URL so far. The scheme will be
-         *                         removed.
-         *    @return string       Scheme part or false.
-         *    @access private
-         */
-        function _chompScheme(&$url) {
-            if (preg_match('/(.*?):(\/\/)(.*)/', $url, $matches)) {
+        return false;
+    }
+    
+    /**
+     *    Extracts the username and password from the
+     *    incoming URL. The // prefix will be reattached
+     *    to the URL after the doublet is extracted.
+     *    @param string $url    URL so far. The username and
+     *                          password are removed.
+     *    @return array         Two item list of username and
+     *                          password. Will urldecode() them.
+     *    @access private
+     */
+    function _chompLogin(&$url) {
+        $prefix = '';
+        if (preg_match('/^(\/\/)(.*)/', $url, $matches)) {
+            $prefix = $matches[1];
+            $url = $matches[2];
+        }
+        if (preg_match('/^([^\/]*)@(.*)/', $url, $matches)) {
+            $url = $prefix . $matches[2];
+            $parts = split(":", $matches[1]);
+            return array(
+                    urldecode($parts[0]),
+                    isset($parts[1]) ? urldecode($parts[1]) : false);
+        }
+        $url = $prefix . $url;
+        return array(false, false);
+    }
+    
+    /**
+     *    Extracts the host part of an incoming URL.
+     *    Includes the port number part. Will extract
+     *    the host if it starts with // or it has
+     *    a top level domain or it has at least two
+     *    dots.
+     *    @param string $url    URL so far. The host will be
+     *                          removed.
+     *    @return string        Host part guess or false.
+     *    @access private
+     */
+    function _chompHost(&$url) {
+        if (preg_match('/^(\/\/)(.*?)(\/.*|\?.*|#.*|$)/', $url, $matches)) {
+            $url = $matches[3];
+            return $matches[2];
+        }
+        if (preg_match('/(.*?)(\.\.\/|\.\/|\/|\?|#|$)(.*)/', $url, $matches)) {
+            $tlds = SimpleUrl::getAllTopLevelDomains();
+            if (preg_match('/[a-z0-9\-]+\.(' . $tlds . ')/i', $matches[1])) {
                 $url = $matches[2] . $matches[3];
                 return $matches[1];
-            }
-            return false;
-        }
-        
-        /**
-         *    Extracts the username and password from the
-         *    incoming URL. The // prefix will be reattached
-         *    to the URL after the doublet is extracted.
-         *    @param string $url    URL so far. The username and
-         *                          password are removed.
-         *    @return array         Two item list of username and
-         *                          password. Will urldecode() them.
-         *    @access private
-         */
-        function _chompLogin(&$url) {
-            $prefix = '';
-            if (preg_match('/^(\/\/)(.*)/', $url, $matches)) {
-                $prefix = $matches[1];
-                $url = $matches[2];
-            }
-            if (preg_match('/(.*?)@(.*)/', $url, $matches)) {
-                $url = $prefix . $matches[2];
-                $parts = split(":", $matches[1]);
-                return array(
-                        urldecode($parts[0]),
-                        isset($parts[1]) ? urldecode($parts[1]) : false);
-            }
-            $url = $prefix . $url;
-            return array(false, false);
-        }
-        
-        /**
-         *    Extracts the host part of an incoming URL.
-         *    Includes the port number part. Will extract
-         *    the host if it starts with // or it has
-         *    a top level domain or it has at least two
-         *    dots.
-         *    @param string $url    URL so far. The host will be
-         *                          removed.
-         *    @return string        Host part guess or false.
-         *    @access private
-         */
-        function _chompHost(&$url) {
-            if (preg_match('/^(\/\/)(.*?)(\/.*|\?.*|#.*|$)/', $url, $matches)) {
-                $url = $matches[3];
-                return $matches[2];
-            }
-            if (preg_match('/(.*?)(\.\.\/|\.\/|\/|\?|#|$)(.*)/', $url, $matches)) {
-                $tlds = SimpleUrl::getAllTopLevelDomains();
-                if (preg_match('/[a-z0-9\-]+\.(' . $tlds . ')/i', $matches[1])) {
-                    $url = $matches[2] . $matches[3];
-                    return $matches[1];
-                } elseif (preg_match('/[a-z0-9\-]+\.[a-z0-9\-]+\.[a-z0-9\-]+/i', $matches[1])) {
-                    $url = $matches[2] . $matches[3];
-                    return $matches[1];
-                }
-            }
-            return false;
-        }
-        
-        /**
-         *    Extracts the path information from the incoming
-         *    URL. Strips this path from the URL.
-         *    @param string $url     URL so far. The host will be
-         *                           removed.
-         *    @return string         Path part or '/'.
-         *    @access private
-         */
-        function _chompPath(&$url) {
-            if (preg_match('/(.*?)(\?|#|$)(.*)/', $url, $matches)) {
-                $url = $matches[2] . $matches[3];
-                return ($matches[1] ? $matches[1] : '');
-            }
-            return '';
-        }
-        
-        /**
-         *    Strips off the request data.
-         *    @param string $url  URL so far. The request will be
-         *                        removed.
-         *    @return string      Raw request part.
-         *    @access private
-         */
-        function _chompRequest(&$url) {
-            if (preg_match('/\?(.*?)(#|$)(.*)/', $url, $matches)) {
+            } elseif (preg_match('/[a-z0-9\-]+\.[a-z0-9\-]+\.[a-z0-9\-]+/i', $matches[1])) {
                 $url = $matches[2] . $matches[3];
                 return $matches[1];
             }
-            return '';
-        }
-         
-        /**
-         *    Breaks the request down into an object.
-         *    @param string $raw           Raw request.
-         *    @return SimpleFormEncoding    Parsed data.
-         *    @access private
-         */
-        function _parseRequest($raw) {
-            $this->_raw = $raw;
-            $request = new SimpleGetEncoding();
-            foreach (split("&", $raw) as $pair) {
-                if (preg_match('/(.*?)=(.*)/', $pair, $matches)) {
-                    $request->add($matches[1], urldecode($matches[2]));
-                } elseif ($pair) {
-                    $request->add($pair, '');
-                }
-            }
-            return $request;
-        }
-        
-        /**
-         *    Accessor for protocol part.
-         *    @param string $default    Value to use if not present.
-         *    @return string            Scheme name, e.g "http".
-         *    @access public
-         */
-        function getScheme($default = false) {
-            return $this->_scheme ? $this->_scheme : $default;
-        }
-        
-        /**
-         *    Accessor for user name.
-         *    @return string    Username preceding host.
-         *    @access public
-         */
-        function getUsername() {
-            return $this->_username;
-        }
-        
-        /**
-         *    Accessor for password.
-         *    @return string    Password preceding host.
-         *    @access public
-         */
-        function getPassword() {
-            return $this->_password;
-        }
-        
-        /**
-         *    Accessor for hostname and port.
-         *    @param string $default    Value to use if not present.
-         *    @return string            Hostname only.
-         *    @access public
-         */
-        function getHost($default = false) {
-            return $this->_host ? $this->_host : $default;
-        }
-        
-        /**
-         *    Accessor for top level domain.
-         *    @return string       Last part of host.
-         *    @access public
-         */
-        function getTld() {
-            $path_parts = pathinfo($this->getHost());
-            return (isset($path_parts['extension']) ? $path_parts['extension'] : false);
-        }
-        
-        /**
-         *    Accessor for port number.
-         *    @return integer    TCP/IP port number.
-         *    @access public
-         */
-        function getPort() {
-            return $this->_port;
-        }        
-                
-       /**
-         *    Accessor for path.
-         *    @return string    Full path including leading slash if implied.
-         *    @access public
-         */
-        function getPath() {
-            if (! $this->_path && $this->_host) {
-                return '/';
-            }
-            return $this->_path;
         }
-        
-        /**
-         *    Accessor for page if any. This may be a
-         *    directory name if ambiguious.
-         *    @return            Page name.
-         *    @access public
-         */
-        function getPage() {
-            if (! preg_match('/([^\/]*?)$/', $this->getPath(), $matches)) {
-                return false;
-            }
-            return $matches[1];
+        return false;
+    }
+    
+    /**
+     *    Extracts the path information from the incoming
+     *    URL. Strips this path from the URL.
+     *    @param string $url     URL so far. The host will be
+     *                           removed.
+     *    @return string         Path part or '/'.
+     *    @access private
+     */
+    function _chompPath(&$url) {
+        if (preg_match('/(.*?)(\?|#|$)(.*)/', $url, $matches)) {
+            $url = $matches[2] . $matches[3];
+            return ($matches[1] ? $matches[1] : '');
         }
-        
-        /**
-         *    Gets the path to the page.
-         *    @return string       Path less the page.
-         *    @access public
-         */
-        function getBasePath() {
-            if (! preg_match('/(.*\/)[^\/]*?$/', $this->getPath(), $matches)) {
-                return false;
-            }
+        return '';
+    }
+    
+    /**
+     *    Strips off the request data.
+     *    @param string $url  URL so far. The request will be
+     *                        removed.
+     *    @return string      Raw request part.
+     *    @access private
+     */
+    function _chompRequest(&$url) {
+        if (preg_match('/\?(.*?)(#|$)(.*)/', $url, $matches)) {
+            $url = $matches[2] . $matches[3];
             return $matches[1];
         }
+        return '';
+    }
         
-        /**
-         *    Accessor for fragment at end of URL after the "#".
-         *    @return string    Part after "#".
-         *    @access public
-         */
-        function getFragment() {
-            return $this->_fragment;
-        }
-        
-        /**
-         *    Sets image coordinates. Set to false to clear
-         *    them.
-         *    @param integer $x    Horizontal position.
-         *    @param integer $y    Vertical position.
-         *    @access public
-         */
-        function setCoordinates($x = false, $y = false) {
-            if (($x === false) || ($y === false)) {
-                $this->_x = $this->_y = false;
-                return;
-            }
-            $this->_x = (integer)$x;
-            $this->_y = (integer)$y;
-        }
-        
-        /**
-         *    Accessor for horizontal image coordinate.
-         *    @return integer        X value.
-         *    @access public
-         */
-        function getX() {
-            return $this->_x;
-        }
-         
-        /**
-         *    Accessor for vertical image coordinate.
-         *    @return integer        Y value.
-         *    @access public
-         */
-        function getY() {
-            return $this->_y;
-        }
-       
-        /**
-         *    Accessor for current request parameters
-         *    in URL string form. Will return teh original request
-         *    if at all possible even if it doesn't make much
-         *    sense.
-         *    @return string   Form is string "?a=1&b=2", etc.
-         *    @access public
-         */
-        function getEncodedRequest() {
-            if ($this->_raw) {
-                $encoded = $this->_raw;
-            } else {
-                $encoded = $this->_request->asUrlRequest();
-            }
-            if ($encoded) {
-                return '?' . preg_replace('/^\?/', '', $encoded);
+    /**
+     *    Breaks the request down into an object.
+     *    @param string $raw           Raw request.
+     *    @return SimpleFormEncoding    Parsed data.
+     *    @access private
+     */
+    function _parseRequest($raw) {
+        $this->_raw = $raw;
+        $request = new SimpleGetEncoding();
+        foreach (split("&", $raw) as $pair) {
+            if (preg_match('/(.*?)=(.*)/', $pair, $matches)) {
+                $request->add($matches[1], urldecode($matches[2]));
+            } elseif ($pair) {
+                $request->add($pair, '');
             }
-            return '';
         }
-        
-        /**
-         *    Adds an additional parameter to the request.
-         *    @param string $key            Name of parameter.
-         *    @param string $value          Value as string.
-         *    @access public
-         */
-        function addRequestParameter($key, $value) {
-            $this->_raw = false;
-            $this->_request->add($key, $value);
+        return $request;
+    }
+    
+    /**
+     *    Accessor for protocol part.
+     *    @param string $default    Value to use if not present.
+     *    @return string            Scheme name, e.g "http".
+     *    @access public
+     */
+    function getScheme($default = false) {
+        return $this->_scheme ? $this->_scheme : $default;
+    }
+    
+    /**
+     *    Accessor for user name.
+     *    @return string    Username preceding host.
+     *    @access public
+     */
+    function getUsername() {
+        return $this->_username;
+    }
+    
+    /**
+     *    Accessor for password.
+     *    @return string    Password preceding host.
+     *    @access public
+     */
+    function getPassword() {
+        return $this->_password;
+    }
+    
+    /**
+     *    Accessor for hostname and port.
+     *    @param string $default    Value to use if not present.
+     *    @return string            Hostname only.
+     *    @access public
+     */
+    function getHost($default = false) {
+        return $this->_host ? $this->_host : $default;
+    }
+    
+    /**
+     *    Accessor for top level domain.
+     *    @return string       Last part of host.
+     *    @access public
+     */
+    function getTld() {
+        $path_parts = pathinfo($this->getHost());
+        return (isset($path_parts['extension']) ? $path_parts['extension'] : false);
+    }
+    
+    /**
+     *    Accessor for port number.
+     *    @return integer    TCP/IP port number.
+     *    @access public
+     */
+    function getPort() {
+        return $this->_port;
+    }        
+            
+    /**
+     *    Accessor for path.
+     *    @return string    Full path including leading slash if implied.
+     *    @access public
+     */
+    function getPath() {
+        if (! $this->_path && $this->_host) {
+            return '/';
         }
-        
-        /**
-         *    Adds additional parameters to the request.
-         *    @param hash/SimpleFormEncoding $parameters   Additional
-         *                                                parameters.
-         *    @access public
-         */
-        function addRequestParameters($parameters) {
-            $this->_raw = false;
-            $this->_request->merge($parameters);
+        return $this->_path;
+    }
+    
+    /**
+     *    Accessor for page if any. This may be a
+     *    directory name if ambiguious.
+     *    @return            Page name.
+     *    @access public
+     */
+    function getPage() {
+        if (! preg_match('/([^\/]*?)$/', $this->getPath(), $matches)) {
+            return false;
         }
-        
-        /**
-         *    Clears down all parameters.
-         *    @access public
-         */
-        function clearRequest() {
-            $this->_raw = false;
-            $this->_request = &new SimpleGetEncoding();
+        return $matches[1];
+    }
+    
+    /**
+     *    Gets the path to the page.
+     *    @return string       Path less the page.
+     *    @access public
+     */
+    function getBasePath() {
+        if (! preg_match('/(.*\/)[^\/]*?$/', $this->getPath(), $matches)) {
+            return false;
         }
-        
-        /**
-         *    Gets the frame target if present. Although
-         *    not strictly part of the URL specification it
-         *    acts as similarily to the browser.
-         *    @return boolean/string    Frame name or false if none.
-         *    @access public
-         */
-        function getTarget() {
-            return $this->_target;
+        return $matches[1];
+    }
+    
+    /**
+     *    Accessor for fragment at end of URL after the "#".
+     *    @return string    Part after "#".
+     *    @access public
+     */
+    function getFragment() {
+        return $this->_fragment;
+    }
+    
+    /**
+     *    Sets image coordinates. Set to false to clear
+     *    them.
+     *    @param integer $x    Horizontal position.
+     *    @param integer $y    Vertical position.
+     *    @access public
+     */
+    function setCoordinates($x = false, $y = false) {
+        if (($x === false) || ($y === false)) {
+            $this->_x = $this->_y = false;
+            return;
         }
+        $this->_x = (integer)$x;
+        $this->_y = (integer)$y;
+    }
+    
+    /**
+     *    Accessor for horizontal image coordinate.
+     *    @return integer        X value.
+     *    @access public
+     */
+    function getX() {
+        return $this->_x;
+    }
         
-        /**
-         *    Attaches a frame target.
-         *    @param string $frame        Name of frame.
-         *    @access public
-         */
-        function setTarget($frame) {
-            $this->_raw = false;
-            $this->_target = $frame;
+    /**
+     *    Accessor for vertical image coordinate.
+     *    @return integer        Y value.
+     *    @access public
+     */
+    function getY() {
+        return $this->_y;
+    }
+    
+    /**
+     *    Accessor for current request parameters
+     *    in URL string form. Will return teh original request
+     *    if at all possible even if it doesn't make much
+     *    sense.
+     *    @return string   Form is string "?a=1&b=2", etc.
+     *    @access public
+     */
+    function getEncodedRequest() {
+        if ($this->_raw) {
+            $encoded = $this->_raw;
+        } else {
+            $encoded = $this->_request->asUrlRequest();
         }
-        
-        /**
-         *    Renders the URL back into a string.
-         *    @return string        URL in canonical form.
-         *    @access public
-         */
-        function asString() {
-            $scheme = $identity = $host = $path = $encoded = $fragment = '';
-            if ($this->_username && $this->_password) {
-                $identity = $this->_username . ':' . $this->_password . '@';
-            }
-            if ($this->getHost()) {
-                $scheme = $this->getScheme() ? $this->getScheme() : 'http';
-                $host = $this->getHost();
-            }
-            if (substr($this->_path, 0, 1) == '/') {
-                $path = $this->normalisePath($this->_path);
-            }
-            $encoded = $this->getEncodedRequest();
-            $fragment = $this->getFragment() ? '#'. $this->getFragment() : '';
-            $coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY();
-            return "$scheme://$identity$host$path$encoded$fragment$coords";
+        if ($encoded) {
+            return '?' . preg_replace('/^\?/', '', $encoded);
         }
-        
-        /**
-         *    Replaces unknown sections to turn a relative
-         *    URL into an absolute one. The base URL can
-         *    be either a string or a SimpleUrl object.
-         *    @param string/SimpleUrl $base       Base URL.
-         *    @access public
-         */
-        function makeAbsolute($base) {
-            if (! is_object($base)) {
-                $base = new SimpleUrl($base);
-            }
-            $scheme = $this->getScheme() ? $this->getScheme() : $base->getScheme();
-            if ($this->getHost()) {
-                $host = $this->getHost();
-                $port = $this->getPort() ? ':' . $this->getPort() : '';
-                $identity = $this->getIdentity() ? $this->getIdentity() . '@' : '';
-                if (! $identity) {
-                    $identity = $base->getIdentity() ? $base->getIdentity() . '@' : '';
-                }
-            } else {
-                $host = $base->getHost();
-                $port = $base->getPort() ? ':' . $base->getPort() : '';
+        return '';
+    }
+    
+    /**
+     *    Adds an additional parameter to the request.
+     *    @param string $key            Name of parameter.
+     *    @param string $value          Value as string.
+     *    @access public
+     */
+    function addRequestParameter($key, $value) {
+        $this->_raw = false;
+        $this->_request->add($key, $value);
+    }
+    
+    /**
+     *    Adds additional parameters to the request.
+     *    @param hash/SimpleFormEncoding $parameters   Additional
+     *                                                parameters.
+     *    @access public
+     */
+    function addRequestParameters($parameters) {
+        $this->_raw = false;
+        $this->_request->merge($parameters);
+    }
+    
+    /**
+     *    Clears down all parameters.
+     *    @access public
+     */
+    function clearRequest() {
+        $this->_raw = false;
+        $this->_request = &new SimpleGetEncoding();
+    }
+    
+    /**
+     *    Gets the frame target if present. Although
+     *    not strictly part of the URL specification it
+     *    acts as similarily to the browser.
+     *    @return boolean/string    Frame name or false if none.
+     *    @access public
+     */
+    function getTarget() {
+        return $this->_target;
+    }
+    
+    /**
+     *    Attaches a frame target.
+     *    @param string $frame        Name of frame.
+     *    @access public
+     */
+    function setTarget($frame) {
+        $this->_raw = false;
+        $this->_target = $frame;
+    }
+    
+    /**
+     *    Renders the URL back into a string.
+     *    @return string        URL in canonical form.
+     *    @access public
+     */
+    function asString() {
+        $path = $this->_path;
+        $scheme = $identity = $host = $encoded = $fragment = '';
+        if ($this->_username && $this->_password) {
+            $identity = $this->_username . ':' . $this->_password . '@';
+        }
+        if ($this->getHost()) {
+            $scheme = $this->getScheme() ? $this->getScheme() : 'http';
+            $scheme .= "://";
+            $host = $this->getHost();
+        }
+        if (substr($this->_path, 0, 1) == '/') {
+            $path = $this->normalisePath($this->_path);
+        }
+        $encoded = $this->getEncodedRequest();
+        $fragment = $this->getFragment() ? '#'. $this->getFragment() : '';
+        $coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY();
+        return "$scheme$identity$host$path$encoded$fragment$coords";
+    }
+    
+    /**
+     *    Replaces unknown sections to turn a relative
+     *    URL into an absolute one. The base URL can
+     *    be either a string or a SimpleUrl object.
+     *    @param string/SimpleUrl $base       Base URL.
+     *    @access public
+     */
+    function makeAbsolute($base) {
+        if (! is_object($base)) {
+            $base = new SimpleUrl($base);
+        }
+        if ($this->getHost()) {
+            $scheme = $this->getScheme();
+            $host = $this->getHost();
+            $port = $this->getPort() ? ':' . $this->getPort() : '';
+            $identity = $this->getIdentity() ? $this->getIdentity() . '@' : '';
+            if (! $identity) {
                 $identity = $base->getIdentity() ? $base->getIdentity() . '@' : '';
             }
-            $path = $this->normalisePath($this->_extractAbsolutePath($base));
-            $encoded = $this->getEncodedRequest();
-            $fragment = $this->getFragment() ? '#'. $this->getFragment() : '';
-            $coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY();
-            return new SimpleUrl("$scheme://$identity$host$port$path$encoded$fragment$coords");
-        }
-        
-        /**
-         *    Replaces unknown sections of the path with base parts
-         *    to return a complete absolute one.
-         *    @param string/SimpleUrl $base       Base URL.
-         *    @param string                       Absolute path.
-         *    @access private
-         */
-        function _extractAbsolutePath($base) {
-            if ($this->getHost()) {
-                return $this->_path;
-            }
-            if (! $this->_isRelativePath($this->_path)) {
-                return $this->_path;
-            }
-            if ($this->_path) {
-                return $base->getBasePath() . $this->_path;
-            }
-            return $base->getPath();
-        }
-        
-        /**
-         *    Simple test to see if a path part is relative.
-         *    @param string $path        Path to test.
-         *    @return boolean            True if starts with a "/".
-         *    @access private
-         */
-        function _isRelativePath($path) {
-            return (substr($path, 0, 1) != '/');
+        } else {
+            $scheme = $base->getScheme();
+            $host = $base->getHost();
+            $port = $base->getPort() ? ':' . $base->getPort() : '';
+            $identity = $base->getIdentity() ? $base->getIdentity() . '@' : '';
+        }
+        $path = $this->normalisePath($this->_extractAbsolutePath($base));
+        $encoded = $this->getEncodedRequest();
+        $fragment = $this->getFragment() ? '#'. $this->getFragment() : '';
+        $coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY();
+        return new SimpleUrl("$scheme://$identity$host$port$path$encoded$fragment$coords");
+    }
+    
+    /**
+     *    Replaces unknown sections of the path with base parts
+     *    to return a complete absolute one.
+     *    @param string/SimpleUrl $base       Base URL.
+     *    @param string                       Absolute path.
+     *    @access private
+     */
+    function _extractAbsolutePath($base) {
+        if ($this->getHost()) {
+            return $this->_path;
         }
-        
-        /**
-         *    Extracts the username and password for use in rendering
-         *    a URL.
-         *    @return string/boolean    Form of username:password or false.
-         *    @access public
-         */
-        function getIdentity() {
-            if ($this->_username && $this->_password) {
-                return $this->_username . ':' . $this->_password;
-            }
-            return false;
+        if (! $this->_isRelativePath($this->_path)) {
+            return $this->_path;
         }
-        
-        /**
-         *    Replaces . and .. sections of the path.
-         *    @param string $path    Unoptimised path.
-         *    @return string         Path with dots removed if possible.
-         *    @access public
-         */
-        function normalisePath($path) {
-            $path = preg_replace('|/\./|', '/', $path);
-            return preg_replace('|/[^/]+/\.\./|', '/', $path);
+        if ($this->_path) {
+            return $base->getBasePath() . $this->_path;
         }
-        
-        /**
-         *    A pipe seperated list of all TLDs that result in two part
-         *    domain names.
-         *    @return string        Pipe separated list.
-         *    @access public
-         *    @static
-         */
-        function getAllTopLevelDomains() {
-            return 'com|edu|net|org|gov|mil|int|biz|info|name|pro|aero|coop|museum';
+        return $base->getPath();
+    }
+    
+    /**
+     *    Simple test to see if a path part is relative.
+     *    @param string $path        Path to test.
+     *    @return boolean            True if starts with a "/".
+     *    @access private
+     */
+    function _isRelativePath($path) {
+        return (substr($path, 0, 1) != '/');
+    }
+    
+    /**
+     *    Extracts the username and password for use in rendering
+     *    a URL.
+     *    @return string/boolean    Form of username:password or false.
+     *    @access public
+     */
+    function getIdentity() {
+        if ($this->_username && $this->_password) {
+            return $this->_username . ':' . $this->_password;
         }
+        return false;
+    }
+    
+    /**
+     *    Replaces . and .. sections of the path.
+     *    @param string $path    Unoptimised path.
+     *    @return string         Path with dots removed if possible.
+     *    @access public
+     */
+    function normalisePath($path) {
+        $path = preg_replace('|/\./|', '/', $path);
+        return preg_replace('|/[^/]+/\.\./|', '/', $path);
+    }
+    
+    /**
+     *    A pipe seperated list of all TLDs that result in two part
+     *    domain names.
+     *    @return string        Pipe separated list.
+     *    @access public
+     *    @static
+     */
+    function getAllTopLevelDomains() {
+        return 'com|edu|net|org|gov|mil|int|biz|info|name|pro|aero|coop|museum';
     }
+}
 ?>
\ No newline at end of file
index a5a6fd20870fc506ceeea4c6e0e1dd49321f1834..5e02beded0e8f7fea8625efd193a4af7ad4f0c9c 100644 (file)
 <?php
+/**
+ *  Base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage WebTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ *  include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/cookies.php');
+require_once(dirname(__FILE__) . '/http.php');
+require_once(dirname(__FILE__) . '/encoding.php');
+require_once(dirname(__FILE__) . '/authentication.php');
+/**#@-*/
+
+if (! defined('DEFAULT_MAX_REDIRECTS')) {
+    define('DEFAULT_MAX_REDIRECTS', 3);
+}
+if (! defined('DEFAULT_CONNECTION_TIMEOUT')) {
+    define('DEFAULT_CONNECTION_TIMEOUT', 15);
+}
+
+/**
+ *    Fetches web pages whilst keeping track of
+ *    cookies and authentication.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class SimpleUserAgent {
+    var $_cookie_jar;
+    var $_cookies_enabled = true;
+    var $_authenticator;
+    var $_max_redirects = DEFAULT_MAX_REDIRECTS;
+    var $_proxy = false;
+    var $_proxy_username = false;
+    var $_proxy_password = false;
+    var $_connection_timeout = DEFAULT_CONNECTION_TIMEOUT;
+    var $_additional_headers = array();
+    
     /**
-     * Base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     WebTester
-     * @version        $Id$
+     *    Starts with no cookies, realms or proxies.
+     *    @access public
      */
-
-    /**#@+
-     * include other SimpleTest class files
+    function SimpleUserAgent() {
+        $this->_cookie_jar = &new SimpleCookieJar();
+        $this->_authenticator = &new SimpleAuthenticator();
+    }
+    
+    /**
+     *    Removes expired and temporary cookies as if
+     *    the browser was closed and re-opened. Authorisation
+     *    has to be obtained again as well.
+     *    @param string/integer $date   Time when session restarted.
+     *                                  If omitted then all persistent
+     *                                  cookies are kept.
+     *    @access public
      */
-    require_once(dirname(__FILE__) . '/cookies.php');
-    require_once(dirname(__FILE__) . '/http.php');
-    require_once(dirname(__FILE__) . '/encoding.php');
-    require_once(dirname(__FILE__) . '/authentication.php');
-    /**#@-*/
-   
-    if (! defined('DEFAULT_MAX_REDIRECTS')) {
-        define('DEFAULT_MAX_REDIRECTS', 3);
+    function restart($date = false) {
+        $this->_cookie_jar->restartSession($date);
+        $this->_authenticator->restartSession();
     }
     
-    if (! defined('DEFAULT_CONNECTION_TIMEOUT')) {
-        define('DEFAULT_CONNECTION_TIMEOUT', 15);
+    /**
+     *    Adds a header to every fetch.
+     *    @param string $header       Header line to add to every
+     *                                request until cleared.
+     *    @access public
+     */
+    function addHeader($header) {
+        $this->_additional_headers[] = $header;
     }
-
+    
     /**
-     *    Fetches web pages whilst keeping track of
-     *    cookies and authentication.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Ages the cookies by the specified time.
+     *    @param integer $interval    Amount in seconds.
+     *    @access public
      */
-    class SimpleUserAgent {
-        var $_cookie_jar;
-        var $_cookies_enabled = true;
-        var $_authenticator;
-        var $_max_redirects = DEFAULT_MAX_REDIRECTS;
-        var $_proxy = false;
-        var $_proxy_username = false;
-        var $_proxy_password = false;
-        var $_connection_timeout = DEFAULT_CONNECTION_TIMEOUT;
-        var $_additional_headers = array();
-        
-        /**
-         *    Starts with no cookies, realms or proxies.
-         *    @access public
-         */
-        function SimpleUserAgent() {
-            $this->_cookie_jar = &new SimpleCookieJar();
-            $this->_authenticator = &new SimpleAuthenticator();
-        }
-        
-        /**
-         *    Removes expired and temporary cookies as if
-         *    the browser was closed and re-opened. Authorisation
-         *    has to be obtained again as well.
-         *    @param string/integer $date   Time when session restarted.
-         *                                  If omitted then all persistent
-         *                                  cookies are kept.
-         *    @access public
-         */
-        function restart($date = false) {
-            $this->_cookie_jar->restartSession($date);
-            $this->_authenticator->restartSession();
-        }
-        
-        /**
-         *    Adds a header to every fetch.
-         *    @param string $header       Header line to add to every
-         *                                request until cleared.
-         *    @access public
-         */
-        function addHeader($header) {
-            $this->_additional_headers[] = $header;
-        }
-        
-        /**
-         *    Ages the cookies by the specified time.
-         *    @param integer $interval    Amount in seconds.
-         *    @access public
-         */
-        function ageCookies($interval) {
-            $this->_cookie_jar->agePrematurely($interval);
-        }
-        
-        /**
-         *    Sets an additional cookie. If a cookie has
-         *    the same name and path it is replaced.
-         *    @param string $name            Cookie key.
-         *    @param string $value           Value of cookie.
-         *    @param string $host            Host upon which the cookie is valid.
-         *    @param string $path            Cookie path if not host wide.
-         *    @param string $expiry          Expiry date.
-         *    @access public
-         */
-        function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
-            $this->_cookie_jar->setCookie($name, $value, $host, $path, $expiry);
-        }
-        
-        /**
-         *    Reads the most specific cookie value from the
-         *    browser cookies.
-         *    @param string $host        Host to search.
-         *    @param string $path        Applicable path.
-         *    @param string $name        Name of cookie to read.
-         *    @return string             False if not present, else the
-         *                               value as a string.
-         *    @access public
-         */
-        function getCookieValue($host, $path, $name) {
-            return $this->_cookie_jar->getCookieValue($host, $path, $name);
-        }
-        
-        /**
-         *    Reads the current cookies within the base URL.
-         *    @param string $name     Key of cookie to find.
-         *    @param SimpleUrl $base  Base URL to search from.
-         *    @return string/boolean  Null if there is no base URL, false
-         *                            if the cookie is not set.
-         *    @access public
-         */
-        function getBaseCookieValue($name, $base) {
-            if (! $base) {
-                return null;
-            }
-            return $this->getCookieValue($base->getHost(), $base->getPath(), $name);
-        }
-        
-        /**
-         *    Switches off cookie sending and recieving.
-         *    @access public
-         */
-        function ignoreCookies() {
-            $this->_cookies_enabled = false;
+    function ageCookies($interval) {
+        $this->_cookie_jar->agePrematurely($interval);
+    }
+    
+    /**
+     *    Sets an additional cookie. If a cookie has
+     *    the same name and path it is replaced.
+     *    @param string $name            Cookie key.
+     *    @param string $value           Value of cookie.
+     *    @param string $host            Host upon which the cookie is valid.
+     *    @param string $path            Cookie path if not host wide.
+     *    @param string $expiry          Expiry date.
+     *    @access public
+     */
+    function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
+        $this->_cookie_jar->setCookie($name, $value, $host, $path, $expiry);
+    }
+    
+    /**
+     *    Reads the most specific cookie value from the
+     *    browser cookies.
+     *    @param string $host        Host to search.
+     *    @param string $path        Applicable path.
+     *    @param string $name        Name of cookie to read.
+     *    @return string             False if not present, else the
+     *                               value as a string.
+     *    @access public
+     */
+    function getCookieValue($host, $path, $name) {
+        return $this->_cookie_jar->getCookieValue($host, $path, $name);
+    }
+    
+    /**
+     *    Reads the current cookies within the base URL.
+     *    @param string $name     Key of cookie to find.
+     *    @param SimpleUrl $base  Base URL to search from.
+     *    @return string/boolean  Null if there is no base URL, false
+     *                            if the cookie is not set.
+     *    @access public
+     */
+    function getBaseCookieValue($name, $base) {
+        if (! $base) {
+            return null;
         }
-        
-        /**
-         *    Switches back on the cookie sending and recieving.
-         *    @access public
-         */
-        function useCookies() {
-            $this->_cookies_enabled = true;
+        return $this->getCookieValue($base->getHost(), $base->getPath(), $name);
+    }
+    
+    /**
+     *    Switches off cookie sending and recieving.
+     *    @access public
+     */
+    function ignoreCookies() {
+        $this->_cookies_enabled = false;
+    }
+    
+    /**
+     *    Switches back on the cookie sending and recieving.
+     *    @access public
+     */
+    function useCookies() {
+        $this->_cookies_enabled = true;
+    }
+    
+    /**
+     *    Sets the socket timeout for opening a connection.
+     *    @param integer $timeout      Maximum time in seconds.
+     *    @access public
+     */
+    function setConnectionTimeout($timeout) {
+        $this->_connection_timeout = $timeout;
+    }
+    
+    /**
+     *    Sets the maximum number of redirects before
+     *    a page will be loaded anyway.
+     *    @param integer $max        Most hops allowed.
+     *    @access public
+     */
+    function setMaximumRedirects($max) {
+        $this->_max_redirects = $max;
+    }
+    
+    /**
+     *    Sets proxy to use on all requests for when
+     *    testing from behind a firewall. Set URL
+     *    to false to disable.
+     *    @param string $proxy        Proxy URL.
+     *    @param string $username     Proxy username for authentication.
+     *    @param string $password     Proxy password for authentication.
+     *    @access public
+     */
+    function useProxy($proxy, $username, $password) {
+        if (! $proxy) {
+            $this->_proxy = false;
+            return;
         }
-        
-        /**
-         *    Sets the socket timeout for opening a connection.
-         *    @param integer $timeout      Maximum time in seconds.
-         *    @access public
-         */
-        function setConnectionTimeout($timeout) {
-            $this->_connection_timeout = $timeout;
+        if ((strncmp($proxy, 'http://', 7) != 0) && (strncmp($proxy, 'https://', 8) != 0)) {
+            $proxy = 'http://'. $proxy;
         }
-        
-        /**
-         *    Sets the maximum number of redirects before
-         *    a page will be loaded anyway.
-         *    @param integer $max        Most hops allowed.
-         *    @access public
-         */
-        function setMaximumRedirects($max) {
-            $this->_max_redirects = $max;
+        $this->_proxy = &new SimpleUrl($proxy);
+        $this->_proxy_username = $username;
+        $this->_proxy_password = $password;
+    }
+    
+    /**
+     *    Test to see if the redirect limit is passed.
+     *    @param integer $redirects        Count so far.
+     *    @return boolean                  True if over.
+     *    @access private
+     */
+    function _isTooManyRedirects($redirects) {
+        return ($redirects > $this->_max_redirects);
+    }
+    
+    /**
+     *    Sets the identity for the current realm.
+     *    @param string $host        Host to which realm applies.
+     *    @param string $realm       Full name of realm.
+     *    @param string $username    Username for realm.
+     *    @param string $password    Password for realm.
+     *    @access public
+     */
+    function setIdentity($host, $realm, $username, $password) {
+        $this->_authenticator->setIdentityForRealm($host, $realm, $username, $password);
+    }
+    
+    /**
+     *    Fetches a URL as a response object. Will keep trying if redirected.
+     *    It will also collect authentication realm information.
+     *    @param string/SimpleUrl $url      Target to fetch.
+     *    @param SimpleEncoding $encoding   Additional parameters for request.
+     *    @return SimpleHttpResponse        Hopefully the target page.
+     *    @access public
+     */
+    function &fetchResponse($url, $encoding) {
+        if ($encoding->getMethod() != 'POST') {
+            $url->addRequestParameters($encoding);
+            $encoding->clear();
         }
-        
-        /**
-         *    Sets proxy to use on all requests for when
-         *    testing from behind a firewall. Set URL
-         *    to false to disable.
-         *    @param string $proxy        Proxy URL.
-         *    @param string $username     Proxy username for authentication.
-         *    @param string $password     Proxy password for authentication.
-         *    @access public
-         */
-        function useProxy($proxy, $username, $password) {
-            if (! $proxy) {
-                $this->_proxy = false;
-                return;
-            }
-            if ((strncmp($proxy, 'http://', 7) != 0) && (strncmp($proxy, 'https://', 8) != 0)) {
-                $proxy = 'http://'. $proxy;
+        $response = &$this->_fetchWhileRedirected($url, $encoding);
+        if ($headers = $response->getHeaders()) {
+            if ($headers->isChallenge()) {
+                $this->_authenticator->addRealm(
+                        $url,
+                        $headers->getAuthentication(),
+                        $headers->getRealm());
             }
-            $this->_proxy = &new SimpleUrl($proxy);
-            $this->_proxy_username = $username;
-            $this->_proxy_password = $password;
-        }
-        
-        /**
-         *    Test to see if the redirect limit is passed.
-         *    @param integer $redirects        Count so far.
-         *    @return boolean                  True if over.
-         *    @access private
-         */
-        function _isTooManyRedirects($redirects) {
-            return ($redirects > $this->_max_redirects);
         }
-        
-        /**
-         *    Sets the identity for the current realm.
-         *    @param string $host        Host to which realm applies.
-         *    @param string $realm       Full name of realm.
-         *    @param string $username    Username for realm.
-         *    @param string $password    Password for realm.
-         *    @access public
-         */
-        function setIdentity($host, $realm, $username, $password) {
-            $this->_authenticator->setIdentityForRealm($host, $realm, $username, $password);
-        }
-        
-        /**
-         *    Fetches a URL as a response object. Will keep trying if redirected.
-         *    It will also collect authentication realm information.
-         *    @param string/SimpleUrl $url      Target to fetch.
-         *    @param SimpleEncoding $encoding   Additional parameters for request.
-         *    @return SimpleHttpResponse        Hopefully the target page.
-         *    @access public
-         */
-        function &fetchResponse($url, $encoding) {
-            if ($encoding->getMethod() != 'POST') {
-                $url->addRequestParameters($encoding);
-                $encoding->clear();
-            }
-            $response = &$this->_fetchWhileRedirected($url, $encoding);
-            if ($headers = $response->getHeaders()) {
-                if ($headers->isChallenge()) {
-                    $this->_authenticator->addRealm(
-                            $url,
-                            $headers->getAuthentication(),
-                            $headers->getRealm());
-                }
+        return $response;
+    }
+    
+    /**
+     *    Fetches the page until no longer redirected or
+     *    until the redirect limit runs out.
+     *    @param SimpleUrl $url                  Target to fetch.
+     *    @param SimpelFormEncoding $encoding    Additional parameters for request.
+     *    @return SimpleHttpResponse             Hopefully the target page.
+     *    @access private
+     */
+    function &_fetchWhileRedirected($url, $encoding) {
+        $redirects = 0;
+        do {
+            $response = &$this->_fetch($url, $encoding);
+            if ($response->isError()) {
+                return $response;
             }
-            return $response;
-        }
-        
-        /**
-         *    Fetches the page until no longer redirected or
-         *    until the redirect limit runs out.
-         *    @param SimpleUrl $url                  Target to fetch.
-         *    @param SimpelFormEncoding $encoding    Additional parameters for request.
-         *    @return SimpleHttpResponse             Hopefully the target page.
-         *    @access private
-         */
-        function &_fetchWhileRedirected($url, $encoding) {
-            $redirects = 0;
-            do {
-                $response = &$this->_fetch($url, $encoding);
-                if ($response->isError()) {
-                    return $response;
-                }
-                $headers = $response->getHeaders();
-                $location = new SimpleUrl($headers->getLocation());
-                $url = $location->makeAbsolute($url);
-                if ($this->_cookies_enabled) {
-                    $headers->writeCookiesToJar($this->_cookie_jar, $url);
-                }
-                if (! $headers->isRedirect()) {
-                    break;
-                }
-                $encoding = new SimpleGetEncoding();
-            } while (! $this->_isTooManyRedirects(++$redirects));
-            return $response;
-        }
-        
-        /**
-         *    Actually make the web request.
-         *    @param SimpleUrl $url                   Target to fetch.
-         *    @param SimpleFormEncoding $encoding     Additional parameters for request.
-         *    @return SimpleHttpResponse              Headers and hopefully content.
-         *    @access protected
-         */
-        function &_fetch($url, $encoding) {
-            $request = &$this->_createRequest($url, $encoding);
-            $response = &$request->fetch($this->_connection_timeout);
-            return $response;
-        }
-        
-        /**
-         *    Creates a full page request.
-         *    @param SimpleUrl $url                 Target to fetch as url object.
-         *    @param SimpleFormEncoding $encoding   POST/GET parameters.
-         *    @return SimpleHttpRequest             New request.
-         *    @access private
-         */
-        function &_createRequest($url, $encoding) {
-            $request = &$this->_createHttpRequest($url, $encoding);
-            $this->_addAdditionalHeaders($request);
+            $headers = $response->getHeaders();
+            $location = new SimpleUrl($headers->getLocation());
+            $url = $location->makeAbsolute($url);
             if ($this->_cookies_enabled) {
-                $request->readCookiesFromJar($this->_cookie_jar, $url);
+                $headers->writeCookiesToJar($this->_cookie_jar, $url);
             }
-            $this->_authenticator->addHeaders($request, $url);
-            return $request;
-        }
-        
-        /**
-         *    Builds the appropriate HTTP request object.
-         *    @param SimpleUrl $url                  Target to fetch as url object.
-         *    @param SimpleFormEncoding $parameters  POST/GET parameters.
-         *    @return SimpleHttpRequest              New request object.
-         *    @access protected
-         */
-        function &_createHttpRequest($url, $encoding) {
-            $request = &new SimpleHttpRequest($this->_createRoute($url), $encoding);
-            return $request;
-        }
-        
-        /**
-         *    Sets up either a direct route or via a proxy.
-         *    @param SimpleUrl $url   Target to fetch as url object.
-         *    @return SimpleRoute     Route to take to fetch URL.
-         *    @access protected
-         */
-        function &_createRoute($url) {
-            if ($this->_proxy) {
-                $route = &new SimpleProxyRoute(
-                        $url,
-                        $this->_proxy,
-                        $this->_proxy_username,
-                        $this->_proxy_password);
-            } else {
-                $route = &new SimpleRoute($url);
+            if (! $headers->isRedirect()) {
+                break;
             }
-            return $route;
+            $encoding = new SimpleGetEncoding();
+        } while (! $this->_isTooManyRedirects(++$redirects));
+        return $response;
+    }
+    
+    /**
+     *    Actually make the web request.
+     *    @param SimpleUrl $url                   Target to fetch.
+     *    @param SimpleFormEncoding $encoding     Additional parameters for request.
+     *    @return SimpleHttpResponse              Headers and hopefully content.
+     *    @access protected
+     */
+    function &_fetch($url, $encoding) {
+        $request = &$this->_createRequest($url, $encoding);
+        $response = &$request->fetch($this->_connection_timeout);
+        return $response;
+    }
+    
+    /**
+     *    Creates a full page request.
+     *    @param SimpleUrl $url                 Target to fetch as url object.
+     *    @param SimpleFormEncoding $encoding   POST/GET parameters.
+     *    @return SimpleHttpRequest             New request.
+     *    @access private
+     */
+    function &_createRequest($url, $encoding) {
+        $request = &$this->_createHttpRequest($url, $encoding);
+        $this->_addAdditionalHeaders($request);
+        if ($this->_cookies_enabled) {
+            $request->readCookiesFromJar($this->_cookie_jar, $url);
         }
-        
-        /**
-         *    Adds additional manual headers.
-         *    @param SimpleHttpRequest $request    Outgoing request.
-         *    @access private
-         */
-        function _addAdditionalHeaders(&$request) {
-            foreach ($this->_additional_headers as $header) {
-                $request->addHeaderLine($header);
-            }
+        $this->_authenticator->addHeaders($request, $url);
+        return $request;
+    }
+    
+    /**
+     *    Builds the appropriate HTTP request object.
+     *    @param SimpleUrl $url                  Target to fetch as url object.
+     *    @param SimpleFormEncoding $parameters  POST/GET parameters.
+     *    @return SimpleHttpRequest              New request object.
+     *    @access protected
+     */
+    function &_createHttpRequest($url, $encoding) {
+        $request = &new SimpleHttpRequest($this->_createRoute($url), $encoding);
+        return $request;
+    }
+    
+    /**
+     *    Sets up either a direct route or via a proxy.
+     *    @param SimpleUrl $url   Target to fetch as url object.
+     *    @return SimpleRoute     Route to take to fetch URL.
+     *    @access protected
+     */
+    function &_createRoute($url) {
+        if ($this->_proxy) {
+            $route = &new SimpleProxyRoute(
+                    $url,
+                    $this->_proxy,
+                    $this->_proxy_username,
+                    $this->_proxy_password);
+        } else {
+            $route = &new SimpleRoute($url);
+        }
+        return $route;
+    }
+    
+    /**
+     *    Adds additional manual headers.
+     *    @param SimpleHttpRequest $request    Outgoing request.
+     *    @access private
+     */
+    function _addAdditionalHeaders(&$request) {
+        foreach ($this->_additional_headers as $header) {
+            $request->addHeaderLine($header);
         }
     }
+}
 ?>
\ No newline at end of file
index be424bb175c4f531b5f1a5e6338d123e901469b3..c29eb64867bbd5ae788417d3e37f22edbae6aa0c 100644 (file)
 <?php
-    /**
    * Base include file for SimpleTest.
    * @package        SimpleTest
    * @subpackage     WebTester
    * @version        $Id$
    */
+/**
 Base include file for SimpleTest.
*  @package    SimpleTest
*  @subpackage WebTester
*  @version    $Id$
+ */
 
-    /**#@+
-     * include other SimpleTest class files
-     */
-    require_once(dirname(__FILE__) . '/test_case.php');
-    require_once(dirname(__FILE__) . '/browser.php');
-    require_once(dirname(__FILE__) . '/page.php');
-    require_once(dirname(__FILE__) . '/expectation.php');
-    /**#@-*/
+/**#@+
+ *  include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/test_case.php');
+require_once(dirname(__FILE__) . '/browser.php');
+require_once(dirname(__FILE__) . '/page.php');
+require_once(dirname(__FILE__) . '/expectation.php');
+/**#@-*/
+
+/**
+ *    Test for an HTML widget value match.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class FieldExpectation extends SimpleExpectation {
+    var $_value;
     
     /**
-     *    Test for an HTML widget value match.
-        *        @package SimpleTest
-        *        @subpackage WebTester
+     *    Sets the field value to compare against.
+     *    @param mixed $value     Test value to match. Can be an
+     *                            expectation for say pattern matching.
+     *    @param string $message  Optiona message override. Can use %s as
+     *                            a placeholder for the original message.
+     *    @access public
      */
-    class FieldExpectation extends SimpleExpectation {
-        var $_value;
-        
-        /**
-         *    Sets the field value to compare against.
-         *    @param mixed $value     Test value to match. Can be an
-         *                            expectation for say pattern matching.
-         *    @param string $message  Optiona message override. Can use %s as
-         *                            a placeholder for the original message.
-         *    @access public
-         */
-        function FieldExpectation($value, $message = '%s') {
-            $this->SimpleExpectation($message);
-            if (is_array($value)) {
-                sort($value);
-            }
-            $this->_value = $value;
-        }
-        
-        /**
-         *    Tests the expectation. True if it matches
-         *    a string value or an array value in any order.
-         *    @param mixed $compare        Comparison value. False for
-         *                                 an unset field.
-         *    @return boolean              True if correct.
-         *    @access public
-         */
-        function test($compare) {
-            if ($this->_value === false) {
-                return ($compare === false);
-            }
-            if ($this->_isSingle($this->_value)) {
-                return $this->_testSingle($compare);
-            }
-            if (is_array($this->_value)) {
-                return $this->_testMultiple($compare);
-            }
-            return false;
+    function FieldExpectation($value, $message = '%s') {
+        $this->SimpleExpectation($message);
+        if (is_array($value)) {
+            sort($value);
         }
-        
-        /**
-         *    Tests for valid field comparisons with a single option.
-         *    @param mixed $value       Value to type check.
-         *    @return boolean           True if integer, string or float.
-         *    @access private
-         */
-        function _isSingle($value) {
-            return is_string($value) || is_integer($value) || is_float($value);
-        }
-        
-        /**
-         *    String comparison for simple field with a single option.
-         *    @param mixed $compare    String to test against.
-         *    @returns boolean         True if matching.
-         *    @access private
-         */
-        function _testSingle($compare) {
-            if (is_array($compare) && count($compare) == 1) {
-                $compare = $compare[0];
-            }
-            if (! $this->_isSingle($compare)) {
-                return false;
-            }
-            return ($this->_value == $compare);
+        $this->_value = $value;
+    }
+    
+    /**
+     *    Tests the expectation. True if it matches
+     *    a string value or an array value in any order.
+     *    @param mixed $compare        Comparison value. False for
+     *                                 an unset field.
+     *    @return boolean              True if correct.
+     *    @access public
+     */
+    function test($compare) {
+        if ($this->_value === false) {
+            return ($compare === false);
         }
-        
-        /**
-         *    List comparison for multivalue field.
-         *    @param mixed $compare    List in any order to test against.
-         *    @returns boolean         True if matching.
-         *    @access private
-         */
-        function _testMultiple($compare) {
-            if (is_string($compare)) {
-                $compare = array($compare);
-            }
-            if (! is_array($compare)) {
-                return false;
-            }
-            sort($compare);
-            return ($this->_value === $compare);
+        if ($this->_isSingle($this->_value)) {
+            return $this->_testSingle($compare);
         }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            $dumper = &$this->_getDumper();
-            if (is_array($compare)) {
-                sort($compare);
-            }
-            if ($this->test($compare)) {
-                return "Field expectation [" . $dumper->describeValue($this->_value) . "]";
-            } else {
-                return "Field expectation [" . $dumper->describeValue($this->_value) .
-                        "] fails with [" .
-                        $dumper->describeValue($compare) . "] " .
-                        $dumper->describeDifference($this->_value, $compare);
-            }
+        if (is_array($this->_value)) {
+            return $this->_testMultiple($compare);
         }
+        return false;
     }
     
     /**
-     *    Test for a specific HTTP header within a header block.
-        *        @package SimpleTest
-        *        @subpackage WebTester
+     *    Tests for valid field comparisons with a single option.
+     *    @param mixed $value       Value to type check.
+     *    @return boolean           True if integer, string or float.
+     *    @access private
      */
-    class HttpHeaderExpectation extends SimpleExpectation {
-        var $_expected_header;
-        var $_expected_value;
-        
-        /**
-         *    Sets the field and value to compare against.
-         *    @param string $header   Case insenstive trimmed header name.
-         *    @param mixed $value     Optional value to compare. If not
-         *                            given then any value will match. If
-         *                            an expectation object then that will
-         *                            be used instead.
-         *    @param string $message  Optiona message override. Can use %s as
-         *                            a placeholder for the original message.
-         */
-        function HttpHeaderExpectation($header, $value = false, $message = '%s') {
-            $this->SimpleExpectation($message);
-            $this->_expected_header = $this->_normaliseHeader($header);
-            $this->_expected_value = $value;
-        }
-        
-        /**
-         *    Accessor for aggregated object.
-         *    @return mixed        Expectation set in constructor.
-         *    @access protected
-         */
-        function _getExpectation() {
-            return $this->_expected_value;
+    function _isSingle($value) {
+        return is_string($value) || is_integer($value) || is_float($value);
+    }
+    
+    /**
+     *    String comparison for simple field with a single option.
+     *    @param mixed $compare    String to test against.
+     *    @returns boolean         True if matching.
+     *    @access private
+     */
+    function _testSingle($compare) {
+        if (is_array($compare) && count($compare) == 1) {
+            $compare = $compare[0];
         }
-        
-        /**
-         *    Removes whitespace at ends and case variations.
-         *    @param string $header    Name of header.
-         *    @param string            Trimmed and lowecased header
-         *                             name.
-         *    @access private
-         */
-        function _normaliseHeader($header) {
-            return strtolower(trim($header));
+        if (! $this->_isSingle($compare)) {
+            return false;
         }
-        
-        /**
-         *    Tests the expectation. True if it matches
-         *    a string value or an array value in any order.
-         *    @param mixed $compare   Raw header block to search.
-         *    @return boolean         True if header present.
-         *    @access public
-         */
-        function test($compare) {
-            return is_string($this->_findHeader($compare));
+        return ($this->_value == $compare);
+    }
+    
+    /**
+     *    List comparison for multivalue field.
+     *    @param mixed $compare    List in any order to test against.
+     *    @returns boolean         True if matching.
+     *    @access private
+     */
+    function _testMultiple($compare) {
+        if (is_string($compare)) {
+            $compare = array($compare);
         }
-        
-        /**
-         *    Searches the incoming result. Will extract the matching
-         *    line as text.
-         *    @param mixed $compare   Raw header block to search.
-         *    @return string          Matching header line.
-         *    @access protected
-         */
-        function _findHeader($compare) {
-            $lines = split("\r\n", $compare);
-            foreach ($lines as $line) {
-                if ($this->_testHeaderLine($line)) {
-                    return $line;
-                }
-            }
+        if (! is_array($compare)) {
             return false;
         }
-        
-        /**
-         *    Compares a single header line against the expectation.
-         *    @param string $line      A single line to compare.
-         *    @return boolean          True if matched.
-         *    @access private
-         */
-        function _testHeaderLine($line) {
-            if (count($parsed = split(':', $line, 2)) < 2) {
-                return false;
-            }
-            list($header, $value) = $parsed;
-            if ($this->_normaliseHeader($header) != $this->_expected_header) {
-                return false;
-            }
-            return $this->_testHeaderValue($value, $this->_expected_value);
+        sort($compare);
+        return ($this->_value === $compare);
+    }
+    
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        $dumper = &$this->_getDumper();
+        if (is_array($compare)) {
+            sort($compare);
         }
-        
-        /**
-         *    Tests the value part of the header.
-         *    @param string $value        Value to test.
-         *    @param mixed $expected      Value to test against.
-         *    @return boolean             True if matched.
-         *    @access protected
-         */
-        function _testHeaderValue($value, $expected) {
-            if ($expected === false) {
-                return true;
-            }
-            if (SimpleExpectation::isExpectation($expected)) {
-                return $expected->test(trim($value));
-            }
-            return (trim($value) == trim($expected));
+        if ($this->test($compare)) {
+            return "Field expectation [" . $dumper->describeValue($this->_value) . "]";
+        } else {
+            return "Field expectation [" . $dumper->describeValue($this->_value) .
+                    "] fails with [" .
+                    $dumper->describeValue($compare) . "] " .
+                    $dumper->describeDifference($this->_value, $compare);
         }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Raw header block to search.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            if (SimpleExpectation::isExpectation($this->_expected_value)) {
-                $message = $this->_expected_value->overlayMessage($compare, $this->_getDumper());
-            } else {
-                $message = $this->_expected_header .
-                        ($this->_expected_value ? ': ' . $this->_expected_value : '');
-            }
-            if (is_string($line = $this->_findHeader($compare))) {
-                return "Searching for header [$message] found [$line]";
-            } else {
-                return "Failed to find header [$message]";
+    }
+}
+
+/**
+ *    Test for a specific HTTP header within a header block.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class HttpHeaderExpectation extends SimpleExpectation {
+    var $_expected_header;
+    var $_expected_value;
+    
+    /**
+     *    Sets the field and value to compare against.
+     *    @param string $header   Case insenstive trimmed header name.
+     *    @param mixed $value     Optional value to compare. If not
+     *                            given then any value will match. If
+     *                            an expectation object then that will
+     *                            be used instead.
+     *    @param string $message  Optiona message override. Can use %s as
+     *                            a placeholder for the original message.
+     */
+    function HttpHeaderExpectation($header, $value = false, $message = '%s') {
+        $this->SimpleExpectation($message);
+        $this->_expected_header = $this->_normaliseHeader($header);
+        $this->_expected_value = $value;
+    }
+    
+    /**
+     *    Accessor for aggregated object.
+     *    @return mixed        Expectation set in constructor.
+     *    @access protected
+     */
+    function _getExpectation() {
+        return $this->_expected_value;
+    }
+    
+    /**
+     *    Removes whitespace at ends and case variations.
+     *    @param string $header    Name of header.
+     *    @param string            Trimmed and lowecased header
+     *                             name.
+     *    @access private
+     */
+    function _normaliseHeader($header) {
+        return strtolower(trim($header));
+    }
+    
+    /**
+     *    Tests the expectation. True if it matches
+     *    a string value or an array value in any order.
+     *    @param mixed $compare   Raw header block to search.
+     *    @return boolean         True if header present.
+     *    @access public
+     */
+    function test($compare) {
+        return is_string($this->_findHeader($compare));
+    }
+    
+    /**
+     *    Searches the incoming result. Will extract the matching
+     *    line as text.
+     *    @param mixed $compare   Raw header block to search.
+     *    @return string          Matching header line.
+     *    @access protected
+     */
+    function _findHeader($compare) {
+        $lines = split("\r\n", $compare);
+        foreach ($lines as $line) {
+            if ($this->_testHeaderLine($line)) {
+                return $line;
             }
         }
+        return false;
     }
-      
+    
     /**
-     *    Test for a specific HTTP header within a header block that
-     *    should not be found.
-        *        @package SimpleTest
-        *        @subpackage WebTester
+     *    Compares a single header line against the expectation.
+     *    @param string $line      A single line to compare.
+     *    @return boolean          True if matched.
+     *    @access private
      */
-    class NoHttpHeaderExpectation extends HttpHeaderExpectation {
-        var $_expected_header;
-        var $_expected_value;
-        
-        /**
-         *    Sets the field and value to compare against.
-         *    @param string $unwanted   Case insenstive trimmed header name.
-         *    @param string $message    Optiona message override. Can use %s as
-         *                              a placeholder for the original message.
-         */
-        function NoHttpHeaderExpectation($unwanted, $message = '%s') {
-            $this->HttpHeaderExpectation($unwanted, false, $message);
-        }
-        
-        /**
-         *    Tests that the unwanted header is not found.
-         *    @param mixed $compare   Raw header block to search.
-         *    @return boolean         True if header present.
-         *    @access public
-         */
-        function test($compare) {
-            return ($this->_findHeader($compare) === false);
+    function _testHeaderLine($line) {
+        if (count($parsed = split(':', $line, 2)) < 2) {
+            return false;
         }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Raw header block to search.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            $expectation = $this->_getExpectation();
-            if (is_string($line = $this->_findHeader($compare))) {
-                return "Found unwanted header [$expectation] with [$line]";
-            } else {
-                return "Did not find unwanted header [$expectation]";
-            }
+        list($header, $value) = $parsed;
+        if ($this->_normaliseHeader($header) != $this->_expected_header) {
+            return false;
         }
+        return $this->_testHeaderValue($value, $this->_expected_value);
     }
     
     /**
-     *    Test for a text substring.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Tests the value part of the header.
+     *    @param string $value        Value to test.
+     *    @param mixed $expected      Value to test against.
+     *    @return boolean             True if matched.
+     *    @access protected
      */
-    class TextExpectation extends SimpleExpectation {
-        var $_substring;
-        
-        /**
-         *    Sets the value to compare against.
-         *    @param string $substring  Text to search for.
-         *    @param string $message    Customised message on failure.
-         *    @access public
-         */
-        function TextExpectation($substring, $message = '%s') {
-            $this->SimpleExpectation($message);
-            $this->_substring = $substring;
+    function _testHeaderValue($value, $expected) {
+        if ($expected === false) {
+            return true;
         }
-        
-        /**
-         *    Accessor for the substring.
-         *    @return string       Text to match.
-         *    @access protected
-         */
-        function _getSubstring() {
-            return $this->_substring;
+        if (SimpleExpectation::isExpectation($expected)) {
+            return $expected->test(trim($value));
         }
-        
-        /**
-         *    Tests the expectation. True if the text contains the
-         *    substring.
-         *    @param string $compare        Comparison value.
-         *    @return boolean               True if correct.
-         *    @access public
-         */
-        function test($compare) {
-            return (strpos($compare, $this->_substring) !== false);
+        return (trim($value) == trim($expected));
+    }
+    
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Raw header block to search.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        if (SimpleExpectation::isExpectation($this->_expected_value)) {
+            $message = $this->_expected_value->overlayMessage($compare, $this->_getDumper());
+        } else {
+            $message = $this->_expected_header .
+                    ($this->_expected_value ? ': ' . $this->_expected_value : '');
+        }
+        if (is_string($line = $this->_findHeader($compare))) {
+            return "Searching for header [$message] found [$line]";
+        } else {
+            return "Failed to find header [$message]";
         }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param mixed $compare      Comparison value.
-         *    @return string             Description of success
-         *                               or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            if ($this->test($compare)) {
-                return $this->_describeTextMatch($this->_getSubstring(), $compare);
-            } else {
-                $dumper = &$this->_getDumper();
-                return "Text [" . $this->_getSubstring() .
-                        "] not detected in [" .
-                        $dumper->describeValue($compare) . "]";
-            }
+    }
+}
+    
+/**
+ *    Test for a specific HTTP header within a header block that
+ *    should not be found.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class NoHttpHeaderExpectation extends HttpHeaderExpectation {
+    var $_expected_header;
+    var $_expected_value;
+    
+    /**
+     *    Sets the field and value to compare against.
+     *    @param string $unwanted   Case insenstive trimmed header name.
+     *    @param string $message    Optiona message override. Can use %s as
+     *                              a placeholder for the original message.
+     */
+    function NoHttpHeaderExpectation($unwanted, $message = '%s') {
+        $this->HttpHeaderExpectation($unwanted, false, $message);
+    }
+    
+    /**
+     *    Tests that the unwanted header is not found.
+     *    @param mixed $compare   Raw header block to search.
+     *    @return boolean         True if header present.
+     *    @access public
+     */
+    function test($compare) {
+        return ($this->_findHeader($compare) === false);
+    }
+    
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Raw header block to search.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        $expectation = $this->_getExpectation();
+        if (is_string($line = $this->_findHeader($compare))) {
+            return "Found unwanted header [$expectation] with [$line]";
+        } else {
+            return "Did not find unwanted header [$expectation]";
         }
-        
-        /**
-         *    Describes a pattern match including the string
-         *    found and it's position.
-         *    @param string $substring      Text to search for.
-         *    @param string $subject        Subject to search.
-         *    @access protected
-         */
-        function _describeTextMatch($substring, $subject) {
-            $position = strpos($subject, $substring);
+    }
+}
+
+/**
+ *    Test for a text substring.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class TextExpectation extends SimpleExpectation {
+    var $_substring;
+    
+    /**
+     *    Sets the value to compare against.
+     *    @param string $substring  Text to search for.
+     *    @param string $message    Customised message on failure.
+     *    @access public
+     */
+    function TextExpectation($substring, $message = '%s') {
+        $this->SimpleExpectation($message);
+        $this->_substring = $substring;
+    }
+    
+    /**
+     *    Accessor for the substring.
+     *    @return string       Text to match.
+     *    @access protected
+     */
+    function _getSubstring() {
+        return $this->_substring;
+    }
+    
+    /**
+     *    Tests the expectation. True if the text contains the
+     *    substring.
+     *    @param string $compare        Comparison value.
+     *    @return boolean               True if correct.
+     *    @access public
+     */
+    function test($compare) {
+        return (strpos($compare, $this->_substring) !== false);
+    }
+    
+    /**
+     *    Returns a human readable test message.
+     *    @param mixed $compare      Comparison value.
+     *    @return string             Description of success
+     *                               or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        if ($this->test($compare)) {
+            return $this->_describeTextMatch($this->_getSubstring(), $compare);
+        } else {
             $dumper = &$this->_getDumper();
-            return "Text [$substring] detected at character [$position] in [" .
-                    $dumper->describeValue($subject) . "] in region [" .
-                    $dumper->clipString($subject, 100, $position) . "]";
+            return "Text [" . $this->_getSubstring() .
+                    "] not detected in [" .
+                    $dumper->describeValue($compare) . "]";
         }
     }
     
     /**
-     *    Fail if a substring is detected within the
-     *    comparison text.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Describes a pattern match including the string
+     *    found and it's position.
+     *    @param string $substring      Text to search for.
+     *    @param string $subject        Subject to search.
+     *    @access protected
      */
-    class NoTextExpectation extends TextExpectation {
-        
-        /**
-         *    Sets the reject pattern
-         *    @param string $substring  Text to search for.
-         *    @param string $message    Customised message on failure.
-         *    @access public
-         */
-        function NoTextExpectation($substring, $message = '%s') {
-            $this->TextExpectation($substring, $message);
-        }
-        
-        /**
-         *    Tests the expectation. False if the substring appears
-         *    in the text.
-         *    @param string $compare        Comparison value.
-         *    @return boolean               True if correct.
-         *    @access public
-         */
-        function test($compare) {
-            return ! parent::test($compare);
-        }
-        
-        /**
-         *    Returns a human readable test message.
-         *    @param string $compare      Comparison value.
-         *    @return string              Description of success
-         *                                or failure.
-         *    @access public
-         */
-        function testMessage($compare) {
-            if ($this->test($compare)) {
-                $dumper = &$this->_getDumper();
-                return "Text [" . $this->_getSubstring() .
-                        "] not detected in [" .
-                        $dumper->describeValue($compare) . "]";
-            } else {
-                return $this->_describeTextMatch($this->_getSubstring(), $compare);
-            }
-        }
+    function _describeTextMatch($substring, $subject) {
+        $position = strpos($subject, $substring);
+        $dumper = &$this->_getDumper();
+        return "Text [$substring] detected at character [$position] in [" .
+                $dumper->describeValue($subject) . "] in region [" .
+                $dumper->clipString($subject, 100, $position) . "]";
     }
+}
+
+/**
+ *    Fail if a substring is detected within the
+ *    comparison text.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class NoTextExpectation extends TextExpectation {
     
     /**
-     *    Test case for testing of web pages. Allows
-     *    fetching of pages, parsing of HTML and
-     *    submitting forms.
-        *    @package SimpleTest
-        *    @subpackage WebTester
+     *    Sets the reject pattern
+     *    @param string $substring  Text to search for.
+     *    @param string $message    Customised message on failure.
+     *    @access public
      */
-    class WebTestCase extends SimpleTestCase {
-        var $_browser;
-        var $_ignore_errors = false;
-        
-        /**
-         *    Creates an empty test case. Should be subclassed
-         *    with test methods for a functional test case.
-         *    @param string $label     Name of test case. Will use
-         *                             the class name if none specified.
-         *    @access public
-         */
-        function WebTestCase($label = false) {
-            $this->SimpleTestCase($label);
-        }
-        
-        /**
-         *    Announces the start of the test.
-         *    @param string $method    Test method just started.
-         *    @access public
-         */
-        function before($method) {
-            parent::before($method);
-            $this->setBrowser($this->createBrowser());
+    function NoTextExpectation($substring, $message = '%s') {
+        $this->TextExpectation($substring, $message);
+    }
+    
+    /**
+     *    Tests the expectation. False if the substring appears
+     *    in the text.
+     *    @param string $compare        Comparison value.
+     *    @return boolean               True if correct.
+     *    @access public
+     */
+    function test($compare) {
+        return ! parent::test($compare);
+    }
+    
+    /**
+     *    Returns a human readable test message.
+     *    @param string $compare      Comparison value.
+     *    @return string              Description of success
+     *                                or failure.
+     *    @access public
+     */
+    function testMessage($compare) {
+        if ($this->test($compare)) {
+            $dumper = &$this->_getDumper();
+            return "Text [" . $this->_getSubstring() .
+                    "] not detected in [" .
+                    $dumper->describeValue($compare) . "]";
+        } else {
+            return $this->_describeTextMatch($this->_getSubstring(), $compare);
         }
+    }
+}
 
-        /**
-         *    Announces the end of the test. Includes private clean up.
-         *    @param string $method    Test method just finished.
-         *    @access public
-         */
-        function after($method) {
-            $this->unsetBrowser();
-            parent::after($method);
-        }
-        
-        /**
-         *    Gets a current browser reference for setting
-         *    special expectations or for detailed
-         *    examination of page fetches.
-         *    @return SimpleBrowser     Current test browser object.
-         *    @access public
-         */
-        function &getBrowser() {
-            return $this->_browser;
-        }
-        
-        /**
-         *    Gets a current browser reference for setting
-         *    special expectations or for detailed
-         *    examination of page fetches.
-         *    @param SimpleBrowser $browser    New test browser object.
-         *    @access public
-         */
-        function setBrowser(&$browser) {
-            return $this->_browser = &$browser;
-        }
-          
-        /**
-         *    Clears the current browser reference to help the
-         *    PHP garbage collector.
-         *    @access public
-         */
-        function unsetBrowser() {
-            unset($this->_browser);
-        }
-      
-        /**
-         *    Creates a new default web browser object.
-         *    Will be cleared at the end of the test method.
-         *    @return TestBrowser           New browser.
-         *    @access public
-         */
-        function &createBrowser() {
-            $browser = &new SimpleBrowser();
-            return $browser;
-        }
-        
-        /**
-         *    Gets the last response error.
-         *    @return string    Last low level HTTP error.
-         *    @access public
-         */
-        function getTransportError() {
-            return $this->_browser->getTransportError();
-        }
-          
-        /**
-         *    Accessor for the currently selected URL.
-         *    @return string        Current location or false if
-         *                          no page yet fetched.
-         *    @access public
-         */
-        function getUrl() {
-            return $this->_browser->getUrl();
-        }
-        
-        /**
-         *    Dumps the current request for debugging.
-         *    @access public
-         */
-        function showRequest() {
-            $this->dump($this->_browser->getRequest());
-        }
-        
-        /**
-         *    Dumps the current HTTP headers for debugging.
-         *    @access public
-         */
-        function showHeaders() {
-            $this->dump($this->_browser->getHeaders());
-        }
-      
-        /**
-         *    Dumps the current HTML source for debugging.
-         *    @access public
-         */
-        function showSource() {
-            $this->dump($this->_browser->getContent());
-        }
-      
-        /**
-         *    Dumps the visible text only for debugging.
-         *    @access public
-         */
-        function showText() {
-            $this->dump(wordwrap($this->_browser->getContentAsText(), 80));
-        }
-        
-        /**
-         *    Simulates the closing and reopening of the browser.
-         *    Temporary cookies will be discarded and timed
-         *    cookies will be expired if later than the
-         *    specified time.
-         *    @param string/integer $date Time when session restarted.
-         *                                If ommitted then all persistent
-         *                                cookies are kept. Time is either
-         *                                Cookie format string or timestamp.
-         *    @access public
-         */
-        function restart($date = false) {
-            if ($date === false) {
-                $date = time();
-            }
-            $this->_browser->restart($date);
-        }
-        
-        /**
-         *    Moves cookie expiry times back into the past.
-         *    Useful for testing timeouts and expiries.
-         *    @param integer $interval    Amount to age in seconds.
-         *    @access public
-         */
-        function ageCookies($interval) {
-            $this->_browser->ageCookies($interval);
-        }
-        
-        /**
-         *    Disables frames support. Frames will not be fetched
-         *    and the frameset page will be used instead.
-         *    @access public
-         */
-        function ignoreFrames() {
-            $this->_browser->ignoreFrames();
-        }
+/**
+ *    Test case for testing of web pages. Allows
+ *    fetching of pages, parsing of HTML and
+ *    submitting forms.
+ *    @package SimpleTest
+ *    @subpackage WebTester
+ */
+class WebTestCase extends SimpleTestCase {
+    var $_browser;
+    var $_ignore_errors = false;
+    
+    /**
+     *    Creates an empty test case. Should be subclassed
+     *    with test methods for a functional test case.
+     *    @param string $label     Name of test case. Will use
+     *                             the class name if none specified.
+     *    @access public
+     */
+    function WebTestCase($label = false) {
+        $this->SimpleTestCase($label);
+    }
+    
+    /**
+     *    Announces the start of the test.
+     *    @param string $method    Test method just started.
+     *    @access public
+     */
+    function before($method) {
+        parent::before($method);
+        $this->setBrowser($this->createBrowser());
+    }
+
+    /**
+     *    Announces the end of the test. Includes private clean up.
+     *    @param string $method    Test method just finished.
+     *    @access public
+     */
+    function after($method) {
+        $this->unsetBrowser();
+        parent::after($method);
+    }
+    
+    /**
+     *    Gets a current browser reference for setting
+     *    special expectations or for detailed
+     *    examination of page fetches.
+     *    @return SimpleBrowser     Current test browser object.
+     *    @access public
+     */
+    function &getBrowser() {
+        return $this->_browser;
+    }
+    
+    /**
+     *    Gets a current browser reference for setting
+     *    special expectations or for detailed
+     *    examination of page fetches.
+     *    @param SimpleBrowser $browser    New test browser object.
+     *    @access public
+     */
+    function setBrowser(&$browser) {
+        return $this->_browser = &$browser;
+    }
         
-        /**
-         *    Switches off cookie sending and recieving.
-         *    @access public
-         */
-        function ignoreCookies() {
-            $this->_browser->ignoreCookies();
-        }
+    /**
+     *    Clears the current browser reference to help the
+     *    PHP garbage collector.
+     *    @access public
+     */
+    function unsetBrowser() {
+        unset($this->_browser);
+    }
+    
+    /**
+     *    Creates a new default web browser object.
+     *    Will be cleared at the end of the test method.
+     *    @return TestBrowser           New browser.
+     *    @access public
+     */
+    function &createBrowser() {
+        $browser = &new SimpleBrowser();
+        return $browser;
+    }
+    
+    /**
+     *    Gets the last response error.
+     *    @return string    Last low level HTTP error.
+     *    @access public
+     */
+    function getTransportError() {
+        return $this->_browser->getTransportError();
+    }
         
-        /**
-         *    Skips errors for the next request only. You might
-         *    want to confirm that a page is unreachable for
-         *    example.
-         *    @access public
-         */
-        function ignoreErrors() {
-            $this->_ignore_errors = true;
+    /**
+     *    Accessor for the currently selected URL.
+     *    @return string        Current location or false if
+     *                          no page yet fetched.
+     *    @access public
+     */
+    function getUrl() {
+        return $this->_browser->getUrl();
+    }
+    
+    /**
+     *    Dumps the current request for debugging.
+     *    @access public
+     */
+    function showRequest() {
+        $this->dump($this->_browser->getRequest());
+    }
+    
+    /**
+     *    Dumps the current HTTP headers for debugging.
+     *    @access public
+     */
+    function showHeaders() {
+        $this->dump($this->_browser->getHeaders());
+    }
+    
+    /**
+     *    Dumps the current HTML source for debugging.
+     *    @access public
+     */
+    function showSource() {
+        $this->dump($this->_browser->getContent());
+    }
+    
+    /**
+     *    Dumps the visible text only for debugging.
+     *    @access public
+     */
+    function showText() {
+        $this->dump(wordwrap($this->_browser->getContentAsText(), 80));
+    }
+    
+    /**
+     *    Simulates the closing and reopening of the browser.
+     *    Temporary cookies will be discarded and timed
+     *    cookies will be expired if later than the
+     *    specified time.
+     *    @param string/integer $date Time when session restarted.
+     *                                If ommitted then all persistent
+     *                                cookies are kept. Time is either
+     *                                Cookie format string or timestamp.
+     *    @access public
+     */
+    function restart($date = false) {
+        if ($date === false) {
+            $date = time();
         }
-        
-        /**
-         *    Issues a fail if there is a transport error anywhere
-         *    in the current frameset. Only one such error is
-         *    reported.
-         *    @param string/boolean $result   HTML or failure.
-         *    @return string/boolean $result  Passes through result.
-         *    @access private
-         */
-        function _failOnError($result) {
-            if (! $this->_ignore_errors) {
-                if ($error = $this->_browser->getTransportError()) {
-                    $this->fail($error);
-                }
+        $this->_browser->restart($date);
+    }
+    
+    /**
+     *    Moves cookie expiry times back into the past.
+     *    Useful for testing timeouts and expiries.
+     *    @param integer $interval    Amount to age in seconds.
+     *    @access public
+     */
+    function ageCookies($interval) {
+        $this->_browser->ageCookies($interval);
+    }
+    
+    /**
+     *    Disables frames support. Frames will not be fetched
+     *    and the frameset page will be used instead.
+     *    @access public
+     */
+    function ignoreFrames() {
+        $this->_browser->ignoreFrames();
+    }
+    
+    /**
+     *    Switches off cookie sending and recieving.
+     *    @access public
+     */
+    function ignoreCookies() {
+        $this->_browser->ignoreCookies();
+    }
+    
+    /**
+     *    Skips errors for the next request only. You might
+     *    want to confirm that a page is unreachable for
+     *    example.
+     *    @access public
+     */
+    function ignoreErrors() {
+        $this->_ignore_errors = true;
+    }
+    
+    /**
+     *    Issues a fail if there is a transport error anywhere
+     *    in the current frameset. Only one such error is
+     *    reported.
+     *    @param string/boolean $result   HTML or failure.
+     *    @return string/boolean $result  Passes through result.
+     *    @access private
+     */
+    function _failOnError($result) {
+        if (! $this->_ignore_errors) {
+            if ($error = $this->_browser->getTransportError()) {
+                $this->fail($error);
             }
-            $this->_ignore_errors = false;
-            return $result;
         }
+        $this->_ignore_errors = false;
+        return $result;
+    }
 
-        /**
-         *    Adds a header to every fetch.
-         *    @param string $header       Header line to add to every
-         *                                request until cleared.
-         *    @access public
-         */
-        function addHeader($header) {
-            $this->_browser->addHeader($header);
-        }
-        
-        /**
-         *    Sets the maximum number of redirects before
-         *    the web page is loaded regardless.
-         *    @param integer $max        Maximum hops.
-         *    @access public
-         */
-        function setMaximumRedirects($max) {
-            if (! $this->_browser) {
-                trigger_error(
-                        'Can only set maximum redirects in a test method, setUp() or tearDown()');
-            }
-            $this->_browser->setMaximumRedirects($max);
+    /**
+     *    Adds a header to every fetch.
+     *    @param string $header       Header line to add to every
+     *                                request until cleared.
+     *    @access public
+     */
+    function addHeader($header) {
+        $this->_browser->addHeader($header);
+    }
+    
+    /**
+     *    Sets the maximum number of redirects before
+     *    the web page is loaded regardless.
+     *    @param integer $max        Maximum hops.
+     *    @access public
+     */
+    function setMaximumRedirects($max) {
+        if (! $this->_browser) {
+            trigger_error(
+                    'Can only set maximum redirects in a test method, setUp() or tearDown()');
         }
-        
-        /**
-         *    Sets the socket timeout for opening a connection and
-         *    receiving at least one byte of information.
-         *    @param integer $timeout      Maximum time in seconds.
-         *    @access public
-         */
-        function setConnectionTimeout($timeout) {
-            $this->_browser->setConnectionTimeout($timeout);
-        }
-        
-        /**
-         *    Sets proxy to use on all requests for when
-         *    testing from behind a firewall. Set URL
-         *    to false to disable.
-         *    @param string $proxy        Proxy URL.
-         *    @param string $username     Proxy username for authentication.
-         *    @param string $password     Proxy password for authentication.
-         *    @access public
-         */
-        function useProxy($proxy, $username = false, $password = false) {
-            $this->_browser->useProxy($proxy, $username, $password);
-        }
-        
-        /**
-         *    Fetches a page into the page buffer. If
-         *    there is no base for the URL then the
-         *    current base URL is used. After the fetch
-         *    the base URL reflects the new location.
-         *    @param string $url          URL to fetch.
-         *    @param hash $parameters     Optional additional GET data.
-         *    @return boolean/string      Raw page on success.
-         *    @access public
-         */
-        function get($url, $parameters = false) {
-            return $this->_failOnError($this->_browser->get($url, $parameters));
-        }
-        
-        /**
-         *    Fetches a page by POST into the page buffer.
-         *    If there is no base for the URL then the
-         *    current base URL is used. After the fetch
-         *    the base URL reflects the new location.
-         *    @param string $url          URL to fetch.
-         *    @param hash $parameters     Optional additional GET data.
-         *    @return boolean/string      Raw page on success.
-         *    @access public
-         */
-        function post($url, $parameters = false) {
-            return $this->_failOnError($this->_browser->post($url, $parameters));
-        }
-        
-        /**
-         *    Does a HTTP HEAD fetch, fetching only the page
-         *    headers. The current base URL is unchanged by this.
-         *    @param string $url          URL to fetch.
-         *    @param hash $parameters     Optional additional GET data.
-         *    @return boolean             True on success.
-         *    @access public
-         */
-        function head($url, $parameters = false) {
-            return $this->_failOnError($this->_browser->head($url, $parameters));
-        }
-        
-        /**
-         *    Equivalent to hitting the retry button on the
-         *    browser. Will attempt to repeat the page fetch.
-         *    @return boolean     True if fetch succeeded.
-         *    @access public
-         */
-        function retry() {
-            return $this->_failOnError($this->_browser->retry());
-        }
-        
-        /**
-         *    Equivalent to hitting the back button on the
-         *    browser.
-         *    @return boolean     True if history entry and
-         *                        fetch succeeded.
-         *    @access public
-         */
-        function back() {
-            return $this->_failOnError($this->_browser->back());
-        }
-        
-        /**
-         *    Equivalent to hitting the forward button on the
-         *    browser.
-         *    @return boolean     True if history entry and
-         *                        fetch succeeded.
-         *    @access public
-         */
-        function forward() {
-            return $this->_failOnError($this->_browser->forward());
-        }
-        
-        /**
-         *    Retries a request after setting the authentication
-         *    for the current realm.
-         *    @param string $username    Username for realm.
-         *    @param string $password    Password for realm.
-         *    @return boolean/string     HTML on successful fetch. Note
-         *                               that authentication may still have
-         *                               failed.
-         *    @access public
-         */
-        function authenticate($username, $password) {
-            return $this->_failOnError(
-                    $this->_browser->authenticate($username, $password));
-        }
-        
-        /**
-         *    Gets the cookie value for the current browser context.
-         *    @param string $name          Name of cookie.
-         *    @return string               Value of cookie or false if unset.
-         *    @access public
-         */
-        function getCookie($name) {
-            return $this->_browser->getCurrentCookieValue($name);
-        }
-        
-        /**
-         *    Sets a cookie in the current browser.
-         *    @param string $name          Name of cookie.
-         *    @param string $value         Cookie value.
-         *    @param string $host          Host upon which the cookie is valid.
-         *    @param string $path          Cookie path if not host wide.
-         *    @param string $expiry        Expiry date.
-         *    @access public
-         */
-        function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
-            $this->_browser->setCookie($name, $value, $host, $path, $expiry);
-        }
-        
-        /**
-         *    Accessor for current frame focus. Will be
-         *    false if no frame has focus.
-         *    @return integer/string/boolean    Label if any, otherwise
-         *                                      the position in the frameset
-         *                                      or false if none.
-         *    @access public
-         */
-        function getFrameFocus() {
-            return $this->_browser->getFrameFocus();
-        }
-        
-        /**
-         *    Sets the focus by index. The integer index starts from 1.
-         *    @param integer $choice    Chosen frame.
-         *    @return boolean           True if frame exists.
-         *    @access public
-         */
-        function setFrameFocusByIndex($choice) {
-            return $this->_browser->setFrameFocusByIndex($choice);
-        }
-        
-        /**
-         *    Sets the focus by name.
-         *    @param string $name    Chosen frame.
-         *    @return boolean        True if frame exists.
-         *    @access public
-         */
-        function setFrameFocus($name) {
-            return $this->_browser->setFrameFocus($name);
-        }
-        
-        /**
-         *    Clears the frame focus. All frames will be searched
-         *    for content.
-         *    @access public
-         */
-        function clearFrameFocus() {
-            return $this->_browser->clearFrameFocus();
-        }
-        
-        /**
-         *    Clicks a visible text item. Will first try buttons,
-         *    then links and then images.
-         *    @param string $label        Visible text or alt text.
-         *    @return string/boolean      Raw page or false.
-         *    @access public
-         */
-        function click($label) {
-            return $this->_failOnError($this->_browser->click($label));
-        }
-        
-        /**
-         *    Checks for a click target.
-         *    @param string $label        Visible text or alt text.
-         *    @return boolean             True if click target.
-         *    @access public
-         */    
-        function assertClickable($label, $message = '%s') {
-            return $this->assertTrue(
-                    $this->_browser->isClickable($label),
-                    sprintf($message, "Click target [$label] should exist"));
-        }
-        
-        /**
-         *    Clicks the submit button by label. The owning
-         *    form will be submitted by this.
-         *    @param string $label    Button label. An unlabeled
-         *                            button can be triggered by 'Submit'.
-         *    @param hash $additional Additional form values.
-         *    @return boolean/string  Page on success, else false.
-         *    @access public
-         */
-        function clickSubmit($label = 'Submit', $additional = false) {
-            return $this->_failOnError(
-                    $this->_browser->clickSubmit($label, $additional));
-        }
-        
-        /**
-         *    Clicks the submit button by name attribute. The owning
-         *    form will be submitted by this.
-         *    @param string $name     Name attribute of button.
-         *    @param hash $additional Additional form values.
-         *    @return boolean/string  Page on success.
-         *    @access public
-         */
-        function clickSubmitByName($name, $additional = false) {
-            return $this->_failOnError(
-                    $this->_browser->clickSubmitByName($name, $additional));
-        }
-        
-        /**
-         *    Clicks the submit button by ID attribute. The owning
-         *    form will be submitted by this.
-         *    @param string $id       ID attribute of button.
-         *    @param hash $additional Additional form values.
-         *    @return boolean/string  Page on success.
-         *    @access public
-         */
-        function clickSubmitById($id, $additional = false) {
-            return $this->_failOnError(
-                    $this->_browser->clickSubmitById($id, $additional));
-        }
-        
-        /**
-         *    Checks for a valid button label.
-         *    @param string $label        Visible text.
-         *    @return boolean             True if click target.
-         *    @access public
-         */    
-        function assertSubmit($label, $message = '%s') {
-            return $this->assertTrue(
-                    $this->_browser->isSubmit($label),
-                    sprintf($message, "Submit button [$label] should exist"));
-        }
-        
-        /**
-         *    Clicks the submit image by some kind of label. Usually
-         *    the alt tag or the nearest equivalent. The owning
-         *    form will be submitted by this. Clicking outside of
-         *    the boundary of the coordinates will result in
-         *    a failure.
-         *    @param string $label    Alt attribute of button.
-         *    @param integer $x       X-coordinate of imaginary click.
-         *    @param integer $y       Y-coordinate of imaginary click.
-         *    @param hash $additional Additional form values.
-         *    @return boolean/string  Page on success.
-         *    @access public
-         */
-        function clickImage($label, $x = 1, $y = 1, $additional = false) {
-            return $this->_failOnError(
-                    $this->_browser->clickImage($label, $x, $y, $additional));
-        }
-        
-        /**
-         *    Clicks the submit image by the name. Usually
-         *    the alt tag or the nearest equivalent. The owning
-         *    form will be submitted by this. Clicking outside of
-         *    the boundary of the coordinates will result in
-         *    a failure.
-         *    @param string $name     Name attribute of button.
-         *    @param integer $x       X-coordinate of imaginary click.
-         *    @param integer $y       Y-coordinate of imaginary click.
-         *    @param hash $additional Additional form values.
-         *    @return boolean/string  Page on success.
-         *    @access public
-         */
-        function clickImageByName($name, $x = 1, $y = 1, $additional = false) {
-            return $this->_failOnError(
-                    $this->_browser->clickImageByName($name, $x, $y, $additional));
-        }
-        
-        /**
-         *    Clicks the submit image by ID attribute. The owning
-         *    form will be submitted by this. Clicking outside of
-         *    the boundary of the coordinates will result in
-         *    a failure.
-         *    @param integer/string $id   ID attribute of button.
-         *    @param integer $x           X-coordinate of imaginary click.
-         *    @param integer $y           Y-coordinate of imaginary click.
-         *    @param hash $additional     Additional form values.
-         *    @return boolean/string      Page on success.
-         *    @access public
-         */
-        function clickImageById($id, $x = 1, $y = 1, $additional = false) {
-            return $this->_failOnError(
-                    $this->_browser->clickImageById($id, $x, $y, $additional));
-        }
-        
-        /**
-         *    Checks for a valid image with atht alt text or title.
-         *    @param string $label        Visible text.
-         *    @return boolean             True if click target.
-         *    @access public
-         */    
-        function assertImage($label, $message = '%s') {
-            return $this->assertTrue(
-                    $this->_browser->isImage($label),
-                    sprintf($message, "Image with text [$label] should exist"));
-        }
-        
-        /**
-         *    Submits a form by the ID.
-         *    @param string $id       Form ID. No button information
-         *                            is submitted this way.
-         *    @return boolean/string  Page on success.
-         *    @access public
-         */
-        function submitFormById($id) {
-            return $this->_failOnError($this->_browser->submitFormById($id));
-        }
-        
-        /**
-         *    Follows a link by name. Will click the first link
-         *    found with this link text by default, or a later
-         *    one if an index is given. Match is case insensitive
-         *    with normalised space.
-         *    @param string $label     Text between the anchor tags.
-         *    @param integer $index    Link position counting from zero.
-         *    @return boolean/string   Page on success.
-         *    @access public
-         */
-        function clickLink($label, $index = 0) {
-            return $this->_failOnError($this->_browser->clickLink($label, $index));
-        }
-        
-        /**
-         *    Follows a link by id attribute.
-         *    @param string $id        ID attribute value.
-         *    @return boolean/string   Page on success.
-         *    @access public
-         */
-        function clickLinkById($id) {
-            return $this->_failOnError($this->_browser->clickLinkById($id));
+        $this->_browser->setMaximumRedirects($max);
+    }
+    
+    /**
+     *    Sets the socket timeout for opening a connection and
+     *    receiving at least one byte of information.
+     *    @param integer $timeout      Maximum time in seconds.
+     *    @access public
+     */
+    function setConnectionTimeout($timeout) {
+        $this->_browser->setConnectionTimeout($timeout);
+    }
+    
+    /**
+     *    Sets proxy to use on all requests for when
+     *    testing from behind a firewall. Set URL
+     *    to false to disable.
+     *    @param string $proxy        Proxy URL.
+     *    @param string $username     Proxy username for authentication.
+     *    @param string $password     Proxy password for authentication.
+     *    @access public
+     */
+    function useProxy($proxy, $username = false, $password = false) {
+        $this->_browser->useProxy($proxy, $username, $password);
+    }
+    
+    /**
+     *    Fetches a page into the page buffer. If
+     *    there is no base for the URL then the
+     *    current base URL is used. After the fetch
+     *    the base URL reflects the new location.
+     *    @param string $url          URL to fetch.
+     *    @param hash $parameters     Optional additional GET data.
+     *    @return boolean/string      Raw page on success.
+     *    @access public
+     */
+    function get($url, $parameters = false) {
+        return $this->_failOnError($this->_browser->get($url, $parameters));
+    }
+    
+    /**
+     *    Fetches a page by POST into the page buffer.
+     *    If there is no base for the URL then the
+     *    current base URL is used. After the fetch
+     *    the base URL reflects the new location.
+     *    @param string $url          URL to fetch.
+     *    @param hash $parameters     Optional additional GET data.
+     *    @return boolean/string      Raw page on success.
+     *    @access public
+     */
+    function post($url, $parameters = false) {
+        return $this->_failOnError($this->_browser->post($url, $parameters));
+    }
+    
+    /**
+     *    Does a HTTP HEAD fetch, fetching only the page
+     *    headers. The current base URL is unchanged by this.
+     *    @param string $url          URL to fetch.
+     *    @param hash $parameters     Optional additional GET data.
+     *    @return boolean             True on success.
+     *    @access public
+     */
+    function head($url, $parameters = false) {
+        return $this->_failOnError($this->_browser->head($url, $parameters));
+    }
+    
+    /**
+     *    Equivalent to hitting the retry button on the
+     *    browser. Will attempt to repeat the page fetch.
+     *    @return boolean     True if fetch succeeded.
+     *    @access public
+     */
+    function retry() {
+        return $this->_failOnError($this->_browser->retry());
+    }
+    
+    /**
+     *    Equivalent to hitting the back button on the
+     *    browser.
+     *    @return boolean     True if history entry and
+     *                        fetch succeeded.
+     *    @access public
+     */
+    function back() {
+        return $this->_failOnError($this->_browser->back());
+    }
+    
+    /**
+     *    Equivalent to hitting the forward button on the
+     *    browser.
+     *    @return boolean     True if history entry and
+     *                        fetch succeeded.
+     *    @access public
+     */
+    function forward() {
+        return $this->_failOnError($this->_browser->forward());
+    }
+    
+    /**
+     *    Retries a request after setting the authentication
+     *    for the current realm.
+     *    @param string $username    Username for realm.
+     *    @param string $password    Password for realm.
+     *    @return boolean/string     HTML on successful fetch. Note
+     *                               that authentication may still have
+     *                               failed.
+     *    @access public
+     */
+    function authenticate($username, $password) {
+        return $this->_failOnError(
+                $this->_browser->authenticate($username, $password));
+    }
+    
+    /**
+     *    Gets the cookie value for the current browser context.
+     *    @param string $name          Name of cookie.
+     *    @return string               Value of cookie or false if unset.
+     *    @access public
+     */
+    function getCookie($name) {
+        return $this->_browser->getCurrentCookieValue($name);
+    }
+    
+    /**
+     *    Sets a cookie in the current browser.
+     *    @param string $name          Name of cookie.
+     *    @param string $value         Cookie value.
+     *    @param string $host          Host upon which the cookie is valid.
+     *    @param string $path          Cookie path if not host wide.
+     *    @param string $expiry        Expiry date.
+     *    @access public
+     */
+    function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
+        $this->_browser->setCookie($name, $value, $host, $path, $expiry);
+    }
+    
+    /**
+     *    Accessor for current frame focus. Will be
+     *    false if no frame has focus.
+     *    @return integer/string/boolean    Label if any, otherwise
+     *                                      the position in the frameset
+     *                                      or false if none.
+     *    @access public
+     */
+    function getFrameFocus() {
+        return $this->_browser->getFrameFocus();
+    }
+    
+    /**
+     *    Sets the focus by index. The integer index starts from 1.
+     *    @param integer $choice    Chosen frame.
+     *    @return boolean           True if frame exists.
+     *    @access public
+     */
+    function setFrameFocusByIndex($choice) {
+        return $this->_browser->setFrameFocusByIndex($choice);
+    }
+    
+    /**
+     *    Sets the focus by name.
+     *    @param string $name    Chosen frame.
+     *    @return boolean        True if frame exists.
+     *    @access public
+     */
+    function setFrameFocus($name) {
+        return $this->_browser->setFrameFocus($name);
+    }
+    
+    /**
+     *    Clears the frame focus. All frames will be searched
+     *    for content.
+     *    @access public
+     */
+    function clearFrameFocus() {
+        return $this->_browser->clearFrameFocus();
+    }
+    
+    /**
+     *    Clicks a visible text item. Will first try buttons,
+     *    then links and then images.
+     *    @param string $label        Visible text or alt text.
+     *    @return string/boolean      Raw page or false.
+     *    @access public
+     */
+    function click($label) {
+        return $this->_failOnError($this->_browser->click($label));
+    }
+    
+    /**
+     *    Checks for a click target.
+     *    @param string $label        Visible text or alt text.
+     *    @return boolean             True if click target.
+     *    @access public
+     */    
+    function assertClickable($label, $message = '%s') {
+        return $this->assertTrue(
+                $this->_browser->isClickable($label),
+                sprintf($message, "Click target [$label] should exist"));
+    }
+    
+    /**
+     *    Clicks the submit button by label. The owning
+     *    form will be submitted by this.
+     *    @param string $label    Button label. An unlabeled
+     *                            button can be triggered by 'Submit'.
+     *    @param hash $additional Additional form values.
+     *    @return boolean/string  Page on success, else false.
+     *    @access public
+     */
+    function clickSubmit($label = 'Submit', $additional = false) {
+        return $this->_failOnError(
+                $this->_browser->clickSubmit($label, $additional));
+    }
+    
+    /**
+     *    Clicks the submit button by name attribute. The owning
+     *    form will be submitted by this.
+     *    @param string $name     Name attribute of button.
+     *    @param hash $additional Additional form values.
+     *    @return boolean/string  Page on success.
+     *    @access public
+     */
+    function clickSubmitByName($name, $additional = false) {
+        return $this->_failOnError(
+                $this->_browser->clickSubmitByName($name, $additional));
+    }
+    
+    /**
+     *    Clicks the submit button by ID attribute. The owning
+     *    form will be submitted by this.
+     *    @param string $id       ID attribute of button.
+     *    @param hash $additional Additional form values.
+     *    @return boolean/string  Page on success.
+     *    @access public
+     */
+    function clickSubmitById($id, $additional = false) {
+        return $this->_failOnError(
+                $this->_browser->clickSubmitById($id, $additional));
+    }
+    
+    /**
+     *    Checks for a valid button label.
+     *    @param string $label        Visible text.
+     *    @return boolean             True if click target.
+     *    @access public
+     */    
+    function assertSubmit($label, $message = '%s') {
+        return $this->assertTrue(
+                $this->_browser->isSubmit($label),
+                sprintf($message, "Submit button [$label] should exist"));
+    }
+    
+    /**
+     *    Clicks the submit image by some kind of label. Usually
+     *    the alt tag or the nearest equivalent. The owning
+     *    form will be submitted by this. Clicking outside of
+     *    the boundary of the coordinates will result in
+     *    a failure.
+     *    @param string $label    Alt attribute of button.
+     *    @param integer $x       X-coordinate of imaginary click.
+     *    @param integer $y       Y-coordinate of imaginary click.
+     *    @param hash $additional Additional form values.
+     *    @return boolean/string  Page on success.
+     *    @access public
+     */
+    function clickImage($label, $x = 1, $y = 1, $additional = false) {
+        return $this->_failOnError(
+                $this->_browser->clickImage($label, $x, $y, $additional));
+    }
+    
+    /**
+     *    Clicks the submit image by the name. Usually
+     *    the alt tag or the nearest equivalent. The owning
+     *    form will be submitted by this. Clicking outside of
+     *    the boundary of the coordinates will result in
+     *    a failure.
+     *    @param string $name     Name attribute of button.
+     *    @param integer $x       X-coordinate of imaginary click.
+     *    @param integer $y       Y-coordinate of imaginary click.
+     *    @param hash $additional Additional form values.
+     *    @return boolean/string  Page on success.
+     *    @access public
+     */
+    function clickImageByName($name, $x = 1, $y = 1, $additional = false) {
+        return $this->_failOnError(
+                $this->_browser->clickImageByName($name, $x, $y, $additional));
+    }
+    
+    /**
+     *    Clicks the submit image by ID attribute. The owning
+     *    form will be submitted by this. Clicking outside of
+     *    the boundary of the coordinates will result in
+     *    a failure.
+     *    @param integer/string $id   ID attribute of button.
+     *    @param integer $x           X-coordinate of imaginary click.
+     *    @param integer $y           Y-coordinate of imaginary click.
+     *    @param hash $additional     Additional form values.
+     *    @return boolean/string      Page on success.
+     *    @access public
+     */
+    function clickImageById($id, $x = 1, $y = 1, $additional = false) {
+        return $this->_failOnError(
+                $this->_browser->clickImageById($id, $x, $y, $additional));
+    }
+    
+    /**
+     *    Checks for a valid image with atht alt text or title.
+     *    @param string $label        Visible text.
+     *    @return boolean             True if click target.
+     *    @access public
+     */    
+    function assertImage($label, $message = '%s') {
+        return $this->assertTrue(
+                $this->_browser->isImage($label),
+                sprintf($message, "Image with text [$label] should exist"));
+    }
+    
+    /**
+     *    Submits a form by the ID.
+     *    @param string $id       Form ID. No button information
+     *                            is submitted this way.
+     *    @return boolean/string  Page on success.
+     *    @access public
+     */
+    function submitFormById($id) {
+        return $this->_failOnError($this->_browser->submitFormById($id));
+    }
+    
+    /**
+     *    Follows a link by name. Will click the first link
+     *    found with this link text by default, or a later
+     *    one if an index is given. Match is case insensitive
+     *    with normalised space.
+     *    @param string $label     Text between the anchor tags.
+     *    @param integer $index    Link position counting from zero.
+     *    @return boolean/string   Page on success.
+     *    @access public
+     */
+    function clickLink($label, $index = 0) {
+        return $this->_failOnError($this->_browser->clickLink($label, $index));
+    }
+    
+    /**
+     *    Follows a link by id attribute.
+     *    @param string $id        ID attribute value.
+     *    @return boolean/string   Page on success.
+     *    @access public
+     */
+    function clickLinkById($id) {
+        return $this->_failOnError($this->_browser->clickLinkById($id));
+    }
+    
+    /**
+     *    Tests for the presence of a link label. Match is
+     *    case insensitive with normalised space.
+     *    @param string $label     Text between the anchor tags.
+     *    @param mixed $expected   Expected URL or expectation object.
+     *    @param string $message   Message to display. Default
+     *                             can be embedded with %s.
+     *    @return boolean          True if link present.
+     *    @access public
+     */
+    function assertLink($label, $expected = true, $message = '%s') {
+        $url = $this->_browser->getLink($label);
+        if ($expected === true || ($expected !== true && $url === false)) {
+            return $this->assertTrue($url !== false, sprintf($message, "Link [$label] should exist"));
         }
-        
-        /**
-         *    Tests for the presence of a link label. Match is
-         *    case insensitive with normalised space.
-         *    @param string $label     Text between the anchor tags.
-         *    @param mixed $expected   Expected URL or expectation object.
-         *    @param string $message   Message to display. Default
-         *                             can be embedded with %s.
-         *    @return boolean          True if link present.
-         *    @access public
-         */
-        function assertLink($label, $expected = true, $message = '%s') {
-            $url = $this->_browser->getLink($label);
-            if ($expected === true) {
-                return $this->assertTrue($url !== false, sprintf($message, "Link [$label] should exist"));
-            }
-            if (! SimpleExpectation::isExpectation($expected)) {
-                $expected = new IdenticalExpectation($expected);
-            }
-            return $this->assert($expected, $url->asString(), sprintf($message, "Link [$label] should match"));
+        if (! SimpleExpectation::isExpectation($expected)) {
+            $expected = new IdenticalExpectation($expected);
         }
+        return $this->assert($expected, $url->asString(), sprintf($message, "Link [$label] should match"));
+    }
 
-        /**
-         *    Tests for the non-presence of a link label. Match is
-         *    case insensitive with normalised space.
-         *    @param string/integer $label    Text between the anchor tags
-         *                                    or ID attribute.
-         *    @param string $message          Message to display. Default
-         *                                    can be embedded with %s.
-         *    @return boolean                 True if link missing.
-         *    @access public
-         */
-        function assertNoLink($label, $message = '%s') {
-            return $this->assertTrue(
-                    $this->_browser->getLink($label) === false,
-                    sprintf($message, "Link [$label] should not exist"));
+    /**
+     *    Tests for the non-presence of a link label. Match is
+     *    case insensitive with normalised space.
+     *    @param string/integer $label    Text between the anchor tags
+     *                                    or ID attribute.
+     *    @param string $message          Message to display. Default
+     *                                    can be embedded with %s.
+     *    @return boolean                 True if link missing.
+     *    @access public
+     */
+    function assertNoLink($label, $message = '%s') {
+        return $this->assertTrue(
+                $this->_browser->getLink($label) === false,
+                sprintf($message, "Link [$label] should not exist"));
+    }
+    
+    /**
+     *    Tests for the presence of a link id attribute.
+     *    @param string $id        Id attribute value.
+     *    @param mixed $expected   Expected URL or expectation object.
+     *    @param string $message   Message to display. Default
+     *                             can be embedded with %s.
+     *    @return boolean          True if link present.
+     *    @access public
+     */
+    function assertLinkById($id, $expected = true, $message = '%s') {
+        $url = $this->_browser->getLinkById($id);
+        if ($expected === true) {
+            return $this->assertTrue($url !== false, sprintf($message, "Link ID [$id] should exist"));
         }
-        
-        /**
-         *    Tests for the presence of a link id attribute.
-         *    @param string $id        Id attribute value.
-         *    @param mixed $expected   Expected URL or expectation object.
-         *    @param string $message   Message to display. Default
-         *                             can be embedded with %s.
-         *    @return boolean          True if link present.
-         *    @access public
-         */
-        function assertLinkById($id, $expected = true, $message = '%s') {
-            $url = $this->_browser->getLinkById($id);
-            if ($expected === true) {
-                return $this->assertTrue($url !== false, sprintf($message, "Link ID [$id] should exist"));
-            }
-            if (! SimpleExpectation::isExpectation($expected)) {
-                $expected = new IdenticalExpectation($expected);
-            }
-            return $this->assert($expected, $url->asString(), sprintf($message, "Link ID [$id] should match"));
+        if (! SimpleExpectation::isExpectation($expected)) {
+            $expected = new IdenticalExpectation($expected);
         }
+        return $this->assert($expected, $url->asString(), sprintf($message, "Link ID [$id] should match"));
+    }
 
-        /**
-         *    Tests for the non-presence of a link label. Match is
-         *    case insensitive with normalised space.
-         *    @param string $id        Id attribute value.
-         *    @param string $message   Message to display. Default
-         *                             can be embedded with %s.
-         *    @return boolean          True if link missing.
-         *    @access public
-         */
-        function assertNoLinkById($id, $message = '%s') {
-            return $this->assertTrue(
-                    $this->_browser->getLinkById($id) === false,
-                    sprintf($message, "Link ID [$id] should not exist"));
-        }
-        
-        /**
-         *    Sets all form fields with that label, or name if there
-         *    is no label attached.
-         *    @param string $name    Name of field in forms.
-         *    @param string $value   New value of field.
-         *    @return boolean        True if field exists, otherwise false.
-         *    @access public
-         */
-        function setField($label, $value) {
-            return $this->_browser->setField($label, $value);
-        }
-        
-        /**
-         *    Sets all form fields with that name.
-         *    @param string $name    Name of field in forms.
-         *    @param string $value   New value of field.
-         *    @return boolean        True if field exists, otherwise false.
-         *    @access public
-         */
-        function setFieldByName($name, $value) {
-            return $this->_browser->setFieldByName($name, $value);
-        }
-          
-        /**
-         *    Sets all form fields with that name.
-         *    @param string/integer $id   Id of field in forms.
-         *    @param string $value        New value of field.
-         *    @return boolean             True if field exists, otherwise false.
-         *    @access public
-         */
-        function setFieldById($id, $value) {
-            return $this->_browser->setFieldById($id, $value);
-        }
-        
-        /**
-         *    Confirms that the form element is currently set
-         *    to the expected value. A missing form will always
-         *    fail. If no value is given then only the existence
-         *    of the field is checked.
-         *    @param string $name       Name of field in forms.
-         *    @param mixed $expected    Expected string/array value or
-         *                              false for unset fields.
-         *    @param string $message    Message to display. Default
-         *                              can be embedded with %s.
-         *    @return boolean           True if pass.
-         *    @access public
-         */
-        function assertField($label, $expected = true, $message = '%s') {
-            $value = $this->_browser->getField($label);
-            return $this->_assertFieldValue($label, $value, $expected, $message);
-        }
-        
-        /**
-         *    Confirms that the form element is currently set
-         *    to the expected value. A missing form element will always
-         *    fail. If no value is given then only the existence
-         *    of the field is checked.
-         *    @param string $name       Name of field in forms.
-         *    @param mixed $expected    Expected string/array value or
-         *                              false for unset fields.
-         *    @param string $message    Message to display. Default
-         *                              can be embedded with %s.
-         *    @return boolean           True if pass.
-         *    @access public
-         */
-        function assertFieldByName($name, $expected = true, $message = '%s') {
-            $value = $this->_browser->getFieldByName($name);
-            return $this->_assertFieldValue($name, $value, $expected, $message);
-        }
-         
-        /**
-         *    Confirms that the form element is currently set
-         *    to the expected value. A missing form will always
-         *    fail. If no ID is given then only the existence
-         *    of the field is checked.
-         *    @param string/integer $id  Name of field in forms.
-         *    @param mixed $expected     Expected string/array value or
-         *                               false for unset fields.
-         *    @param string $message     Message to display. Default
-         *                               can be embedded with %s.
-         *    @return boolean            True if pass.
-         *    @access public
-         */
-        function assertFieldById($id, $expected = true, $message = '%s') {
-            $value = $this->_browser->getFieldById($id);
-            return $this->_assertFieldValue($id, $value, $expected, $message);
-        }
+    /**
+     *    Tests for the non-presence of a link label. Match is
+     *    case insensitive with normalised space.
+     *    @param string $id        Id attribute value.
+     *    @param string $message   Message to display. Default
+     *                             can be embedded with %s.
+     *    @return boolean          True if link missing.
+     *    @access public
+     */
+    function assertNoLinkById($id, $message = '%s') {
+        return $this->assertTrue(
+                $this->_browser->getLinkById($id) === false,
+                sprintf($message, "Link ID [$id] should not exist"));
+    }
+    
+    /**
+     *    Sets all form fields with that label, or name if there
+     *    is no label attached.
+     *    @param string $name    Name of field in forms.
+     *    @param string $value   New value of field.
+     *    @return boolean        True if field exists, otherwise false.
+     *    @access public
+     */
+    function setField($label, $value, $position=false) {
+        return $this->_browser->setField($label, $value, $position);
+    }
+    
+    /**
+     *    Sets all form fields with that name.
+     *    @param string $name    Name of field in forms.
+     *    @param string $value   New value of field.
+     *    @return boolean        True if field exists, otherwise false.
+     *    @access public
+     */
+    function setFieldByName($name, $value, $position=false) {
+        return $this->_browser->setFieldByName($name, $value, $position);
+    }
         
-        /**
-         *    Tests the field value against the expectation.
-         *    @param string $identifier      Name, ID or label.
-         *    @param mixed $value            Current field value.
-         *    @param mixed $expected         Expected value to match.
-         *    @param string $message         Failure message.
-         *    @return boolean                True if pass
-         *    @access protected
-         */
-        function _assertFieldValue($identifier, $value, $expected, $message) {
-            if ($expected === true) {
-                return $this->assertTrue(
-                        isset($value),
-                        sprintf($message, "Field [$identifier] should exist"));
-            }
-            if (! SimpleExpectation::isExpectation($expected)) {
-                $identifier = str_replace('%', '%%', $identifier);
-                $expected = new FieldExpectation(
-                        $expected,
-                        "Field [$identifier] should match with [%s]");
-            }
-            return $this->assert($expected, $value, $message);
-        }
-       
-        /**
-         *    Checks the response code against a list
-         *    of possible values.
-         *    @param array $responses    Possible responses for a pass.
-         *    @param string $message     Message to display. Default
-         *                               can be embedded with %s.
-         *    @return boolean            True if pass.
-         *    @access public
-         */
-        function assertResponse($responses, $message = '%s') {
-            $responses = (is_array($responses) ? $responses : array($responses));
-            $code = $this->_browser->getResponseCode();
-            $message = sprintf($message, "Expecting response in [" .
-                    implode(", ", $responses) . "] got [$code]");
-            return $this->assertTrue(in_array($code, $responses), $message);
-        }
+    /**
+     *    Sets all form fields with that id.
+     *    @param string/integer $id   Id of field in forms.
+     *    @param string $value        New value of field.
+     *    @return boolean             True if field exists, otherwise false.
+     *    @access public
+     */
+    function setFieldById($id, $value) {
+        return $this->_browser->setFieldById($id, $value);
+    }
+    
+    /**
+     *    Confirms that the form element is currently set
+     *    to the expected value. A missing form will always
+     *    fail. If no value is given then only the existence
+     *    of the field is checked.
+     *    @param string $name       Name of field in forms.
+     *    @param mixed $expected    Expected string/array value or
+     *                              false for unset fields.
+     *    @param string $message    Message to display. Default
+     *                              can be embedded with %s.
+     *    @return boolean           True if pass.
+     *    @access public
+     */
+    function assertField($label, $expected = true, $message = '%s') {
+        $value = $this->_browser->getField($label);
+        return $this->_assertFieldValue($label, $value, $expected, $message);
+    }
+    
+    /**
+     *    Confirms that the form element is currently set
+     *    to the expected value. A missing form element will always
+     *    fail. If no value is given then only the existence
+     *    of the field is checked.
+     *    @param string $name       Name of field in forms.
+     *    @param mixed $expected    Expected string/array value or
+     *                              false for unset fields.
+     *    @param string $message    Message to display. Default
+     *                              can be embedded with %s.
+     *    @return boolean           True if pass.
+     *    @access public
+     */
+    function assertFieldByName($name, $expected = true, $message = '%s') {
+        $value = $this->_browser->getFieldByName($name);
+        return $this->_assertFieldValue($name, $value, $expected, $message);
+    }
         
-        /**
-         *    Checks the mime type against a list
-         *    of possible values.
-         *    @param array $types      Possible mime types for a pass.
-         *    @param string $message   Message to display.
-         *    @return boolean          True if pass.
-         *    @access public
-         */
-        function assertMime($types, $message = '%s') {
-            $types = (is_array($types) ? $types : array($types));
-            $type = $this->_browser->getMimeType();
-            $message = sprintf($message, "Expecting mime type in [" .
-                    implode(", ", $types) . "] got [$type]");
-            return $this->assertTrue(in_array($type, $types), $message);
+    /**
+     *    Confirms that the form element is currently set
+     *    to the expected value. A missing form will always
+     *    fail. If no ID is given then only the existence
+     *    of the field is checked.
+     *    @param string/integer $id  Name of field in forms.
+     *    @param mixed $expected     Expected string/array value or
+     *                               false for unset fields.
+     *    @param string $message     Message to display. Default
+     *                               can be embedded with %s.
+     *    @return boolean            True if pass.
+     *    @access public
+     */
+    function assertFieldById($id, $expected = true, $message = '%s') {
+        $value = $this->_browser->getFieldById($id);
+        return $this->_assertFieldValue($id, $value, $expected, $message);
+    }
+    
+    /**
+     *    Tests the field value against the expectation.
+     *    @param string $identifier      Name, ID or label.
+     *    @param mixed $value            Current field value.
+     *    @param mixed $expected         Expected value to match.
+     *    @param string $message         Failure message.
+     *    @return boolean                True if pass
+     *    @access protected
+     */
+    function _assertFieldValue($identifier, $value, $expected, $message) {
+        if ($expected === true) {
+            return $this->assertTrue(
+                    isset($value),
+                    sprintf($message, "Field [$identifier] should exist"));
         }
-        
-        /**
-         *    Attempt to match the authentication type within
-         *    the security realm we are currently matching.
-         *    @param string $authentication   Usually basic.
-         *    @param string $message          Message to display.
-         *    @return boolean                 True if pass.
-         *    @access public
-         */
-        function assertAuthentication($authentication = false, $message = '%s') {
-            if (! $authentication) {
-                $message = sprintf($message, "Expected any authentication type, got [" .
-                        $this->_browser->getAuthentication() . "]");
-                return $this->assertTrue(
-                        $this->_browser->getAuthentication(),
-                        $message);
-            } else {
-                $message = sprintf($message, "Expected authentication [$authentication] got [" .
-                        $this->_browser->getAuthentication() . "]");
-                return $this->assertTrue(
-                        strtolower($this->_browser->getAuthentication()) == strtolower($authentication),
-                        $message);
-            }
+        if (! SimpleExpectation::isExpectation($expected)) {
+            $identifier = str_replace('%', '%%', $identifier);
+            $expected = new FieldExpectation(
+                    $expected,
+                    "Field [$identifier] should match with [%s]");
         }
-        
-        /**
-         *    Checks that no authentication is necessary to view
-         *    the desired page.
-         *    @param string $message     Message to display.
-         *    @return boolean            True if pass.
-         *    @access public
-         */
-        function assertNoAuthentication($message = '%s') {
-            $message = sprintf($message, "Expected no authentication type, got [" .
+        return $this->assert($expected, $value, $message);
+    }
+    
+    /**
+     *    Checks the response code against a list
+     *    of possible values.
+     *    @param array $responses    Possible responses for a pass.
+     *    @param string $message     Message to display. Default
+     *                               can be embedded with %s.
+     *    @return boolean            True if pass.
+     *    @access public
+     */
+    function assertResponse($responses, $message = '%s') {
+        $responses = (is_array($responses) ? $responses : array($responses));
+        $code = $this->_browser->getResponseCode();
+        $message = sprintf($message, "Expecting response in [" .
+                implode(", ", $responses) . "] got [$code]");
+        return $this->assertTrue(in_array($code, $responses), $message);
+    }
+    
+    /**
+     *    Checks the mime type against a list
+     *    of possible values.
+     *    @param array $types      Possible mime types for a pass.
+     *    @param string $message   Message to display.
+     *    @return boolean          True if pass.
+     *    @access public
+     */
+    function assertMime($types, $message = '%s') {
+        $types = (is_array($types) ? $types : array($types));
+        $type = $this->_browser->getMimeType();
+        $message = sprintf($message, "Expecting mime type in [" .
+                implode(", ", $types) . "] got [$type]");
+        return $this->assertTrue(in_array($type, $types), $message);
+    }
+    
+    /**
+     *    Attempt to match the authentication type within
+     *    the security realm we are currently matching.
+     *    @param string $authentication   Usually basic.
+     *    @param string $message          Message to display.
+     *    @return boolean                 True if pass.
+     *    @access public
+     */
+    function assertAuthentication($authentication = false, $message = '%s') {
+        if (! $authentication) {
+            $message = sprintf($message, "Expected any authentication type, got [" .
                     $this->_browser->getAuthentication() . "]");
-            return $this->assertFalse($this->_browser->getAuthentication(), $message);
-        }
-        
-        /**
-         *    Attempts to match the current security realm.
-         *    @param string $realm     Name of security realm.
-         *    @param string $message   Message to display.
-         *    @return boolean          True if pass.
-         *    @access public
-         */
-        function assertRealm($realm, $message = '%s') {
-            if (! SimpleExpectation::isExpectation($realm)) {
-                $realm = new EqualExpectation($realm);
-            }
-            return $this->assert(
-                    $realm,
-                    $this->_browser->getRealm(),
-                    "Expected realm -> $message");
-        }
-        
-        /**
-         *    Checks each header line for the required value. If no
-         *    value is given then only an existence check is made.
-         *    @param string $header    Case insensitive header name.
-         *    @param mixed $value      Case sensitive trimmed string to
-         *                             match against. An expectation object
-         *                             can be used for pattern matching.
-         *    @return boolean          True if pass.
-         *    @access public
-         */
-        function assertHeader($header, $value = false, $message = '%s') {
-            return $this->assert(
-                    new HttpHeaderExpectation($header, $value),
-                    $this->_browser->getHeaders(),
-                    $message);
-        }
-          
-        /**
-         *    @deprecated
-         */
-        function assertHeaderPattern($header, $pattern, $message = '%s') {
-            return $this->assert(
-                    new HttpHeaderExpectation($header, new PatternExpectation($pattern)),
-                    $this->_browser->getHeaders(),
-                    $message);
-        }
-
-        /**
-         *    Confirms that the header type has not been received.
-         *    Only the landing page is checked. If you want to check
-         *    redirect pages, then you should limit redirects so
-         *    as to capture the page you want.
-         *    @param string $header    Case insensitive header name.
-         *    @return boolean          True if pass.
-         *    @access public
-         */
-        function assertNoHeader($header, $message = '%s') {
-            return $this->assert(
-                    new NoHttpHeaderExpectation($header),
-                    $this->_browser->getHeaders(),
-                    $message);
-        }
-          
-        /**
-         *    @deprecated
-         */
-        function assertNoUnwantedHeader($header, $message = '%s') {
-            return $this->assertNoHeader($header, $message);
-        }
-        
-        /**
-         *    Tests the text between the title tags.
-         *    @param string/SimpleExpectation $title    Expected title.
-         *    @param string $message                    Message to display.
-         *    @return boolean                           True if pass.
-         *    @access public
-         */
-        function assertTitle($title = false, $message = '%s') {
-            if (! SimpleExpectation::isExpectation($title)) {
-                $title = new EqualExpectation($title);
-            }
-            return $this->assert($title, $this->_browser->getTitle(), $message);
-        }
-        
-        /**
-         *    Will trigger a pass if the text is found in the plain
-         *    text form of the page.
-         *    @param string $text       Text to look for.
-         *    @param string $message    Message to display.
-         *    @return boolean           True if pass.
-         *    @access public
-         */
-        function assertText($text, $message = '%s') {
-            return $this->assert(
-                    new TextExpectation($text),
-                    $this->_browser->getContentAsText(),
-                    $message);
-        }
-        
-        /**
-         *       @deprecated
-         */
-        function assertWantedText($text, $message = '%s') {
-               return $this->assertText($text, $message);
-        }
-        
-        /**
-         *    Will trigger a pass if the text is not found in the plain
-         *    text form of the page.
-         *    @param string $text       Text to look for.
-         *    @param string $message    Message to display.
-         *    @return boolean           True if pass.
-         *    @access public
-         */
-        function assertNoText($text, $message = '%s') {
-            return $this->assert(
-                    new NoTextExpectation($text),
-                    $this->_browser->getContentAsText(),
-                    $message);
-        }
-        
-        /**
-         *       @deprecated
-         */
-        function assertNoUnwantedText($text, $message = '%s') {
-               return $this->assertNoText($text, $message);
-        }
-        
-        /**
-         *    Will trigger a pass if the Perl regex pattern
-         *    is found in the raw content.
-         *    @param string $pattern    Perl regex to look for including
-         *                              the regex delimiters.
-         *    @param string $message    Message to display.
-         *    @return boolean           True if pass.
-         *    @access public
-         */
-        function assertPattern($pattern, $message = '%s') {
-            return $this->assert(
-                    new PatternExpectation($pattern),
-                    $this->_browser->getContent(),
+            return $this->assertTrue(
+                    $this->_browser->getAuthentication(),
                     $message);
-        }
-        
-        /**
-         *       @deprecated
-         */
-        function assertWantedPattern($pattern, $message = '%s') {
-               return $this->assertPattern($pattern, $message);
-        }
-        
-        /**
-         *    Will trigger a pass if the perl regex pattern
-         *    is not present in raw content.
-         *    @param string $pattern    Perl regex to look for including
-         *                              the regex delimiters.
-         *    @param string $message    Message to display.
-         *    @return boolean           True if pass.
-         *    @access public
-         */
-        function assertNoPattern($pattern, $message = '%s') {
-            return $this->assert(
-                    new NoPatternExpectation($pattern),
-                    $this->_browser->getContent(),
+        } else {
+            $message = sprintf($message, "Expected authentication [$authentication] got [" .
+                    $this->_browser->getAuthentication() . "]");
+            return $this->assertTrue(
+                    strtolower($this->_browser->getAuthentication()) == strtolower($authentication),
                     $message);
         }
+    }
+    
+    /**
+     *    Checks that no authentication is necessary to view
+     *    the desired page.
+     *    @param string $message     Message to display.
+     *    @return boolean            True if pass.
+     *    @access public
+     */
+    function assertNoAuthentication($message = '%s') {
+        $message = sprintf($message, "Expected no authentication type, got [" .
+                $this->_browser->getAuthentication() . "]");
+        return $this->assertFalse($this->_browser->getAuthentication(), $message);
+    }
+    
+    /**
+     *    Attempts to match the current security realm.
+     *    @param string $realm     Name of security realm.
+     *    @param string $message   Message to display.
+     *    @return boolean          True if pass.
+     *    @access public
+     */
+    function assertRealm($realm, $message = '%s') {
+        if (! SimpleExpectation::isExpectation($realm)) {
+            $realm = new EqualExpectation($realm);
+        }
+        return $this->assert(
+                $realm,
+                $this->_browser->getRealm(),
+                "Expected realm -> $message");
+    }
+    
+    /**
+     *    Checks each header line for the required value. If no
+     *    value is given then only an existence check is made.
+     *    @param string $header    Case insensitive header name.
+     *    @param mixed $value      Case sensitive trimmed string to
+     *                             match against. An expectation object
+     *                             can be used for pattern matching.
+     *    @return boolean          True if pass.
+     *    @access public
+     */
+    function assertHeader($header, $value = false, $message = '%s') {
+        return $this->assert(
+                new HttpHeaderExpectation($header, $value),
+                $this->_browser->getHeaders(),
+                $message);
+    }
         
-        /**
-         *       @deprecated
-         */
-        function assertNoUnwantedPattern($pattern, $message = '%s') {
-               return $this->assertNoPattern($pattern, $message);
-        }
+    /**
+     *    @deprecated
+     */
+    function assertHeaderPattern($header, $pattern, $message = '%s') {
+        return $this->assert(
+                new HttpHeaderExpectation($header, new PatternExpectation($pattern)),
+                $this->_browser->getHeaders(),
+                $message);
+    }
+
+    /**
+     *    Confirms that the header type has not been received.
+     *    Only the landing page is checked. If you want to check
+     *    redirect pages, then you should limit redirects so
+     *    as to capture the page you want.
+     *    @param string $header    Case insensitive header name.
+     *    @return boolean          True if pass.
+     *    @access public
+     */
+    function assertNoHeader($header, $message = '%s') {
+        return $this->assert(
+                new NoHttpHeaderExpectation($header),
+                $this->_browser->getHeaders(),
+                $message);
+    }
         
-        /**
-         *    Checks that a cookie is set for the current page
-         *    and optionally checks the value.
-         *    @param string $name        Name of cookie to test.
-         *    @param string $expected    Expected value as a string or
-         *                               false if any value will do.
-         *    @param string $message     Message to display.
-         *    @return boolean            True if pass.
-         *    @access public
-         */
-        function assertCookie($name, $expected = false, $message = '%s') {
-            $value = $this->getCookie($name);
-            if (! $expected) {
-                return $this->assertTrue(
-                        $value,
-                        sprintf($message, "Expecting cookie [$name]"));
-            }
-            if (! SimpleExpectation::isExpectation($expected)) {
-                $expected = new EqualExpectation($expected);
-            }
-            return $this->assert($expected, $value, "Expecting cookie [$name] -> $message");
+    /**
+     *    @deprecated
+     */
+    function assertNoUnwantedHeader($header, $message = '%s') {
+        return $this->assertNoHeader($header, $message);
+    }
+    
+    /**
+     *    Tests the text between the title tags.
+     *    @param string/SimpleExpectation $title    Expected title.
+     *    @param string $message                    Message to display.
+     *    @return boolean                           True if pass.
+     *    @access public
+     */
+    function assertTitle($title = false, $message = '%s') {
+        if (! SimpleExpectation::isExpectation($title)) {
+            $title = new EqualExpectation($title);
         }
-        
-        /**
-         *    Checks that no cookie is present or that it has
-         *    been successfully cleared.
-         *    @param string $name        Name of cookie to test.
-         *    @param string $message     Message to display.
-         *    @return boolean            True if pass.
-         *    @access public
-         */
-        function assertNoCookie($name, $message = '%s') {
+        return $this->assert($title, $this->_browser->getTitle(), $message);
+    }
+    
+    /**
+     *    Will trigger a pass if the text is found in the plain
+     *    text form of the page.
+     *    @param string $text       Text to look for.
+     *    @param string $message    Message to display.
+     *    @return boolean           True if pass.
+     *    @access public
+     */
+    function assertText($text, $message = '%s') {
+        return $this->assert(
+                new TextExpectation($text),
+                $this->_browser->getContentAsText(),
+                $message);
+    }
+    
+    /**
+     *    @deprecated
+     */
+    function assertWantedText($text, $message = '%s') {
+        return $this->assertText($text, $message);
+    }
+    
+    /**
+     *    Will trigger a pass if the text is not found in the plain
+     *    text form of the page.
+     *    @param string $text       Text to look for.
+     *    @param string $message    Message to display.
+     *    @return boolean           True if pass.
+     *    @access public
+     */
+    function assertNoText($text, $message = '%s') {
+        return $this->assert(
+                new NoTextExpectation($text),
+                $this->_browser->getContentAsText(),
+                $message);
+    }
+    
+    /**
+     *    @deprecated
+     */
+    function assertNoUnwantedText($text, $message = '%s') {
+        return $this->assertNoText($text, $message);
+    }
+    
+    /**
+     *    Will trigger a pass if the Perl regex pattern
+     *    is found in the raw content.
+     *    @param string $pattern    Perl regex to look for including
+     *                              the regex delimiters.
+     *    @param string $message    Message to display.
+     *    @return boolean           True if pass.
+     *    @access public
+     */
+    function assertPattern($pattern, $message = '%s') {
+        return $this->assert(
+                new PatternExpectation($pattern),
+                $this->_browser->getContent(),
+                $message);
+    }
+    
+    /**
+     *    @deprecated
+     */
+    function assertWantedPattern($pattern, $message = '%s') {
+        return $this->assertPattern($pattern, $message);
+    }
+    
+    /**
+     *    Will trigger a pass if the perl regex pattern
+     *    is not present in raw content.
+     *    @param string $pattern    Perl regex to look for including
+     *                              the regex delimiters.
+     *    @param string $message    Message to display.
+     *    @return boolean           True if pass.
+     *    @access public
+     */
+    function assertNoPattern($pattern, $message = '%s') {
+        return $this->assert(
+                new NoPatternExpectation($pattern),
+                $this->_browser->getContent(),
+                $message);
+    }
+    
+    /**
+     *    @deprecated
+     */
+    function assertNoUnwantedPattern($pattern, $message = '%s') {
+        return $this->assertNoPattern($pattern, $message);
+    }
+    
+    /**
+     *    Checks that a cookie is set for the current page
+     *    and optionally checks the value.
+     *    @param string $name        Name of cookie to test.
+     *    @param string $expected    Expected value as a string or
+     *                               false if any value will do.
+     *    @param string $message     Message to display.
+     *    @return boolean            True if pass.
+     *    @access public
+     */
+    function assertCookie($name, $expected = false, $message = '%s') {
+        $value = $this->getCookie($name);
+        if (! $expected) {
             return $this->assertTrue(
-                    $this->getCookie($name) === false,
-                    sprintf($message, "Not expecting cookie [$name]"));
+                    $value,
+                    sprintf($message, "Expecting cookie [$name]"));
         }
-
-        /**
-         *    Called from within the test methods to register
-         *    passes and failures.
-         *    @param boolean $result    Pass on true.
-         *    @param string $message    Message to display describing
-         *                              the test state.
-         *    @return boolean           True on pass
-         *    @access public
-         */
-        function assertTrue($result, $message = false) {
-            return $this->assert(new TrueExpectation(), $result, $message);
+        if (! SimpleExpectation::isExpectation($expected)) {
+            $expected = new EqualExpectation($expected);
         }
+        return $this->assert($expected, $value, "Expecting cookie [$name] -> $message");
+    }
+    
+    /**
+     *    Checks that no cookie is present or that it has
+     *    been successfully cleared.
+     *    @param string $name        Name of cookie to test.
+     *    @param string $message     Message to display.
+     *    @return boolean            True if pass.
+     *    @access public
+     */
+    function assertNoCookie($name, $message = '%s') {
+        return $this->assertTrue(
+                $this->getCookie($name) === false,
+                sprintf($message, "Not expecting cookie [$name]"));
+    }
 
-        /**
-         *    Will be true on false and vice versa. False
-         *    is the PHP definition of false, so that null,
-         *    empty strings, zero and an empty array all count
-         *    as false.
-         *    @param boolean $result    Pass on false.
-         *    @param string $message    Message to display.
-         *    @return boolean           True on pass
-         *    @access public
-         */
-        function assertFalse($result, $message = '%s') {
-            return $this->assert(new FalseExpectation(), $result, $message);
-        }
-        
-        /**
-         *    Will trigger a pass if the two parameters have
-         *    the same value only. Otherwise a fail. This
-         *    is for testing hand extracted text, etc.
-         *    @param mixed $first          Value to compare.
-         *    @param mixed $second         Value to compare.
-         *    @param string $message       Message to display.
-         *    @return boolean              True on pass
-         *    @access public
-         */
-        function assertEqual($first, $second, $message = '%s') {
-            return $this->assert(
-                    new EqualExpectation($first),
-                    $second,
-                    $message);
-        }
-        
-        /**
-         *    Will trigger a pass if the two parameters have
-         *    a different value. Otherwise a fail. This
-         *    is for testing hand extracted text, etc.
-         *    @param mixed $first           Value to compare.
-         *    @param mixed $second          Value to compare.
-         *    @param string $message        Message to display.
-         *    @return boolean               True on pass
-         *    @access public
-         */
-        function assertNotEqual($first, $second, $message = '%s') {
-            return $this->assert(
-                    new NotEqualExpectation($first),
-                    $second,
-                    $message);
-        }
+    /**
+     *    Called from within the test methods to register
+     *    passes and failures.
+     *    @param boolean $result    Pass on true.
+     *    @param string $message    Message to display describing
+     *                              the test state.
+     *    @return boolean           True on pass
+     *    @access public
+     */
+    function assertTrue($result, $message = false) {
+        return $this->assert(new TrueExpectation(), $result, $message);
+    }
 
-        /**
-         *    Uses a stack trace to find the line of an assertion.
-         *    @return string           Line number of first assert*
-         *                             method embedded in format string.
-         *    @access public
-         */
-        function getAssertionLine() {
-            $trace = new SimpleStackTrace(array('assert', 'click', 'pass', 'fail'));
-            return $trace->traceMethod();
-        }
+    /**
+     *    Will be true on false and vice versa. False
+     *    is the PHP definition of false, so that null,
+     *    empty strings, zero and an empty array all count
+     *    as false.
+     *    @param boolean $result    Pass on false.
+     *    @param string $message    Message to display.
+     *    @return boolean           True on pass
+     *    @access public
+     */
+    function assertFalse($result, $message = '%s') {
+        return $this->assert(new FalseExpectation(), $result, $message);
+    }
+    
+    /**
+     *    Will trigger a pass if the two parameters have
+     *    the same value only. Otherwise a fail. This
+     *    is for testing hand extracted text, etc.
+     *    @param mixed $first          Value to compare.
+     *    @param mixed $second         Value to compare.
+     *    @param string $message       Message to display.
+     *    @return boolean              True on pass
+     *    @access public
+     */
+    function assertEqual($first, $second, $message = '%s') {
+        return $this->assert(
+                new EqualExpectation($first),
+                $second,
+                $message);
+    }
+    
+    /**
+     *    Will trigger a pass if the two parameters have
+     *    a different value. Otherwise a fail. This
+     *    is for testing hand extracted text, etc.
+     *    @param mixed $first           Value to compare.
+     *    @param mixed $second          Value to compare.
+     *    @param string $message        Message to display.
+     *    @return boolean               True on pass
+     *    @access public
+     */
+    function assertNotEqual($first, $second, $message = '%s') {
+        return $this->assert(
+                new NotEqualExpectation($first),
+                $second,
+                $message);
+    }
+
+    /**
+     *    Uses a stack trace to find the line of an assertion.
+     *    @return string           Line number of first assert*
+     *                             method embedded in format string.
+     *    @access public
+     */
+    function getAssertionLine() {
+        $trace = new SimpleStackTrace(array('assert', 'click', 'pass', 'fail'));
+        return $trace->traceMethod();
     }
+}
 ?>
\ No newline at end of file
index 0de9a5ebe82984f6b94eb3d399a730a7c5608f3d..ce5f0bbe12536ece80556727b71b7a8fa32d9d6a 100644 (file)
 <?php
+/**
+ *  base include file for SimpleTest
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id$
+ */
+
+/**#@+
+ *  include other SimpleTest class files
+ */
+require_once(dirname(__FILE__) . '/scorer.php');
+/**#@-*/
+
+/**
+ *    Creates the XML needed for remote communication
+ *    by SimpleTest.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class XmlReporter extends SimpleReporter {
+    var $_indent;
+    var $_namespace;
+
     /**
-     * base include file for SimpleTest
-     * @package        SimpleTest
-     * @subpackage     UnitTester
-     * @version        $Id$
+     *    Sets up indentation and namespace.
+     *    @param string $namespace        Namespace to add to each tag.
+     *    @param string $indent           Indenting to add on each nesting.
+     *    @access public
      */
+    function XmlReporter($namespace = false, $indent = '  ') {
+        $this->SimpleReporter();
+        $this->_namespace = ($namespace ? $namespace . ':' : '');
+        $this->_indent = $indent;
+    }
 
-    /**#@+
-     * include other SimpleTest class files
+    /**
+     *    Calculates the pretty printing indent level
+     *    from the current level of nesting.
+     *    @param integer $offset  Extra indenting level.
+     *    @return string          Leading space.
+     *    @access protected
      */
-    require_once(dirname(__FILE__) . '/scorer.php');
-    /**#@-*/
+    function _getIndent($offset = 0) {
+        return str_repeat(
+                $this->_indent,
+                count($this->getTestList()) + $offset);
+    }
 
     /**
-     *    Creates the XML needed for remote communication
-     *    by SimpleTest.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Converts character string to parsed XML
+     *    entities string.
+     *    @param string text        Unparsed character data.
+     *    @return string            Parsed character data.
+     *    @access public
      */
-    class XmlReporter extends SimpleReporter {
-        var $_indent;
-        var $_namespace;
-
-        /**
-         *    Sets up indentation and namespace.
-         *    @param string $namespace        Namespace to add to each tag.
-         *    @param string $indent           Indenting to add on each nesting.
-         *    @access public
-         */
-        function XmlReporter($namespace = false, $indent = '  ') {
-            $this->SimpleReporter();
-            $this->_namespace = ($namespace ? $namespace . ':' : '');
-            $this->_indent = $indent;
-        }
+    function toParsedXml($text) {
+        return str_replace(
+                array('&', '<', '>', '"', '\''),
+                array('&amp;', '&lt;', '&gt;', '&quot;', '&apos;'),
+                $text);
+    }
 
-        /**
-         *    Calculates the pretty printing indent level
-         *    from the current level of nesting.
-         *    @param integer $offset  Extra indenting level.
-         *    @return string          Leading space.
-         *    @access protected
-         */
-        function _getIndent($offset = 0) {
-            return str_repeat(
-                    $this->_indent,
-                    count($this->getTestList()) + $offset);
-        }
+    /**
+     *    Paints the start of a group test.
+     *    @param string $test_name   Name of test that is starting.
+     *    @param integer $size       Number of test cases starting.
+     *    @access public
+     */
+    function paintGroupStart($test_name, $size) {
+        parent::paintGroupStart($test_name, $size);
+        print $this->_getIndent();
+        print "<" . $this->_namespace . "group size=\"$size\">\n";
+        print $this->_getIndent(1);
+        print "<" . $this->_namespace . "name>" .
+                $this->toParsedXml($test_name) .
+                "</" . $this->_namespace . "name>\n";
+    }
 
-        /**
-         *    Converts character string to parsed XML
-         *    entities string.
-         *    @param string text        Unparsed character data.
-         *    @return string            Parsed character data.
-         *    @access public
-         */
-        function toParsedXml($text) {
-            return str_replace(
-                    array('&', '<', '>', '"', '\''),
-                    array('&amp;', '&lt;', '&gt;', '&quot;', '&apos;'),
-                    $text);
-        }
+    /**
+     *    Paints the end of a group test.
+     *    @param string $test_name   Name of test that is ending.
+     *    @access public
+     */
+    function paintGroupEnd($test_name) {
+        print $this->_getIndent();
+        print "</" . $this->_namespace . "group>\n";
+        parent::paintGroupEnd($test_name);
+    }
 
-        /**
-         *    Paints the start of a group test.
-         *    @param string $test_name   Name of test that is starting.
-         *    @param integer $size       Number of test cases starting.
-         *    @access public
-         */
-        function paintGroupStart($test_name, $size) {
-            parent::paintGroupStart($test_name, $size);
-            print $this->_getIndent();
-            print "<" . $this->_namespace . "group size=\"$size\">\n";
-            print $this->_getIndent(1);
-            print "<" . $this->_namespace . "name>" .
-                    $this->toParsedXml($test_name) .
-                    "</" . $this->_namespace . "name>\n";
-        }
+    /**
+     *    Paints the start of a test case.
+     *    @param string $test_name   Name of test that is starting.
+     *    @access public
+     */
+    function paintCaseStart($test_name) {
+        parent::paintCaseStart($test_name);
+        print $this->_getIndent();
+        print "<" . $this->_namespace . "case>\n";
+        print $this->_getIndent(1);
+        print "<" . $this->_namespace . "name>" .
+                $this->toParsedXml($test_name) .
+                "</" . $this->_namespace . "name>\n";
+    }
 
-        /**
-         *    Paints the end of a group test.
-         *    @param string $test_name   Name of test that is ending.
-         *    @access public
-         */
-        function paintGroupEnd($test_name) {
-            print $this->_getIndent();
-            print "</" . $this->_namespace . "group>\n";
-            parent::paintGroupEnd($test_name);
-        }
+    /**
+     *    Paints the end of a test case.
+     *    @param string $test_name   Name of test that is ending.
+     *    @access public
+     */
+    function paintCaseEnd($test_name) {
+        print $this->_getIndent();
+        print "</" . $this->_namespace . "case>\n";
+        parent::paintCaseEnd($test_name);
+    }
 
-        /**
-         *    Paints the start of a test case.
-         *    @param string $test_name   Name of test that is starting.
-         *    @access public
-         */
-        function paintCaseStart($test_name) {
-            parent::paintCaseStart($test_name);
-            print $this->_getIndent();
-            print "<" . $this->_namespace . "case>\n";
-            print $this->_getIndent(1);
-            print "<" . $this->_namespace . "name>" .
-                    $this->toParsedXml($test_name) .
-                    "</" . $this->_namespace . "name>\n";
-        }
+    /**
+     *    Paints the start of a test method.
+     *    @param string $test_name   Name of test that is starting.
+     *    @access public
+     */
+    function paintMethodStart($test_name) {
+        parent::paintMethodStart($test_name);
+        print $this->_getIndent();
+        print "<" . $this->_namespace . "test>\n";
+        print $this->_getIndent(1);
+        print "<" . $this->_namespace . "name>" .
+                $this->toParsedXml($test_name) .
+                "</" . $this->_namespace . "name>\n";
+    }
 
-        /**
-         *    Paints the end of a test case.
-         *    @param string $test_name   Name of test that is ending.
-         *    @access public
-         */
-        function paintCaseEnd($test_name) {
-            print $this->_getIndent();
-            print "</" . $this->_namespace . "case>\n";
-            parent::paintCaseEnd($test_name);
-        }
+    /**
+     *    Paints the end of a test method.
+     *    @param string $test_name   Name of test that is ending.
+     *    @param integer $progress   Number of test cases ending.
+     *    @access public
+     */
+    function paintMethodEnd($test_name) {
+        print $this->_getIndent();
+        print "</" . $this->_namespace . "test>\n";
+        parent::paintMethodEnd($test_name);
+    }
 
-        /**
-         *    Paints the start of a test method.
-         *    @param string $test_name   Name of test that is starting.
-         *    @access public
-         */
-        function paintMethodStart($test_name) {
-            parent::paintMethodStart($test_name);
-            print $this->_getIndent();
-            print "<" . $this->_namespace . "test>\n";
-            print $this->_getIndent(1);
-            print "<" . $this->_namespace . "name>" .
-                    $this->toParsedXml($test_name) .
-                    "</" . $this->_namespace . "name>\n";
-        }
+    /**
+     *    Paints pass as XML.
+     *    @param string $message        Message to encode.
+     *    @access public
+     */
+    function paintPass($message) {
+        parent::paintPass($message);
+        print $this->_getIndent(1);
+        print "<" . $this->_namespace . "pass>";
+        print $this->toParsedXml($message);
+        print "</" . $this->_namespace . "pass>\n";
+    }
 
-        /**
-         *    Paints the end of a test method.
-         *    @param string $test_name   Name of test that is ending.
-         *    @param integer $progress   Number of test cases ending.
-         *    @access public
-         */
-        function paintMethodEnd($test_name) {
-            print $this->_getIndent();
-            print "</" . $this->_namespace . "test>\n";
-            parent::paintMethodEnd($test_name);
-        }
+    /**
+     *    Paints failure as XML.
+     *    @param string $message        Message to encode.
+     *    @access public
+     */
+    function paintFail($message) {
+        parent::paintFail($message);
+        print $this->_getIndent(1);
+        print "<" . $this->_namespace . "fail>";
+        print $this->toParsedXml($message);
+        print "</" . $this->_namespace . "fail>\n";
+    }
 
-        /**
-                *    Paints pass as XML.
-         *    @param string $message        Message to encode.
-         *    @access public
-         */
-        function paintPass($message) {
-            parent::paintPass($message);
-            print $this->_getIndent(1);
-            print "<" . $this->_namespace . "pass>";
-            print $this->toParsedXml($message);
-            print "</" . $this->_namespace . "pass>\n";
-        }
+    /**
+     *    Paints error as XML.
+     *    @param string $message        Message to encode.
+     *    @access public
+     */
+    function paintError($message) {
+        parent::paintError($message);
+        print $this->_getIndent(1);
+        print "<" . $this->_namespace . "exception>";
+        print $this->toParsedXml($message);
+        print "</" . $this->_namespace . "exception>\n";
+    }
 
-        /**
-                *    Paints failure as XML.
-         *    @param string $message        Message to encode.
-         *    @access public
-         */
-        function paintFail($message) {
-            parent::paintFail($message);
-            print $this->_getIndent(1);
-            print "<" . $this->_namespace . "fail>";
-            print $this->toParsedXml($message);
-            print "</" . $this->_namespace . "fail>\n";
-        }
+    /**
+     *    Paints exception as XML.
+     *    @param Exception $exception    Exception to encode.
+     *    @access public
+     */
+    function paintException($exception) {
+        parent::paintException($exception);
+        print $this->_getIndent(1);
+        print "<" . $this->_namespace . "exception>";
+        $message = 'Unexpected exception of type [' . get_class($exception) .
+                '] with message ['. $exception->getMessage() .
+                '] in ['. $exception->getFile() .
+                ' line ' . $exception->getLine() . ']';
+        print $this->toParsedXml($message);
+        print "</" . $this->_namespace . "exception>\n";
+    }
 
-        /**
-                *    Paints error as XML.
-         *    @param string $message        Message to encode.
-         *    @access public
-         */
-        function paintError($message) {
-            parent::paintError($message);
-            print $this->_getIndent(1);
-            print "<" . $this->_namespace . "exception>";
-            print $this->toParsedXml($message);
-            print "</" . $this->_namespace . "exception>\n";
-        }
+    /**
+     *    Paints the skipping message and tag.
+     *    @param string $message        Text to display in skip tag.
+     *    @access public
+     */
+    function paintSkip($message) {
+        parent::paintSkip($message);
+        print $this->_getIndent(1);
+        print "<" . $this->_namespace . "skip>";
+        print $this->toParsedXml($message);
+        print "</" . $this->_namespace . "skip>\n";
+    }
 
-        /**
-                *    Paints exception as XML.
-         *    @param Exception $exception    Exception to encode.
-         *    @access public
-         */
-        function paintException($exception) {
-            parent::paintException($exception);
-            print $this->_getIndent(1);
-            print "<" . $this->_namespace . "exception>";
-            $message = 'Unexpected exception of type [' . get_class($exception) .
-                    '] with message ['. $exception->getMessage() .
-                    '] in ['. $exception->getFile() .
-                    ' line ' . $exception->getLine() . ']';
-            print $this->toParsedXml($message);
-            print "</" . $this->_namespace . "exception>\n";
-        }
+    /**
+     *    Paints a simple supplementary message.
+     *    @param string $message        Text to display.
+     *    @access public
+     */
+    function paintMessage($message) {
+        parent::paintMessage($message);
+        print $this->_getIndent(1);
+        print "<" . $this->_namespace . "message>";
+        print $this->toParsedXml($message);
+        print "</" . $this->_namespace . "message>\n";
+    }
 
-        /**
-         *    Paints the skipping message and tag.
-         *    @param string $message        Text to display in skip tag.
-         *    @access public
-         */
-        function paintSkip($message) {
-            parent::paintSkip($message);
-            print $this->_getIndent(1);
-            print "<" . $this->_namespace . "skip>";
-            print $this->toParsedXml($message);
-            print "</" . $this->_namespace . "skip>\n";
-        }
+    /**
+     *    Paints a formatted ASCII message such as a
+     *    variable dump.
+     *    @param string $message        Text to display.
+     *    @access public
+     */
+    function paintFormattedMessage($message) {
+        parent::paintFormattedMessage($message);
+        print $this->_getIndent(1);
+        print "<" . $this->_namespace . "formatted>";
+        print "<![CDATA[$message]]>";
+        print "</" . $this->_namespace . "formatted>\n";
+    }
 
-        /**
-         *    Paints a simple supplementary message.
-         *    @param string $message        Text to display.
-         *    @access public
-         */
-        function paintMessage($message) {
-            parent::paintMessage($message);
-            print $this->_getIndent(1);
-            print "<" . $this->_namespace . "message>";
-            print $this->toParsedXml($message);
-            print "</" . $this->_namespace . "message>\n";
-        }
+    /**
+     *    Serialises the event object.
+     *    @param string $type        Event type as text.
+     *    @param mixed $payload      Message or object.
+     *    @access public
+     */
+    function paintSignal($type, $payload) {
+        parent::paintSignal($type, $payload);
+        print $this->_getIndent(1);
+        print "<" . $this->_namespace . "signal type=\"$type\">";
+        print "<![CDATA[" . serialize($payload) . "]]>";
+        print "</" . $this->_namespace . "signal>\n";
+    }
 
-        /**
-         *    Paints a formatted ASCII message such as a
-         *    variable dump.
-         *    @param string $message        Text to display.
-         *    @access public
-         */
-        function paintFormattedMessage($message) {
-            parent::paintFormattedMessage($message);
-            print $this->_getIndent(1);
-            print "<" . $this->_namespace . "formatted>";
-            print "<![CDATA[$message]]>";
-            print "</" . $this->_namespace . "formatted>\n";
+    /**
+     *    Paints the test document header.
+     *    @param string $test_name     First test top level
+     *                                 to start.
+     *    @access public
+     *    @abstract
+     */
+    function paintHeader($test_name) {
+        if (! SimpleReporter::inCli()) {
+            header('Content-type: text/xml');
         }
-
-        /**
-         *    Serialises the event object.
-         *    @param string $type        Event type as text.
-         *    @param mixed $payload      Message or object.
-         *    @access public
-         */
-        function paintSignal($type, &$payload) {
-            parent::paintSignal($type, $payload);
-            print $this->_getIndent(1);
-            print "<" . $this->_namespace . "signal type=\"$type\">";
-            print "<![CDATA[" . serialize($payload) . "]]>";
-            print "</" . $this->_namespace . "signal>\n";
+        print "<?xml version=\"1.0\"";
+        if ($this->_namespace) {
+            print " xmlns:" . $this->_namespace .
+                    "=\"www.lastcraft.com/SimpleTest/Beta3/Report\"";
         }
+        print "?>\n";
+        print "<" . $this->_namespace . "run>\n";
+    }
 
-        /**
-         *    Paints the test document header.
-         *    @param string $test_name     First test top level
-         *                                 to start.
-         *    @access public
-         *    @abstract
-         */
-        function paintHeader($test_name) {
-            if (! SimpleReporter::inCli()) {
-                header('Content-type: text/xml');
-            }
-            print "<?xml version=\"1.0\"";
-            if ($this->_namespace) {
-                print " xmlns:" . $this->_namespace .
-                        "=\"www.lastcraft.com/SimpleTest/Beta3/Report\"";
-            }
-            print "?>\n";
-            print "<" . $this->_namespace . "run>\n";
-        }
+    /**
+     *    Paints the test document footer.
+     *    @param string $test_name        The top level test.
+     *    @access public
+     *    @abstract
+     */
+    function paintFooter($test_name) {
+        print "</" . $this->_namespace . "run>\n";
+    }
+}
+
+/**
+ *    Accumulator for incoming tag. Holds the
+ *    incoming test structure information for
+ *    later dispatch to the reporter.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class NestingXmlTag {
+    var $_name;
+    var $_attributes;
 
-        /**
-         *    Paints the test document footer.
-         *    @param string $test_name        The top level test.
-         *    @access public
-         *    @abstract
-         */
-        function paintFooter($test_name) {
-            print "</" . $this->_namespace . "run>\n";
-        }
+    /**
+     *    Sets the basic test information except
+     *    the name.
+     *    @param hash $attributes   Name value pairs.
+     *    @access public
+     */
+    function NestingXmlTag($attributes) {
+        $this->_name = false;
+        $this->_attributes = $attributes;
     }
 
     /**
-     *    Accumulator for incoming tag. Holds the
-     *    incoming test structure information for
-     *    later dispatch to the reporter.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Sets the test case/method name.
+     *    @param string $name        Name of test.
+     *    @access public
      */
-    class NestingXmlTag {
-        var $_name;
-        var $_attributes;
+    function setName($name) {
+        $this->_name = $name;
+    }
 
-        /**
-         *    Sets the basic test information except
-         *    the name.
-         *    @param hash $attributes   Name value pairs.
-         *    @access public
-         */
-        function NestingXmlTag($attributes) {
-            $this->_name = false;
-            $this->_attributes = $attributes;
-        }
+    /**
+     *    Accessor for name.
+     *    @return string        Name of test.
+     *    @access public
+     */
+    function getName() {
+        return $this->_name;
+    }
 
-        /**
-         *    Sets the test case/method name.
-         *    @param string $name        Name of test.
-         *    @access public
-         */
-        function setName($name) {
-            $this->_name = $name;
-        }
+    /**
+     *    Accessor for attributes.
+     *    @return hash        All attributes.
+     *    @access protected
+     */
+    function _getAttributes() {
+        return $this->_attributes;
+    }
+}
 
-        /**
-         *    Accessor for name.
-         *    @return string        Name of test.
-         *    @access public
-         */
-        function getName() {
-            return $this->_name;
-        }
+/**
+ *    Accumulator for incoming method tag. Holds the
+ *    incoming test structure information for
+ *    later dispatch to the reporter.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class NestingMethodTag extends NestingXmlTag {
 
-        /**
-         *    Accessor for attributes.
-         *    @return hash        All attributes.
-         *    @access protected
-         */
-        function _getAttributes() {
-            return $this->_attributes;
-        }
+    /**
+     *    Sets the basic test information except
+     *    the name.
+     *    @param hash $attributes   Name value pairs.
+     *    @access public
+     */
+    function NestingMethodTag($attributes) {
+        $this->NestingXmlTag($attributes);
     }
 
     /**
-     *    Accumulator for incoming method tag. Holds the
-     *    incoming test structure information for
-     *    later dispatch to the reporter.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Signals the appropriate start event on the
+     *    listener.
+     *    @param SimpleReporter $listener    Target for events.
+     *    @access public
      */
-    class NestingMethodTag extends NestingXmlTag {
+    function paintStart(&$listener) {
+        $listener->paintMethodStart($this->getName());
+    }
 
-        /**
-         *    Sets the basic test information except
-         *    the name.
-         *    @param hash $attributes   Name value pairs.
-         *    @access public
-         */
-        function NestingMethodTag($attributes) {
-            $this->NestingXmlTag($attributes);
-        }
+    /**
+     *    Signals the appropriate end event on the
+     *    listener.
+     *    @param SimpleReporter $listener    Target for events.
+     *    @access public
+     */
+    function paintEnd(&$listener) {
+        $listener->paintMethodEnd($this->getName());
+    }
+}
 
-        /**
-         *    Signals the appropriate start event on the
-         *    listener.
-         *    @param SimpleReporter $listener    Target for events.
-         *    @access public
-         */
-        function paintStart(&$listener) {
-            $listener->paintMethodStart($this->getName());
-        }
+/**
+ *    Accumulator for incoming case tag. Holds the
+ *    incoming test structure information for
+ *    later dispatch to the reporter.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class NestingCaseTag extends NestingXmlTag {
 
-        /**
-         *    Signals the appropriate end event on the
-         *    listener.
-         *    @param SimpleReporter $listener    Target for events.
-         *    @access public
-         */
-        function paintEnd(&$listener) {
-            $listener->paintMethodEnd($this->getName());
-        }
+    /**
+     *    Sets the basic test information except
+     *    the name.
+     *    @param hash $attributes   Name value pairs.
+     *    @access public
+     */
+    function NestingCaseTag($attributes) {
+        $this->NestingXmlTag($attributes);
     }
 
     /**
-     *    Accumulator for incoming case tag. Holds the
-     *    incoming test structure information for
-     *    later dispatch to the reporter.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Signals the appropriate start event on the
+     *    listener.
+     *    @param SimpleReporter $listener    Target for events.
+     *    @access public
      */
-    class NestingCaseTag extends NestingXmlTag {
+    function paintStart(&$listener) {
+        $listener->paintCaseStart($this->getName());
+    }
 
-        /**
-         *    Sets the basic test information except
-         *    the name.
-         *    @param hash $attributes   Name value pairs.
-         *    @access public
-         */
-        function NestingCaseTag($attributes) {
-            $this->NestingXmlTag($attributes);
-        }
+    /**
+     *    Signals the appropriate end event on the
+     *    listener.
+     *    @param SimpleReporter $listener    Target for events.
+     *    @access public
+     */
+    function paintEnd(&$listener) {
+        $listener->paintCaseEnd($this->getName());
+    }
+}
 
-        /**
-         *    Signals the appropriate start event on the
-         *    listener.
-         *    @param SimpleReporter $listener    Target for events.
-         *    @access public
-         */
-        function paintStart(&$listener) {
-            $listener->paintCaseStart($this->getName());
-        }
+/**
+ *    Accumulator for incoming group tag. Holds the
+ *    incoming test structure information for
+ *    later dispatch to the reporter.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class NestingGroupTag extends NestingXmlTag {
 
-        /**
-         *    Signals the appropriate end event on the
-         *    listener.
-         *    @param SimpleReporter $listener    Target for events.
-         *    @access public
-         */
-        function paintEnd(&$listener) {
-            $listener->paintCaseEnd($this->getName());
-        }
+    /**
+     *    Sets the basic test information except
+     *    the name.
+     *    @param hash $attributes   Name value pairs.
+     *    @access public
+     */
+    function NestingGroupTag($attributes) {
+        $this->NestingXmlTag($attributes);
     }
 
     /**
-     *    Accumulator for incoming group tag. Holds the
-     *    incoming test structure information for
-     *    later dispatch to the reporter.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
+     *    Signals the appropriate start event on the
+     *    listener.
+     *    @param SimpleReporter $listener    Target for events.
+     *    @access public
      */
-    class NestingGroupTag extends NestingXmlTag {
-
-        /**
-         *    Sets the basic test information except
-         *    the name.
-         *    @param hash $attributes   Name value pairs.
-         *    @access public
-         */
-        function NestingGroupTag($attributes) {
-            $this->NestingXmlTag($attributes);
-        }
-
-        /**
-         *    Signals the appropriate start event on the
-         *    listener.
-         *    @param SimpleReporter $listener    Target for events.
-         *    @access public
-         */
-        function paintStart(&$listener) {
-            $listener->paintGroupStart($this->getName(), $this->getSize());
-        }
+    function paintStart(&$listener) {
+        $listener->paintGroupStart($this->getName(), $this->getSize());
+    }
 
-        /**
-         *    Signals the appropriate end event on the
-         *    listener.
-         *    @param SimpleReporter $listener    Target for events.
-         *    @access public
-         */
-        function paintEnd(&$listener) {
-            $listener->paintGroupEnd($this->getName());
-        }
+    /**
+     *    Signals the appropriate end event on the
+     *    listener.
+     *    @param SimpleReporter $listener    Target for events.
+     *    @access public
+     */
+    function paintEnd(&$listener) {
+        $listener->paintGroupEnd($this->getName());
+    }
 
-        /**
-         *    The size in the attributes.
-         *    @return integer     Value of size attribute or zero.
-         *    @access public
-         */
-        function getSize() {
-            $attributes = $this->_getAttributes();
-            if (isset($attributes['SIZE'])) {
-                return (integer)$attributes['SIZE'];
-            }
-            return 0;
+    /**
+     *    The size in the attributes.
+     *    @return integer     Value of size attribute or zero.
+     *    @access public
+     */
+    function getSize() {
+        $attributes = $this->_getAttributes();
+        if (isset($attributes['SIZE'])) {
+            return (integer)$attributes['SIZE'];
         }
+        return 0;
     }
+}
+
+/**
+ *    Parser for importing the output of the XmlReporter.
+ *    Dispatches that output to another reporter.
+ *    @package SimpleTest
+ *    @subpackage UnitTester
+ */
+class SimpleTestXmlParser {
+    var $_listener;
+    var $_expat;
+    var $_tag_stack;
+    var $_in_content_tag;
+    var $_content;
+    var $_attributes;
 
     /**
-     *    Parser for importing the output of the XmlReporter.
-     *    Dispatches that output to another reporter.
-        *        @package SimpleTest
-        *        @subpackage UnitTester
-     */
-    class SimpleTestXmlParser {
-        var $_listener;
-        var $_expat;
-        var $_tag_stack;
-        var $_in_content_tag;
-        var $_content;
-        var $_attributes;
-
-        /**
-         *    Loads a listener with the SimpleReporter
-         *    interface.
-         *    @param SimpleReporter $listener   Listener of tag events.
-         *    @access public
-         */
-        function SimpleTestXmlParser(&$listener) {
-            $this->_listener = &$listener;
-            $this->_expat = &$this->_createParser();
-            $this->_tag_stack = array();
-            $this->_in_content_tag = false;
-            $this->_content = '';
-            $this->_attributes = array();
-        }
+     *    Loads a listener with the SimpleReporter
+     *    interface.
+     *    @param SimpleReporter $listener   Listener of tag events.
+     *    @access public
+     */
+    function SimpleTestXmlParser(&$listener) {
+        $this->_listener = &$listener;
+        $this->_expat = &$this->_createParser();
+        $this->_tag_stack = array();
+        $this->_in_content_tag = false;
+        $this->_content = '';
+        $this->_attributes = array();
+    }
 
-        /**
-         *    Parses a block of XML sending the results to
-         *    the listener.
-         *    @param string $chunk        Block of text to read.
-         *    @return boolean             True if valid XML.
-         *    @access public
-         */
-        function parse($chunk) {
-            if (! xml_parse($this->_expat, $chunk)) {
-                trigger_error('XML parse error with ' .
-                        xml_error_string(xml_get_error_code($this->_expat)));
-                return false;
-            }
-            return true;
+    /**
+     *    Parses a block of XML sending the results to
+     *    the listener.
+     *    @param string $chunk        Block of text to read.
+     *    @return boolean             True if valid XML.
+     *    @access public
+     */
+    function parse($chunk) {
+        if (! xml_parse($this->_expat, $chunk)) {
+            trigger_error('XML parse error with ' .
+                    xml_error_string(xml_get_error_code($this->_expat)));
+            return false;
         }
+        return true;
+    }
 
-        /**
-         *    Sets up expat as the XML parser.
-         *    @return resource        Expat handle.
-         *    @access protected
-         */
-        function &_createParser() {
-            $expat = xml_parser_create();
-            xml_set_object($expat, $this);
-            xml_set_element_handler($expat, '_startElement', '_endElement');
-            xml_set_character_data_handler($expat, '_addContent');
-            xml_set_default_handler($expat, '_default');
-            return $expat;
-        }
+    /**
+     *    Sets up expat as the XML parser.
+     *    @return resource        Expat handle.
+     *    @access protected
+     */
+    function &_createParser() {
+        $expat = xml_parser_create();
+        xml_set_object($expat, $this);
+        xml_set_element_handler($expat, '_startElement', '_endElement');
+        xml_set_character_data_handler($expat, '_addContent');
+        xml_set_default_handler($expat, '_default');
+        return $expat;
+    }
 
-        /**
-         *    Opens a new test nesting level.
-         *    @return NestedXmlTag     The group, case or method tag
-         *                             to start.
-         *    @access private
-         */
-        function _pushNestingTag($nested) {
-            array_unshift($this->_tag_stack, $nested);
-        }
+    /**
+     *    Opens a new test nesting level.
+     *    @return NestedXmlTag     The group, case or method tag
+     *                             to start.
+     *    @access private
+     */
+    function _pushNestingTag($nested) {
+        array_unshift($this->_tag_stack, $nested);
+    }
 
-        /**
-         *    Accessor for current test structure tag.
-         *    @return NestedXmlTag     The group, case or method tag
-         *                             being parsed.
-         *    @access private
-         */
-        function &_getCurrentNestingTag() {
-            return $this->_tag_stack[0];
-        }
+    /**
+     *    Accessor for current test structure tag.
+     *    @return NestedXmlTag     The group, case or method tag
+     *                             being parsed.
+     *    @access private
+     */
+    function &_getCurrentNestingTag() {
+        return $this->_tag_stack[0];
+    }
 
-        /**
-         *    Ends a nesting tag.
-         *    @return NestedXmlTag     The group, case or method tag
-         *                             just finished.
-         *    @access private
-         */
-        function _popNestingTag() {
-            return array_shift($this->_tag_stack);
-        }
+    /**
+     *    Ends a nesting tag.
+     *    @return NestedXmlTag     The group, case or method tag
+     *                             just finished.
+     *    @access private
+     */
+    function _popNestingTag() {
+        return array_shift($this->_tag_stack);
+    }
 
-        /**
-         *    Test if tag is a leaf node with only text content.
-         *    @param string $tag        XML tag name.
-         *    @return @boolean          True if leaf, false if nesting.
-         *    @private
-         */
-        function _isLeaf($tag) {
-            return in_array($tag, array(
-                    'NAME', 'PASS', 'FAIL', 'EXCEPTION', 'SKIP', 'MESSAGE', 'FORMATTED', 'SIGNAL'));
-        }
+    /**
+     *    Test if tag is a leaf node with only text content.
+     *    @param string $tag        XML tag name.
+     *    @return @boolean          True if leaf, false if nesting.
+     *    @private
+     */
+    function _isLeaf($tag) {
+        return in_array($tag, array(
+                'NAME', 'PASS', 'FAIL', 'EXCEPTION', 'SKIP', 'MESSAGE', 'FORMATTED', 'SIGNAL'));
+    }
 
-        /**
-         *    Handler for start of event element.
-         *    @param resource $expat     Parser handle.
-         *    @param string $tag         Element name.
-         *    @param hash $attributes    Name value pairs.
-         *                               Attributes without content
-         *                               are marked as true.
-         *    @access protected
-         */
-        function _startElement($expat, $tag, $attributes) {
-            $this->_attributes = $attributes;
-            if ($tag == 'GROUP') {
-                $this->_pushNestingTag(new NestingGroupTag($attributes));
-            } elseif ($tag == 'CASE') {
-                $this->_pushNestingTag(new NestingCaseTag($attributes));
-            } elseif ($tag == 'TEST') {
-                $this->_pushNestingTag(new NestingMethodTag($attributes));
-            } elseif ($this->_isLeaf($tag)) {
-                $this->_in_content_tag = true;
-                $this->_content = '';
-            }
+    /**
+     *    Handler for start of event element.
+     *    @param resource $expat     Parser handle.
+     *    @param string $tag         Element name.
+     *    @param hash $attributes    Name value pairs.
+     *                               Attributes without content
+     *                               are marked as true.
+     *    @access protected
+     */
+    function _startElement($expat, $tag, $attributes) {
+        $this->_attributes = $attributes;
+        if ($tag == 'GROUP') {
+            $this->_pushNestingTag(new NestingGroupTag($attributes));
+        } elseif ($tag == 'CASE') {
+            $this->_pushNestingTag(new NestingCaseTag($attributes));
+        } elseif ($tag == 'TEST') {
+            $this->_pushNestingTag(new NestingMethodTag($attributes));
+        } elseif ($this->_isLeaf($tag)) {
+            $this->_in_content_tag = true;
+            $this->_content = '';
         }
+    }
 
-        /**
-         *    End of element event.
-         *    @param resource $expat     Parser handle.
-         *    @param string $tag         Element name.
-         *    @access protected
-         */
-        function _endElement($expat, $tag) {
-            $this->_in_content_tag = false;
-            if (in_array($tag, array('GROUP', 'CASE', 'TEST'))) {
-                $nesting_tag = $this->_popNestingTag();
-                $nesting_tag->paintEnd($this->_listener);
-            } elseif ($tag == 'NAME') {
-                $nesting_tag = &$this->_getCurrentNestingTag();
-                $nesting_tag->setName($this->_content);
-                $nesting_tag->paintStart($this->_listener);
-            } elseif ($tag == 'PASS') {
-                $this->_listener->paintPass($this->_content);
-            } elseif ($tag == 'FAIL') {
-                $this->_listener->paintFail($this->_content);
-            } elseif ($tag == 'EXCEPTION') {
-                $this->_listener->paintError($this->_content);
-            } elseif ($tag == 'SKIP') {
-                $this->_listener->paintSkip($this->_content);
-            } elseif ($tag == 'SIGNAL') {
-                $this->_listener->paintSignal(
-                        $this->_attributes['TYPE'],
-                        unserialize($this->_content));
-            } elseif ($tag == 'MESSAGE') {
-                $this->_listener->paintMessage($this->_content);
-            } elseif ($tag == 'FORMATTED') {
-                $this->_listener->paintFormattedMessage($this->_content);
-            }
+    /**
+     *    End of element event.
+     *    @param resource $expat     Parser handle.
+     *    @param string $tag         Element name.
+     *    @access protected
+     */
+    function _endElement($expat, $tag) {
+        $this->_in_content_tag = false;
+        if (in_array($tag, array('GROUP', 'CASE', 'TEST'))) {
+            $nesting_tag = $this->_popNestingTag();
+            $nesting_tag->paintEnd($this->_listener);
+        } elseif ($tag == 'NAME') {
+            $nesting_tag = &$this->_getCurrentNestingTag();
+            $nesting_tag->setName($this->_content);
+            $nesting_tag->paintStart($this->_listener);
+        } elseif ($tag == 'PASS') {
+            $this->_listener->paintPass($this->_content);
+        } elseif ($tag == 'FAIL') {
+            $this->_listener->paintFail($this->_content);
+        } elseif ($tag == 'EXCEPTION') {
+            $this->_listener->paintError($this->_content);
+        } elseif ($tag == 'SKIP') {
+            $this->_listener->paintSkip($this->_content);
+        } elseif ($tag == 'SIGNAL') {
+            $this->_listener->paintSignal(
+                    $this->_attributes['TYPE'],
+                    unserialize($this->_content));
+        } elseif ($tag == 'MESSAGE') {
+            $this->_listener->paintMessage($this->_content);
+        } elseif ($tag == 'FORMATTED') {
+            $this->_listener->paintFormattedMessage($this->_content);
         }
+    }
 
-        /**
-         *    Content between start and end elements.
-         *    @param resource $expat     Parser handle.
-         *    @param string $text        Usually output messages.
-         *    @access protected
-         */
-        function _addContent($expat, $text) {
-            if ($this->_in_content_tag) {
-                $this->_content .= $text;
-            }
-            return true;
+    /**
+     *    Content between start and end elements.
+     *    @param resource $expat     Parser handle.
+     *    @param string $text        Usually output messages.
+     *    @access protected
+     */
+    function _addContent($expat, $text) {
+        if ($this->_in_content_tag) {
+            $this->_content .= $text;
         }
+        return true;
+    }
 
-        /**
-         *    XML and Doctype handler. Discards all such content.
-         *    @param resource $expat     Parser handle.
-         *    @param string $default     Text of default content.
-         *    @access protected
-         */
-        function _default($expat, $default) {
-        }
+    /**
+     *    XML and Doctype handler. Discards all such content.
+     *    @param resource $expat     Parser handle.
+     *    @param string $default     Text of default content.
+     *    @access protected
+     */
+    function _default($expat, $default) {
     }
+}
 ?>