From 85414e846ab11f92b69e8a584189464b2991f59f Mon Sep 17 00:00:00 2001 From: nicolasconnault Date: Thu, 26 Apr 2007 07:11:44 +0000 Subject: [PATCH] Upgraded simpletestlib from 1.0.1alpha3 to 1.0.1beta --- .../HELP_MY_TESTS_DONT_WORK_ANYMORE | 26 ++- lib/simpletestlib/LICENSE | 4 +- lib/simpletestlib/README | 2 +- lib/simpletestlib/VERSION | 2 +- lib/simpletestlib/browser.php | 82 +++++-- lib/simpletestlib/collector.php | 182 +++++++-------- lib/simpletestlib/compatibility.php | 27 +-- lib/simpletestlib/dumper.php | 42 ---- lib/simpletestlib/encoding.php | 2 +- lib/simpletestlib/errors.php | 126 ++++++++--- lib/simpletestlib/exceptions.php | 158 ++++++++++++- lib/simpletestlib/expectation.php | 109 ++++++++- .../extensions/pear_test_case.php | 16 +- .../extensions/phpunit_test_case.php | 2 +- lib/simpletestlib/mock_objects.php | 102 ++------- lib/simpletestlib/parser.php | 16 +- lib/simpletestlib/reflection_php5.php | 39 ++-- lib/simpletestlib/remote.php | 6 +- lib/simpletestlib/reporter.php | 67 +++++- lib/simpletestlib/scorer.php | 92 +++++++- lib/simpletestlib/shell_tester.php | 31 ++- lib/simpletestlib/simpletest.php | 213 ++++++++++++++--- lib/simpletestlib/test_case.php | 214 +++++++++--------- lib/simpletestlib/unit_tester.php | 164 +++++++++----- lib/simpletestlib/url.php | 4 +- lib/simpletestlib/web_tester.php | 196 +++++++++++----- lib/simpletestlib/xml.php | 51 ++++- 27 files changed, 1354 insertions(+), 621 deletions(-) diff --git a/lib/simpletestlib/HELP_MY_TESTS_DONT_WORK_ANYMORE b/lib/simpletestlib/HELP_MY_TESTS_DONT_WORK_ANYMORE index 2c9ae6f5b1..806a7a7056 100644 --- a/lib/simpletestlib/HELP_MY_TESTS_DONT_WORK_ANYMORE +++ b/lib/simpletestlib/HELP_MY_TESTS_DONT_WORK_ANYMORE @@ -5,6 +5,28 @@ 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 _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. + +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 +immediately. + +No method TestCase::signal() +---------------------------- +This has been deprecated in favour of triggering an error or +throwing an exception. Deprecated as of 1.0.1beta. + +No method TestCase::sendMessage() +--------------------------------- +This has been deprecated as of 1.0.1beta. + Failure to connect now emits failures ------------------------------------- It used to be that you would have to use the @@ -46,7 +68,7 @@ No method addPartialMockCode() ------------------------------ The ability to insert arbitrary partial mock code has been removed. This was a low value feature -causing needless complications.It was removed +causing needless complications. It was removed in the 1.0.1beta release. No method setMockBaseClass() @@ -139,7 +161,7 @@ My custom test case ignored by tally() The _assertTrue method has had it's signature changed due to a bug in the PHP 5.0.1 release. You must now use getTest() from within that method to get the test case. Mock compatibility with other -unit testers is now deprecated as of 1.0.1alpha as PEAR::PHUnit2 +unit testers is now deprecated as of 1.0.1alpha as PEAR::PHPUnit2 should soon have mock support of it's own. Broken code extending SimpleRunner diff --git a/lib/simpletestlib/LICENSE b/lib/simpletestlib/LICENSE index 4a1f72014e..09f465ab70 100644 --- a/lib/simpletestlib/LICENSE +++ b/lib/simpletestlib/LICENSE @@ -2,7 +2,7 @@ Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -485,7 +485,7 @@ convey the exclusion of warranty; and each file should have at least the You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. diff --git a/lib/simpletestlib/README b/lib/simpletestlib/README index c2ff9b757f..b8d1979205 100644 --- a/lib/simpletestlib/README +++ b/lib/simpletestlib/README @@ -1,7 +1,7 @@ SimpleTest ========== You probably got this package from... -http://sourceforge.net/projects/simpletest/ +http://simpletest.sourceforge.net/projects/simpletest/ If there is no licence agreement with this package please download a version from the location above. You must read and accept that diff --git a/lib/simpletestlib/VERSION b/lib/simpletestlib/VERSION index 9980c7df52..fff574851f 100644 --- a/lib/simpletestlib/VERSION +++ b/lib/simpletestlib/VERSION @@ -1 +1 @@ -1.0.1alpha3 \ No newline at end of file +1.0.1beta \ No newline at end of file diff --git a/lib/simpletestlib/browser.php b/lib/simpletestlib/browser.php index 462262af43..6b41d0b4b4 100644 --- a/lib/simpletestlib/browser.php +++ b/lib/simpletestlib/browser.php @@ -894,6 +894,17 @@ $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 some kind of label. Usually @@ -962,6 +973,17 @@ $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)); + } /** * Submits a form by the ID. @@ -981,16 +1003,16 @@ } /** - * Follows a link by label. Will click the first link + * 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 Page on success. + * @return string/boolean URL on success. * @access public */ - function clickLink($label, $index = 0) { + function getLink($label, $index = 0) { $urls = $this->_page->getUrlsByLabel($label); if (count($urls) == 0) { return false; @@ -998,18 +1020,36 @@ if (count($urls) < $index + 1) { return false; } - $this->_load($urls[$index], new SimpleGetEncoding()); - return $this->getContent(); + return $urls[$index]; } /** - * Tests to see if a link is present by label. - * @param string $label Text of value attribute. - * @return boolean True if link present. + * 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 isLink($label) { - return (count($this->_page->getUrlsByLabel($label)) > 0); + function getLinkById($id) { + return $this->_page->getUrlById($id); } /** @@ -1019,23 +1059,13 @@ * @access public */ function clickLinkById($id) { - if (! ($url = $this->_page->getUrlById($id))) { + if (! ($url = $this->getLinkById($id))) { return false; } $this->_load($url, new SimpleGetEncoding()); return $this->getContent(); } - /** - * Tests to see if a link is present by ID attribute. - * @param string $id Text of id attribute. - * @return boolean True if link present. - * @access public - */ - function isLinkById($id) { - return (boolean)$this->_page->getUrlById($id); - } - /** * Clicks a visible text item. Will first try buttons, * then links and then images. @@ -1053,5 +1083,15 @@ } 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 diff --git a/lib/simpletestlib/collector.php b/lib/simpletestlib/collector.php index 34229d8064..439f6b5e9d 100644 --- a/lib/simpletestlib/collector.php +++ b/lib/simpletestlib/collector.php @@ -1,115 +1,107 @@ - * @package SimpleTest - * @subpackage UnitTester - * @version $Id$ - */ - -/** - * The basic collector for {@link GroupTest} - * - * @see collect(), GroupTest::collect() - * @package SimpleTest - * @subpackage UnitTester - */ -class SimpleCollector { - /** - * Strips off any kind of slash at the end so as to normalise the path + * This file contains the following classes: {@link SimpleCollector}, + * {@link SimplePatternCollector}. * - * @param string $path Path to normalise. + * @author Travis Swicegood + * @package SimpleTest + * @subpackage UnitTester + * @version $Id$ */ - function _removeTrailingSlash($path) { - return preg_replace('|[\\/]$|', '', $path); - - /** - * @internal - * Try benchmarking the following. It's more code, but by not using the - * regex, it may be faster? Also, shouldn't be looking for - * DIRECTORY_SEPERATOR instead of a manual "/"? - */ - if (substr($path, -1) == DIRECTORY_SEPERATOR) { - 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() + * The basic collector for {@link GroupTest} + * + * @see collect(), GroupTest::collect() + * @package SimpleTest + * @subpackage UnitTester */ - function collect(&$test, $path) { - $path = $this->_removeTrailingSlash($path); - if ($handle = opendir($path)) { - while (($entry = readdir($handle)) !== false) { - $this->_handle($test, $path . DIRECTORY_SEPARATOR . $entry); + 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; } - 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); + /** + * 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); + } + closedir($handle); + } } - } -} - -/** - * 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; + /** + * 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); + } + } + } /** + * An extension to {@link SimpleCollector} that only adds files matching a + * given 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 + * @package SimpleTest + * @subpackage UnitTester + * @see SimpleCollector */ - function SimplePatternCollector($pattern = '/php$/i') { - $this->_pattern = $pattern; - } + 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); + /** + * 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 diff --git a/lib/simpletestlib/compatibility.php b/lib/simpletestlib/compatibility.php index 67b806a948..eefb6029b8 100644 --- a/lib/simpletestlib/compatibility.php +++ b/lib/simpletestlib/compatibility.php @@ -70,6 +70,9 @@ if (is_array($first) && is_array($second)) { return SimpleTestCompatibility::_isArrayOfIdenticalTypes($first, $second); } + if ($first !== $second) { + return false; + } return true; } @@ -105,8 +108,7 @@ * @static */ function isReference(&$first, &$second) { - if (version_compare(phpversion(), '5', '>=') - && is_object($first)) { + if (version_compare(phpversion(), '5', '>=') && is_object($first)) { return ($first === $second); } if (is_object($first) && is_object($second)) { @@ -133,9 +135,6 @@ * @static */ function isA($object, $class) { - if (function_exists('is_a')) { - return is_a($object, $class); - } if (version_compare(phpversion(), '5') >= 0) { if (! class_exists($class, false)) { if (function_exists('interface_exists')) { @@ -147,6 +146,9 @@ 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))); } @@ -167,18 +169,5 @@ set_socket_timeout($handle, $timeout, 0); } } - - /** - * Gets the current stack trace topmost first. - * @return array List of stack frames. - * @access public - * @static - */ - function getStackTrace() { - if (function_exists('debug_backtrace')) { - return array_reverse(debug_backtrace()); - } - return array(); - } } -?> +?> \ No newline at end of file diff --git a/lib/simpletestlib/dumper.php b/lib/simpletestlib/dumper.php index 63af5ea86c..ea21e6455d 100644 --- a/lib/simpletestlib/dumper.php +++ b/lib/simpletestlib/dumper.php @@ -356,47 +356,5 @@ ob_end_clean(); return $formatted; } - - /** - * Extracts the last assertion that was not within - * Simpletest itself. The name must start with "assert". - * @param array $stack List of stack frames. - * @access public - * @static - */ - function getFormattedAssertionLine($stack) { - foreach ($stack as $frame) { - if (isset($frame['file'])) { - if (strpos($frame['file'], SIMPLE_TEST) !== false) { - if (dirname($frame['file']) . '/' == SIMPLE_TEST) { - continue; - } - } - } - if (SimpleDumper::_stackFrameIsAnAssertion($frame)) { - return ' at [' . $frame['file'] . ' line ' . $frame['line'] . ']'; - } - } - return ''; - } - - /** - * Tries to determine if the method call is an assertion. - * @param array $frame PHP stack frame. - * @access private - * @static - */ - function _stackFrameIsAnAssertion($frame) { - if (($frame['function'] == 'fail') || ($frame['function'] == 'pass')) { - return true; - } - if (strncmp($frame['function'], 'assert', 6) == 0) { - return true; - } - if (strncmp($frame['function'], 'expect', 6) == 0) { - return true; - } - return false; - } } ?> \ No newline at end of file diff --git a/lib/simpletestlib/encoding.php b/lib/simpletestlib/encoding.php index a1a0d764a4..bffa071319 100644 --- a/lib/simpletestlib/encoding.php +++ b/lib/simpletestlib/encoding.php @@ -37,7 +37,7 @@ * @access public */ function asRequest() { - return $this->_key . '=' . urlencode($this->_value); + return urlencode($this->_key) . '=' . urlencode($this->_value); } /** diff --git a/lib/simpletestlib/errors.php b/lib/simpletestlib/errors.php index 99b50962a0..76745f88a8 100644 --- a/lib/simpletestlib/errors.php +++ b/lib/simpletestlib/errors.php @@ -15,6 +15,8 @@ * 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. @@ -39,13 +41,15 @@ * @access public */ function invoke($method) { - set_error_handler('simpleTestErrorHandler'); + $context = &SimpleTest::getContext(); + $queue = &$context->get('SimpleErrorQueue'); + $queue->setTestCase($this->GetTestCase()); + set_error_handler('SimpleTestErrorHandler'); parent::invoke($method); - $queue = &SimpleErrorQueue::instance(); - while (list($severity, $message, $file, $line, $globals) = $queue->extract()) { + while (list($severity, $message, $file, $line) = $queue->extract()) { $severity = SimpleErrorQueue::getSeverityAsString($severity); - $test_case = &$this->getTestCase(); - $test_case->error($severity, $message, $file, $line); + $test = &$this->getTestCase(); + $test->error($severity, $message, $file, $line); } restore_error_handler(); } @@ -59,28 +63,63 @@ */ class SimpleErrorQueue { var $_queue; + var $_expectation_queue; + var $_test; /** * Starts with an empty queue. - * @access public */ function SimpleErrorQueue() { $this->clear(); } + /** + * Sets the currently running test case. + * @param SimpleTestCase $test Test case to send messages to. + * @access public + */ + function setTestCase(&$test) { + $this->_test = &$test; + } + /** * Adds an error to the front of the queue. - * @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. + * @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, $message, $filename, $line, $super_globals) { - array_push( - $this->_queue, - array($severity, $message, $filename, $line, $super_globals)); + 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)); + } + } + + /** + * 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); + $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); + } } /** @@ -105,32 +144,52 @@ */ function clear() { $this->_queue = array(); + $this->_expectation_queue = array(); } /** - * Tests to see if the queue is empty. - * @return True if empty. + * @deprecated */ - function isEmpty() { - return (count($this->_queue) == 0); + function assertNoErrors($message) { + return $this->_test->assert( + new TrueExpectation(), + count($this->_queue) == 0, + sprintf($message, 'Should be no errors')); } /** - * Global access to a single error queue. - * @return Global error queue object. - * @access public - * @static + * @deprecated */ - function &instance() { - static $queue = false; - if (! $queue) { - $queue = new SimpleErrorQueue(); + function assertError($expected, $message) { + if (count($this->_queue) == 0) { + $this->_test->fail(sprintf($message, 'Expected error not found')); + return false; } - return $queue; + 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]")); + } + + /** + * 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)); } /** - * Converst an error code into it's string + * Converts an error code into it's string * representation. * @param $severity PHP integer error code. * @return String version of error code. @@ -167,16 +226,17 @@ * @static * @access public */ - function simpleTestErrorHandler($severity, $message, $filename, $line, $super_globals) { + 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"); } - $queue = &SimpleErrorQueue::instance(); - $queue->add($severity, $message, $filename, $line, $super_globals); - set_error_handler('simpleTestErrorHandler'); + $context = &SimpleTest::getContext(); + $queue = &$context->get('SimpleErrorQueue'); + $queue->add($severity, $message, $filename, $line); + set_error_handler('SimpleTestErrorHandler'); } } ?> \ No newline at end of file diff --git a/lib/simpletestlib/exceptions.php b/lib/simpletestlib/exceptions.php index d8b14ef932..a2bf0afff0 100644 --- a/lib/simpletestlib/exceptions.php +++ b/lib/simpletestlib/exceptions.php @@ -11,10 +11,11 @@ * for dependent libraries. */ require_once(dirname(__FILE__) . '/invoker.php'); + require_once(dirname(__FILE__) . '/expectation.php'); /** * Extension that traps exceptions and turns them into - * an error message. + * an error message. PHP5 only. * @package SimpleTest * @subpackage UnitTester */ @@ -29,19 +30,162 @@ } /** - * Invokes a test method and dispatches any - * untrapped errors. + * 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. - * @access public */ function invoke($method) { + $trap = SimpleTest::getContext()->get('SimpleExceptionTrap'); + $trap->clear(); try { parent::invoke($method); } catch (Exception $exception) { - $test_case = &$this->getTestCase(); - $test_case->tearDown(); // Added by T.J.Hunt@open.ac.uk. - $test_case->exception($exception); + if (! $trap->isExpected($this->getTestCase(), $exception)) { + $this->getTestCase()->exception($exception); + } + $trap->clear(); + } + if ($message = $trap->getOutstanding()) { + $this->getTestCase()->fail($message); + } + } + } + + /** + * 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; + + /** + * 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); + } + 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 . "]"; + } + 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(); + } + } + + /** + * Stores expected exceptions for when they + * get thrown. Saves the irritating try...catch + * block. + * @package SimpleTest + * @subpackage UnitTester + */ + class SimpleExceptionTrap { + private $expected; + private $message; + + /** + * 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(); + } + 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; + } + + /** + * 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; } } ?> \ No newline at end of file diff --git a/lib/simpletestlib/expectation.php b/lib/simpletestlib/expectation.php index 7cb4cf820b..a4e9de25f7 100644 --- a/lib/simpletestlib/expectation.php +++ b/lib/simpletestlib/expectation.php @@ -30,7 +30,6 @@ * @param string $message Customised message on failure. */ function SimpleExpectation($message = '%s') { - $this->_dumper = &new SimpleDumper(); $this->_message = $message; } @@ -58,12 +57,14 @@ /** * Overlays the generated message onto the stored user * message. An additional message can be interjected. - * @param mixed $compare Comparison value. - * @return string Description of success - * or failure. + * @param mixed $compare Comparison value. + * @param SimpleDumper $dumper For formatting the results. + * @return string Description of success + * or failure. * @access public */ - function overlayMessage($compare) { + function overlayMessage($compare, $dumper) { + $this->_dumper = $dumper; return sprintf($this->_message, $this->testMessage($compare)); } @@ -91,6 +92,96 @@ SimpleTestCompatibility::isA($expectation, 'SimpleExpectation'); } } + + /** + * A wildcard expectation always matches. + * @package SimpleTest + * @subpackage MockObjects + */ + class AnythingExpectation extends SimpleExpectation { + + /** + * Tests the expectation. Always true. + * @param mixed $compare Ignored. + * @return boolean True. + * @access public + */ + function test($compare) { + return true; + } + + /** + * 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) . ']'; + } + } + + /** + * An expectation that passes on boolean true. + * @package SimpleTest + * @subpackage MockObjects + */ + class TrueExpectation extends SimpleExpectation { + + /** + * 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 'Expected true, got [' . $dumper->describeValue($compare) . ']'; + } + } + + /** + * 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; + } + + /** + * 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 equality. @@ -471,8 +562,8 @@ /** * Describes a pattern match including the string * found and it's position. - * @package SimpleTest - * @subpackage UnitTester + * @package SimpleTest + * @subpackage UnitTester * @param string $pattern Regex to match against. * @param string $subject Subject to search. * @access protected @@ -480,7 +571,7 @@ function _describePatternMatch($pattern, $subject) { preg_match($pattern, $subject, $matches); $position = strpos($subject, $matches[0]); - $dumper = &$this->_getDumper(); + $dumper = $this->_getDumper(); return "Pattern [$pattern] detected at character [$position] in [" . $dumper->describeValue($subject) . "] as [" . $matches[0] . "] in region [" . @@ -717,4 +808,4 @@ "] should contain method [$method]"; } } -?> +?> \ No newline at end of file diff --git a/lib/simpletestlib/extensions/pear_test_case.php b/lib/simpletestlib/extensions/pear_test_case.php index 7c91fb0043..4f9f4ad3ee 100644 --- a/lib/simpletestlib/extensions/pear_test_case.php +++ b/lib/simpletestlib/extensions/pear_test_case.php @@ -58,7 +58,7 @@ * @public */ function assertNotNull($value, $message = "%s") { - parent::assertTrue(isset($value), $message); + parent::assert(new TrueExpectation(), isset($value), $message); } /** @@ -68,7 +68,7 @@ * @public */ function assertNull($value, $message = "%s") { - parent::assertTrue(!isset($value), $message); + parent::assert(new TrueExpectation(), !isset($value), $message); } /** @@ -86,7 +86,8 @@ "[" . $dumper->describeValue($first) . "] and [" . $dumper->describeValue($second) . "] should reference the same object"); - return $this->assertTrue( + return $this->assert( + new TrueExpectation(), SimpleTestCompatibility::isReference($first, $second), $message); } @@ -106,7 +107,8 @@ "[" . $dumper->describeValue($first) . "] and [" . $dumper->describeValue($second) . "] should not be the same object"); - return $this->assertFalse( + return $this->assert( + new falseExpectation(), SimpleTestCompatibility::isReference($first, $second), $message); } @@ -119,7 +121,7 @@ * @public */ function assertTrue($condition, $message = "%s") { - parent::assertTrue($condition, $message); + parent::assert(new TrueExpectation(), $condition, $message); } /** @@ -130,7 +132,7 @@ * @public */ function assertFalse($condition, $message = "%s") { - parent::assertTrue(!$condition, $message); + parent::assert(new FalseExpectation(), $condition, $message); } /** @@ -152,7 +154,7 @@ * @public */ function assertType($value, $type, $message = "%s") { - parent::assertTrue(gettype($value) == strtolower($type), $message); + parent::assert(new TrueExpectation(), gettype($value) == strtolower($type), $message); } /** diff --git a/lib/simpletestlib/extensions/phpunit_test_case.php b/lib/simpletestlib/extensions/phpunit_test_case.php index 7646b9c35c..1628ed7d23 100644 --- a/lib/simpletestlib/extensions/phpunit_test_case.php +++ b/lib/simpletestlib/extensions/phpunit_test_case.php @@ -38,7 +38,7 @@ * @public */ function assert($condition, $message = false) { - parent::assertTrue($condition, $message); + parent::assert(new TrueExpectation(), $condition, $message); } /** diff --git a/lib/simpletestlib/mock_objects.php b/lib/simpletestlib/mock_objects.php index ccb16df678..aefdd31e6d 100644 --- a/lib/simpletestlib/mock_objects.php +++ b/lib/simpletestlib/mock_objects.php @@ -26,36 +26,6 @@ define('MOCK_ANYTHING', '*'); } - /** - * A wildcard expectation always matches. - * @package SimpleTest - * @subpackage MockObjects - */ - class AnythingExpectation extends SimpleExpectation { - - /** - * Tests the expectation. Always true. - * @param mixed $compare Ignored. - * @return boolean True. - * @access public - */ - function test($compare) { - return true; - } - - /** - * 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) . ']'; - } - } - /** * Parameter comparison assertion. * @package SimpleTest @@ -70,8 +40,6 @@ * those that are wildcarded. * If the value is not an array * then it is considered to match any. - * @param mixed $wildcard Any parameter matching this - * will always match. * @param string $message Customised message on failure. * @access public */ @@ -151,7 +119,7 @@ $comparison = $this->_coerceToExpectation($expected[$i]); if (! $comparison->test($parameters[$i])) { $messages[] = "parameter " . ($i + 1) . " with [" . - $comparison->overlayMessage($parameters[$i]) . "]"; + $comparison->overlayMessage($parameters[$i], $this->_getDumper()) . "]"; } } return "Parameter expectation differs at " . implode(" and ", $messages); @@ -473,7 +441,8 @@ * @access protected */ function &_getCurrentTestCase() { - return SimpleTest::getCurrent(); + $context = &SimpleTest::getContext(); + return $context->getTest(); } /** @@ -809,20 +778,17 @@ * 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 $method Current method name. + * @param string $test_method Current method name. + * @param SimpleTestCase $test Test to send message to. * @access public */ - function atTestEnd($method) { + function atTestEnd($test_method, &$test) { foreach ($this->_expected_counts as $method => $expectation) { - $this->_assertTrue( - $expectation->test($this->getCallCount($method)), - $expectation->overlayMessage($this->getCallCount($method))); + $test->assert($expectation, $this->getCallCount($method)); } foreach ($this->_max_counts as $method => $expectation) { if ($expectation->test($this->getCallCount($method))) { - $this->_assertTrue( - true, - $expectation->overlayMessage($this->getCallCount($method))); + $test->assert($expectation, $this->getCallCount($method)); } } } @@ -880,39 +846,24 @@ * @access private */ function _checkExpectations($method, $args, $timing) { + $test = &$this->_getCurrentTestCase(); if (isset($this->_max_counts[$method])) { if (! $this->_max_counts[$method]->test($timing + 1)) { - $this->_assertTrue( - false, - $this->_max_counts[$method]->overlayMessage($timing + 1)); + $test->assert($this->_max_counts[$method], $timing + 1); } } if (isset($this->_expected_args_at[$timing][$method])) { - $this->_assertTrue( - $this->_expected_args_at[$timing][$method]->test($args), - "Mock method [$method] at [$timing] -> " . - $this->_expected_args_at[$timing][$method]->overlayMessage($args)); + $test->assert( + $this->_expected_args_at[$timing][$method], + $args, + "Mock method [$method] at [$timing] -> %s"); } elseif (isset($this->_expected_args[$method])) { - $this->_assertTrue( - $this->_expected_args[$method]->test($args), - "Mock method [$method] -> " . $this->_expected_args[$method]->overlayMessage($args)); + $test->assert( + $this->_expected_args[$method], + $args, + "Mock method [$method] -> %s"); } } - - /** - * Triggers an assertion on the held test case. - * Should be overridden when using another test - * framework other than the SimpleTest one if the - * assertion method has a different name. - * @param boolean $assertion True will pass. - * @param string $message Message that will go with - * the test event. - * @access protected - */ - function _assertTrue($assertion, $message) { - $test = &$this->_getCurrentTestCase(); - $test->assertTrue($assertion, $message); - } } /** @@ -928,7 +879,7 @@ * @access public */ function Mock() { - trigger_error('Mock factory methods are class only.'); + trigger_error('Mock factory methods are static.'); } /** @@ -970,19 +921,12 @@ /** * Uses a stack trace to find the line of an assertion. - * @param array $stack Stack frames top most first. Only - * needed if not using the PHP - * backtrace function. - * @return string Location of first expect* - * method embedded in format string. * @access public * @static */ - function getExpectationLine($stack = false) { - if ($stack === false) { - $stack = SimpleTestCompatibility::getStackTrace(); - } - return SimpleDumper::getFormattedAssertionLine($stack); + function getExpectationLine() { + $trace = new SimpleStackTrace(array('expect')); + return $trace->traceMethod(); } } @@ -1051,7 +995,7 @@ } $mock_reflection = new SimpleReflection($this->_mock_class); if ($mock_reflection->classExistsSansAutoload()) { - trigger_error("Partial mock class [$mock_class] already exists"); + trigger_error('Partial mock class [' . $this->_mock_class . '] already exists'); return false; } return eval($this->_extendClassCode($methods)); diff --git a/lib/simpletestlib/parser.php b/lib/simpletestlib/parser.php index 97db5ca1c4..06977f8c21 100644 --- a/lib/simpletestlib/parser.php +++ b/lib/simpletestlib/parser.php @@ -645,13 +645,15 @@ * @access public */ function acceptAttributeToken($token, $event) { - 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)); + 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 true; } diff --git a/lib/simpletestlib/reflection_php5.php b/lib/simpletestlib/reflection_php5.php index b816ef34fa..7e023f37d0 100644 --- a/lib/simpletestlib/reflection_php5.php +++ b/lib/simpletestlib/reflection_php5.php @@ -8,8 +8,8 @@ /** * Version specific reflection API. - * @package SimpleTest - * @subpackage UnitTester + * @package SimpleTest + * @subpackage UnitTester */ class SimpleReflection { var $_interface; @@ -193,12 +193,17 @@ * @access public */ function getSignature($name) { - if ($name == '__get') { - return 'function __get($key)'; - } 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()"; } @@ -217,31 +222,31 @@ * @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)) . - ")"; + $interface = new ReflectionClass($this->_interface); + $method = $interface->getMethod($name); + $reference = $method->returnsReference() ? '&' : ''; + return "function $reference$name(" . + implode(', ', $this->_getParameterSignatures($method)) . + ")"; } /** * Gets the source code for each parameter. * @param ReflectionMethod $method Method object from - * reflection API + * reflection API * @return array List of strings, each * a snippet of code. * @access private */ function _getParameterSignatures($method) { - $signatures = array(); + $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' : ''); + (! is_null($type) ? $type->getName() . ' ' : '') . + ($parameter->isPassedByReference() ? '&' : '') . + '$' . $this->_suppressSpurious($parameter->getName()) . + ($this->_isOptional($parameter) ? ' = null' : ''); } return $signatures; } diff --git a/lib/simpletestlib/remote.php b/lib/simpletestlib/remote.php index cb6b9f2d75..c7e02b124e 100644 --- a/lib/simpletestlib/remote.php +++ b/lib/simpletestlib/remote.php @@ -75,7 +75,8 @@ * @access protected */ function &_createBrowser() { - return new SimpleBrowser(); + $browser = &new SimpleBrowser(); + return $browser; } /** @@ -85,7 +86,8 @@ * @access protected */ function &_createParser(&$reporter) { - return new SimpleTestXmlParser($reporter); + $parser = &new SimpleTestXmlParser($reporter); + return $parser; } /** diff --git a/lib/simpletestlib/reporter.php b/lib/simpletestlib/reporter.php index 3be2a1572b..492faab9af 100644 --- a/lib/simpletestlib/reporter.php +++ b/lib/simpletestlib/reporter.php @@ -40,6 +40,7 @@ */ function paintHeader($test_name) { $this->sendNoCacheHeaders(); + print ""; print "\n\n$test_name\n"; print "\n"; @@ -74,7 +75,9 @@ * @access protected */ function _getCss() { - return ".fail { color: red; } pre { background-color: lightgray; }"; + return ".fail { background-color: inherit; color: red; }" . + ".pass { background-color: inherit; color: green; }" . + " pre { background-color: lightgray; color: inherit; }"; } /** @@ -115,10 +118,9 @@ } /** - * Paints a PHP error or exception. + * Paints a PHP error. * @param string $message Message is ignored. * @access public - * @abstract */ function paintError($message) { parent::paintError($message); @@ -129,6 +131,38 @@ print " -> " . $this->_htmlEntities($message) . "
\n"; } + /** + * Paints a PHP exception. + * @param Exception $exception Exception to display. + * @access public + */ + function paintException($exception) { + parent::paintException($exception); + print "Exception: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + $message = 'Unexpected exception of type [' . get_class($exception) . + '] with message ['. $exception->getMessage() . + '] in ['. $exception->getFile() . + ' line ' . $exception->getLine() . ']'; + print " -> " . $this->_htmlEntities($message) . "
\n"; + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + parent::paintSkip($message); + print "Skipped: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . $this->_htmlEntities($message) . "
\n"; + } + /** * Paints formatted text such as dumped variables. * @param string $message Text to show. @@ -218,7 +252,7 @@ /** * Paints a PHP error or exception. - * @param string $message Message is ignored. + * @param string $message Message to be shown. * @access public * @abstract */ @@ -227,6 +261,31 @@ print "Exception " . $this->getExceptionCount() . "!\n$message\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 formatted text such as dumped variables. * @param string $message Text to show. diff --git a/lib/simpletestlib/scorer.php b/lib/simpletestlib/scorer.php index 62d232d652..279d714dbf 100644 --- a/lib/simpletestlib/scorer.php +++ b/lib/simpletestlib/scorer.php @@ -149,8 +149,7 @@ } /** - * Deals with PHP 4 throwing an error or PHP 5 - * throwing an exception. + * Deals with PHP 4 throwing an error. * @param string $message Text of error formatted by * the test case. * @access public @@ -159,6 +158,23 @@ $this->_exceptions++; } + /** + * 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. @@ -237,6 +253,16 @@ $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(); + } /** * Paints the start of a group test. Will also paint @@ -393,7 +419,7 @@ var $_reporter; /** - * Mediates between teh reporter and the test case. + * Mediates between the reporter and the test case. * @param SimpleScorer $reporter Reporter to receive events. */ function SimpleReporterDecorator(&$reporter) { @@ -443,6 +469,16 @@ 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. @@ -527,6 +563,24 @@ $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 string $message Text to display. @@ -635,6 +689,16 @@ } 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. @@ -736,6 +800,28 @@ $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); + } + } + + /** + * 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. diff --git a/lib/simpletestlib/shell_tester.php b/lib/simpletestlib/shell_tester.php index 1e513ef184..d47d2d6dce 100644 --- a/lib/simpletestlib/shell_tester.php +++ b/lib/simpletestlib/shell_tester.php @@ -63,7 +63,7 @@ /** * Test case for testing of command line scripts and - * utilities. Usually scripts taht are external to the + * utilities. Usually scripts that are external to the * PHP code, but support it in some way. * @package SimpleTest * @subpackage UnitTester @@ -127,6 +127,33 @@ $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); + } + + /** + * 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 @@ -303,4 +330,4 @@ return $shell; } } -?> \ No newline at end of file +?> diff --git a/lib/simpletestlib/simpletest.php b/lib/simpletestlib/simpletest.php index 0a1693a65c..6464bdc36b 100644 --- a/lib/simpletestlib/simpletest.php +++ b/lib/simpletestlib/simpletest.php @@ -17,10 +17,8 @@ /**#@-*/ /** - * Static global directives and options. I hate this - * class. It's a mixture of reference hacks, configuration - * and previous design screw-ups that I have to maintain - * to keep backward compatibility. + * Registry and test context. Includes a few + * global options that I'm slowly getting rid of. * @package SimpleTest */ class SimpleTest { @@ -59,8 +57,8 @@ * 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 nice to havethe ignore() - * calls at the top of teh file. + * 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 @@ -156,30 +154,6 @@ return $registry['DefaultProxyPassword']; } - /** - * 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 - * @static - */ - function setCurrent(&$test) { - $registry = &SimpleTest::_getRegistry(); - $registry['CurrentTestCase'] = &$test; - } - - /** - * Accessor for current test instance. - * @return SimpleTEstCase Currently running test. - * @access public - * @static - */ - function &getCurrent() { - $registry = &SimpleTest::_getRegistry(); - return $registry['CurrentTestCase']; - } - /** * Accessor for global registry of options. * @return hash All stored values. @@ -194,6 +168,21 @@ return $registry; } + /** + * 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; + } + /** * Constant default values. * @return hash All registry defaults. @@ -211,6 +200,168 @@ } } + /** + * 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(); + } + + /** + * 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; + } + + /** + * 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 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; + + /** + * Stashes the list of target prefixes. + * @param array $prefixes List of method prefixes + * to search for. + */ + 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'] . ']'; + } + } + 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; + } + } + } + return false; + } + + /** + * 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 false; + } + + /** + * Grabs a current stack trace. + * @return array Fulle trace. + * @access private + */ + function _captureTrace() { + if (function_exists('debug_backtrace')) { + return array_reverse(debug_backtrace()); + } + return array(); + } + } + /** * @deprecated */ @@ -279,4 +430,4 @@ return Simpletest::getDefaultProxyPassword(); } } -?> \ No newline at end of file +?> diff --git a/lib/simpletestlib/test_case.php b/lib/simpletestlib/test_case.php index c2f56f009e..62d03493b4 100644 --- a/lib/simpletestlib/test_case.php +++ b/lib/simpletestlib/test_case.php @@ -24,10 +24,8 @@ require_once(dirname(__FILE__) . '/reflection_php4.php'); } if (! defined('SIMPLE_TEST')) { - /** - * @ignore - */ - define('SIMPLE_TEST', dirname(__FILE__) . '/'); + /** @ignore */ + define('SIMPLE_TEST', dirname(__FILE__) . DIRECTORY_SEPARATOR); } /**#@-*/ @@ -43,6 +41,7 @@ var $_label = false; var $_reporter; var $_observers; + var $_should_skip = false; /** * Sets up the test with no display. @@ -65,6 +64,41 @@ 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()); + } + } + + /** + * 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. @@ -83,21 +117,27 @@ * 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) { - SimpleTest::setCurrent($this); + $context = &SimpleTest::getContext(); + $context->setTest($this); + $context->setReporter($reporter); $this->_reporter = &$reporter; - $this->_reporter->paintCaseStart($this->getLabel()); - foreach ($this->getTests() as $method) { - if ($this->_reporter->shouldInvoke($this->getLabel(), $method)) { - $invoker = &$this->_reporter->createInvoker($this->createInvoker()); - $invoker->before($method); - $invoker->invoke($method); - $invoker->after($method); + $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); + } } } - $this->_reporter->paintCaseEnd($this->getLabel()); + $reporter->paintCaseEnd($this->getLabel()); unset($this->_reporter); return $reporter->getStatus(); } @@ -169,7 +209,7 @@ */ function after($method) { for ($i = 0; $i < count($this->_observers); $i++) { - $this->_observers[$i]->atTestEnd($method); + $this->_observers[$i]->atTestEnd($method, $this); } $this->_reporter->paintMethodEnd($method); } @@ -185,9 +225,7 @@ } /** - * Sends a pass event with a message. - * @param string $message Message to send. - * @access public + * @deprecated */ function pass($message = "Pass") { if (! isset($this->_reporter)) { @@ -226,7 +264,7 @@ trigger_error('Can only make assertions within test methods'); } $this->_reporter->paintError( - "Unexpected PHP error [$message] severity [$severity] in [$file] line [$line]"); + "Unexpected PHP error [$message] severity [$severity] in [$file line $line]"); } /** @@ -236,21 +274,11 @@ * @access public */ function exception($exception) { - $this->_reporter->paintError( - 'Unexpected exception of type [' . get_class($exception) . - '] with message ['. $exception->getMessage() . - '] in ['. $exception->getFile() . - '] line [' . $exception->getLine() . ']'); + $this->_reporter->paintException($exception); } /** - * Sends a user defined event to the test reporter. - * This is for small scale extension where - * both the test case and either the reporter or - * display are subclassed. - * @param string $type Type of event. - * @param mixed $payload Object or message to deliver. - * @access public + * @deprecated */ function signal($type, &$payload) { if (! isset($this->_reporter)) { @@ -259,15 +287,6 @@ $this->_reporter->paintSignal($type, $payload); } - /** - * Cancels any outstanding errors. - * @access public - */ - function swallowErrors() { - $queue = &SimpleErrorQueue::instance(); - $queue->clear(); - } - /** * Runs an expectation directly, for extending the * tests with new expectation classes. @@ -278,9 +297,15 @@ * @access public */ function assert(&$expectation, $compare, $message = '%s') { - return $this->assertTrue( - $expectation->test($compare), - sprintf($message, $expectation->overlayMessage($compare))); + 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()))); + } } /** @@ -290,58 +315,15 @@ return $this->assert($expectation, $compare, $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) { - if (! $message) { - $message = 'True assertion got ' . ($result ? 'True' : 'False'); - } - if ($result) { - return $this->pass($message); - } else { - return $this->fail($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 = false) { - if (! $message) { - $message = 'False assertion got ' . ($result ? 'True' : 'False'); - } - return $this->assertTrue(! $result, $message); - } - /** * Uses a stack trace to find the line of an assertion. - * @param string $format String formatting. - * @param array $stack Stack frames top most first. Only - * needed if not using the PHP - * backtrace function. * @return string Line number of first assert* * method embedded in format string. * @access public */ - function getAssertionLine($stack = false) { - if ($stack === false) { - $stack = SimpleTestCompatibility::getStackTrace(); - } - return SimpleDumper::getFormattedAssertionLine($stack); + function getAssertionLine() { + $trace = new SimpleStackTrace(array('assert', 'expect', 'pass', 'fail', 'skip')); + return $trace->traceMethod(); } /** @@ -354,7 +336,8 @@ * @access public */ function dump($variable, $message = false) { - $formatted = SimpleDumper::dump($variable); + $dumper = $this->_reporter->getDumper(); + $formatted = $dumper->dump($variable); if ($message) { $formatted = $message . "\n" . $formatted; } @@ -363,10 +346,7 @@ } /** - * Dispatches a text message straight to the - * test suite. Useful for status bar displays. - * @param string $message Message to show. - * @access public + * @deprecated */ function sendMessage($message) { $this->_reporter->PaintMessage($message); @@ -390,7 +370,7 @@ * @package SimpleTest * @subpackage UnitTester */ - class GroupTest { + class TestSuite { var $_label; var $_test_cases; var $_old_track_errors; @@ -402,7 +382,7 @@ * of the test. * @access public */ - function GroupTest($label = false) { + function TestSuite($label = false) { $this->_label = $label ? $label : get_class($this); $this->_test_cases = array(); $this->_old_track_errors = ini_get('track_errors'); @@ -440,7 +420,7 @@ * @access public */ function addTestClass($class) { - if ($this->_getBaseTestCase($class) == 'grouptest') { + if ($this->_getBaseTestCase($class) == 'testsuite' || $this->_getBaseTestCase($class) == 'grouptest') { $this->_test_cases[] = &new $class(); } else { $this->_test_cases[] = $class; @@ -457,12 +437,12 @@ function addTestFile($test_file) { $existing_classes = get_declared_classes(); if ($error = $this->_requireWithError($test_file)) { - $this->addTestCase(new BadGroupTest($test_file, $error)); + $this->addTestCase(new BadTestSuite($test_file, $error)); return; } $classes = $this->_selectRunnableTests($existing_classes, get_declared_classes()); if (count($classes) == 0) { - $this->addTestCase(new BadGroupTest($test_file, "No runnable test cases in [$test_file]")); + $this->addTestCase(new BadTestSuite($test_file, "No runnable test cases in [$test_file]")); return; } $group = &$this->_createGroupFromClasses($test_file, $classes); @@ -482,11 +462,14 @@ $error = isset($php_errormsg) ? $php_errormsg : false; $this->_disableErrorReporting(); $self_inflicted_errors = array( - 'Assigning the return value of new by reference is deprecated', - 'var: Deprecated. Please use the public/private/protected modifiers'); - if (in_array($error, $self_inflicted_errors)) { - return false; - } + '/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; } @@ -548,13 +531,13 @@ * Builds a group test from a class list. * @param string $title Title of new group. * @param array $classes Test classes. - * @return GroupTest Group loaded with the new + * @return TestSuite Group loaded with the new * test cases. * @access private */ function &_createGroupFromClasses($title, $classes) { SimpleTest::ignoreParentsIfIgnored($classes); - $group = &new GroupTest($title); + $group = &new TestSuite($title); foreach ($classes as $class) { if (! SimpleTest::isIgnored($class)) { $group->addTestClass($class); @@ -572,7 +555,7 @@ function _getBaseTestCase($class) { while ($class = get_parent_class($class)) { $class = strtolower($class); - if ($class == "simpletestcase" || $class == "grouptest") { + if ($class == 'simpletestcase' || $class == 'testsuite' || $class == 'grouptest') { return $class; } } @@ -603,6 +586,7 @@ $class = $this->_test_cases[$i]; $test = &new $class(); $test->run($reporter); + unset($test); } else { $this->_test_cases[$i]->run($reporter); } @@ -628,6 +612,11 @@ return $count; } } + + /** + * @deprecated + */ + class GroupTest extends TestSuite { } /** * This is a failing group test for when a test suite hasn't @@ -635,7 +624,7 @@ * @package SimpleTest * @subpackage UnitTester */ - class BadGroupTest { + class BadTestSuite { var $_label; var $_error; @@ -645,7 +634,7 @@ * of the test. * @access public */ - function BadGroupTest($label, $error) { + function BadTestSuite($label, $error) { $this->_label = $label; $this->_error = $error; } @@ -666,7 +655,7 @@ */ function run(&$reporter) { $reporter->paintGroupStart($this->getLabel(), $this->getSize()); - $reporter->paintFail('Bad GroupTest [' . $this->getLabel() . + $reporter->paintFail('Bad TestSuite [' . $this->getLabel() . '] with error [' . $this->_error . ']'); $reporter->paintGroupEnd($this->getLabel()); return $reporter->getStatus(); @@ -681,4 +670,9 @@ return 0; } } -?> + + /** + * @deprecated + */ + class BadGroupTest extends BadTestSuite { } +?> \ No newline at end of file diff --git a/lib/simpletestlib/unit_tester.php b/lib/simpletestlib/unit_tester.php index bbfe02bb18..6efaf60d55 100644 --- a/lib/simpletestlib/unit_tester.php +++ b/lib/simpletestlib/unit_tester.php @@ -36,6 +36,33 @@ $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); + } + + /** + * 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. @@ -43,11 +70,11 @@ * @return boolean True on pass * @access public */ - function assertNull($value, $message = "%s") { + function assertNull($value, $message = '%s') { $dumper = &new SimpleDumper(); $message = sprintf( $message, - "[" . $dumper->describeValue($value) . "] should be null"); + '[' . $dumper->describeValue($value) . '] should be null'); return $this->assertTrue(! isset($value), $message); } @@ -58,11 +85,11 @@ * @return boolean True on pass. * @access public */ - function assertNotNull($value, $message = "%s") { + function assertNotNull($value, $message = '%s') { $dumper = &new SimpleDumper(); $message = sprintf( $message, - "[" . $dumper->describeValue($value) . "] should not be null"); + '[' . $dumper->describeValue($value) . '] should not be null'); return $this->assertTrue(isset($value), $message); } @@ -76,7 +103,7 @@ * @return boolean True on pass. * @access public */ - function assertIsA($object, $type, $message = "%s") { + function assertIsA($object, $type, $message = '%s') { return $this->assert( new IsAExpectation($type), $object, @@ -93,7 +120,7 @@ * @return boolean True on pass. * @access public */ - function assertNotA($object, $type, $message = "%s") { + function assertNotA($object, $type, $message = '%s') { return $this->assert( new NotAExpectation($type), $object, @@ -109,7 +136,7 @@ * @return boolean True on pass * @access public */ - function assertEqual($first, $second, $message = "%s") { + function assertEqual($first, $second, $message = '%s') { return $this->assert( new EqualExpectation($first), $second, @@ -125,7 +152,7 @@ * @return boolean True on pass * @access public */ - function assertNotEqual($first, $second, $message = "%s") { + function assertNotEqual($first, $second, $message = '%s') { return $this->assert( new NotEqualExpectation($first), $second, @@ -142,7 +169,7 @@ * @return boolean True on pass * @access public */ - function assertWithinMargin($first, $second, $margin, $message = "%s") { + function assertWithinMargin($first, $second, $margin, $message = '%s') { return $this->assert( new WithinMarginExpectation($first, $margin), $second, @@ -159,7 +186,7 @@ * @return boolean True on pass * @access public */ - function assertOutsideMargin($first, $second, $margin, $message = "%s") { + function assertOutsideMargin($first, $second, $margin, $message = '%s') { return $this->assert( new OutsideMarginExpectation($first, $margin), $second, @@ -175,7 +202,7 @@ * @return boolean True on pass * @access public */ - function assertIdentical($first, $second, $message = "%s") { + function assertIdentical($first, $second, $message = '%s') { return $this->assert( new IdenticalExpectation($first), $second, @@ -191,7 +218,7 @@ * @return boolean True on pass * @access public */ - function assertNotIdentical($first, $second, $message = "%s") { + function assertNotIdentical($first, $second, $message = '%s') { return $this->assert( new NotIdenticalExpectation($first), $second, @@ -207,13 +234,13 @@ * @return boolean True on pass * @access public */ - function assertReference(&$first, &$second, $message = "%s") { + function assertReference(&$first, &$second, $message = '%s') { $dumper = &new SimpleDumper(); $message = sprintf( $message, - "[" . $dumper->describeValue($first) . - "] and [" . $dumper->describeValue($second) . - "] should reference the same object"); + '[' . $dumper->describeValue($first) . + '] and [' . $dumper->describeValue($second) . + '] should reference the same object'); return $this->assertTrue( SimpleTestCompatibility::isReference($first, $second), $message); @@ -229,13 +256,13 @@ * @return boolean True on pass * @access public */ - function assertClone(&$first, &$second, $message = "%s") { + 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"); + '[' . $dumper->describeValue($first) . + '] and [' . $dumper->describeValue($second) . + '] should not be the same object'); $identical = &new IdenticalExpectation($first); return $this->assertTrue( $identical->test($second) && @@ -268,7 +295,7 @@ * @return boolean True on pass * @access public */ - function assertPattern($pattern, $subject, $message = "%s") { + function assertPattern($pattern, $subject, $message = '%s') { return $this->assert( new PatternExpectation($pattern), $subject, @@ -278,7 +305,7 @@ /** * @deprecated */ - function assertWantedPattern($pattern, $subject, $message = "%s") { + function assertWantedPattern($pattern, $subject, $message = '%s') { return $this->assertPattern($pattern, $subject, $message); } @@ -292,7 +319,7 @@ * @return boolean True on pass * @access public */ - function assertNoPattern($pattern, $subject, $message = "%s") { + function assertNoPattern($pattern, $subject, $message = '%s') { return $this->assert( new NoPatternExpectation($pattern), $subject, @@ -302,50 +329,63 @@ /** * @deprecated */ - function assertNoUnwantedPattern($pattern, $subject, $message = "%s") { + function assertNoUnwantedPattern($pattern, $subject, $message = '%s') { return $this->assertNoPattern($pattern, $subject, $message); } /** - * Confirms that no errors have occoured so - * far in the test method. - * @param string $message Message to display. - * @return boolean True on pass + * @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 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 assertNoErrors($message = "%s") { - $queue = &SimpleErrorQueue::instance(); - return $this->assertTrue( - $queue->isEmpty(), - sprintf($message, "Should be no errors")); + function expectError($expected = false, $message = '%s') { + $context = &SimpleTest::getContext(); + $queue = &$context->get('SimpleErrorQueue'); + $queue->expectError($this->_coerceExpectation($expected), $message); } /** - * Confirms that an error has occoured and - * optionally that the error text matches exactly. - * @param string $expected Expected error text or - * false for no check. - * @param string $message Message to display. - * @return boolean True on pass + * 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 assertError($expected = false, $message = "%s") { - $queue = &SimpleErrorQueue::instance(); - if ($queue->isEmpty()) { - $this->fail(sprintf($message, "Expected error not found")); - return; - } - list($severity, $content, $file, $line, $globals) = $queue->extract(); - $severity = SimpleErrorQueue::getSeverityAsString($severity); - if (! $expected) { - return $this->pass( - "Captured a PHP error of [$content] severity [$severity] in [$file] line [$line] -> %s"); - } - $expected = $this->_coerceToExpectation($expected); - return $this->assert( - $expected, - $content, - "Expected PHP error [$content] severity [$severity] in [$file] line [$line] -> %s"); + function expectException($expected = false, $message = '%s') { + $context = &SimpleTest::getContext(); + $queue = &$context->get('SimpleExceptionTrap'); + $queue->expectException($expected, $message . $this->getAssertionLine()); } /** @@ -356,18 +396,24 @@ * @return SimpleExpectation Expectation object. * @access private */ - function _coerceToExpectation($expected) { + 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); } /** * @deprecated */ - function assertErrorPattern($pattern, $message = "%s") { + function assertErrorPattern($pattern, $message = '%s') { return $this->assertError(new PatternExpectation($pattern), $message); } } -?> +?> \ No newline at end of file diff --git a/lib/simpletestlib/url.php b/lib/simpletestlib/url.php index 9a68156eea..b66b31f43c 100644 --- a/lib/simpletestlib/url.php +++ b/lib/simpletestlib/url.php @@ -507,8 +507,8 @@ * @access public */ function normalisePath($path) { - $path = preg_replace('|/[^/]+/\.\./|', '/', $path); - return preg_replace('|/\./|', '/', $path); + $path = preg_replace('|/\./|', '/', $path); + return preg_replace('|/[^/]+/\.\./|', '/', $path); } /** diff --git a/lib/simpletestlib/web_tester.php b/lib/simpletestlib/web_tester.php index c98507deef..be424bb175 100644 --- a/lib/simpletestlib/web_tester.php +++ b/lib/simpletestlib/web_tester.php @@ -120,8 +120,8 @@ } else { return "Field expectation [" . $dumper->describeValue($this->_value) . "] fails with [" . - $this->_dumper->describeValue($compare) . "] " . - $this->_dumper->describeDifference($this->_value, $compare); + $dumper->describeValue($compare) . "] " . + $dumper->describeDifference($this->_value, $compare); } } } @@ -242,7 +242,7 @@ */ function testMessage($compare) { if (SimpleExpectation::isExpectation($this->_expected_value)) { - $message = $this->_expected_value->testMessage($compare); + $message = $this->_expected_value->overlayMessage($compare, $this->_getDumper()); } else { $message = $this->_expected_header . ($this->_expected_value ? ': ' . $this->_expected_value : ''); @@ -784,7 +784,7 @@ * @param string $expiry Expiry date. * @access public */ - function setCookie($name, $value, $host = false, $path = "/", $expiry = false) { + function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { $this->_browser->setCookie($name, $value, $host, $path, $expiry); } @@ -840,6 +840,18 @@ 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. @@ -880,6 +892,18 @@ $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 @@ -933,6 +957,18 @@ $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 @@ -968,53 +1004,25 @@ return $this->_failOnError($this->_browser->clickLinkById($id)); } - /** - * 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 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, $message = "%s") { - return $this->assertTrue( - $this->_browser->isLink($label), - sprintf($message, "Link [$label] should exist")); + 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")); } /** @@ -1027,24 +1035,30 @@ * @return boolean True if link missing. * @access public */ - function assertNoLink($label, $message = "%s") { - return $this->assertFalse( - $this->_browser->isLink($label), + 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, $message = "%s") { - return $this->assertTrue( - $this->_browser->isLinkById($id), - sprintf($message, "Link ID [$id] should exist")); + 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")); } /** @@ -1056,9 +1070,9 @@ * @return boolean True if link missing. * @access public */ - function assertNoLinkById($id, $message = "%s") { - return $this->assertFalse( - $this->_browser->isLinkById($id), + function assertNoLinkById($id, $message = '%s') { + return $this->assertTrue( + $this->_browser->getLinkById($id) === false, sprintf($message, "Link ID [$id] should not exist")); } @@ -1313,9 +1327,9 @@ /** * Tests the text between the title tags. - * @param string $title Expected title. - * @param string $message Message to display. - * @return boolean True if pass. + * @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') { @@ -1451,5 +1465,77 @@ $this->getCookie($name) === false, sprintf($message, "Not 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); + } + + /** + * 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 diff --git a/lib/simpletestlib/xml.php b/lib/simpletestlib/xml.php index c6158cf5ef..0de9a5ebe8 100644 --- a/lib/simpletestlib/xml.php +++ b/lib/simpletestlib/xml.php @@ -23,7 +23,9 @@ var $_namespace; /** - * Does nothing yet. + * 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 = ' ') { @@ -140,8 +142,8 @@ } /** - * Increments the pass count. - * @param string $message Message is ignored. + * Paints pass as XML. + * @param string $message Message to encode. * @access public */ function paintPass($message) { @@ -153,8 +155,8 @@ } /** - * Increments the fail count. - * @param string $message Message is ignored. + * Paints failure as XML. + * @param string $message Message to encode. * @access public */ function paintFail($message) { @@ -166,10 +168,9 @@ } /** - * Paints a PHP error or exception. - * @param string $message Message is ignored. + * Paints error as XML. + * @param string $message Message to encode. * @access public - * @abstract */ function paintError($message) { parent::paintError($message); @@ -179,6 +180,36 @@ print "_namespace . "exception>\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 "_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 "_namespace . "skip>\n"; + } + /** * Paints a simple supplementary message. * @param string $message Text to display. @@ -531,7 +562,7 @@ */ function _isLeaf($tag) { return in_array($tag, array( - 'NAME', 'PASS', 'FAIL', 'EXCEPTION', 'MESSAGE', 'FORMATTED', 'SIGNAL')); + 'NAME', 'PASS', 'FAIL', 'EXCEPTION', 'SKIP', 'MESSAGE', 'FORMATTED', 'SIGNAL')); } /** @@ -578,6 +609,8 @@ $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'], -- 2.39.5