Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Reorganized a little. Added debug runner and associated exceptions. C…

…ompletely finished with testFile static method.
  • Loading branch information...
commit 03f9f384c0f172afee021e2495e63850af2e386f 1 parent e3b2dbe
@xdissent authored
View
8 README.rst
@@ -75,14 +75,18 @@ Departures From Python's ``doctest`` Module
Todo
----
-* Fill out DocTest_TestResults class.
-
* Finish Finder class (which will remove phpDoc formatting from tests).
* Add the familiar ``testMod()`` static method (requires completed Finder class).
* Allow less strict formatting of exceptions.
+* Check exception type and message independently, since Python handles both as one
+ "exception message".
+
+* Change ``IGNORE_EXCEPTION_DETAIL`` flag to ``IGNORE_EXCEPTION_MESSAGE`` and/or
+ ``IGNORE_EXCEPTION_TYPE``.
+
* Fully support (and test) multibyte encodings.
* Review indentation handling since PHP shouldn't care.
View
28 src/DocTest/DebugRunner.php
@@ -0,0 +1,28 @@
+<?php
+
+class DocTest_DebugRunner extends DocTest_Runner
+{
+ public function run($test, $out=null, $clear_globs=true)
+ {
+ $r = parent::run($test, $out, false);
+
+ /**
+ * Clear test globals if necessary.
+ */
+ if ($clear_globs) {
+ $test->globs = array();
+ }
+
+ return $r;
+ }
+
+ protected function reportFailure($out, $test, $example, $got)
+ {
+ throw new DocTest_Failure($test, $example, $got);
+ }
+
+ protected function reportUnexpectedException($out, $test, $example, $exception)
+ {
+ throw new DocTest_UnexpectedException($test, $example, $exception);
+ }
+}
View
271 src/DocTest/DocTest.php
@@ -0,0 +1,271 @@
+<?php
+
+class DocTest
+{
+ /**
+ * A static array of option flags values keyed by option name.
+ *
+ * @var array
+ */
+ public static $option_flags_by_name;
+
+ public function __construct($examples, $globs, $name, $filename,
+ $lineno, $docstring
+ ) {
+ $this->examples = $examples;
+ $this->docstring = $docstring;
+ $this->globs = $globs;
+ $this->name = $name;
+ $this->filename = $filename;
+ $this->lineno = $lineno;
+ }
+
+ /**
+ * Returns a string representation of a DocTest.
+ *
+ * The string returned will contain the number of examples as well
+ * as the filename and line number of the DocTest.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ if (!count($this->examples)) {
+ $examples = 'no examples';
+ } elseif (count($this->examples == 1)) {
+ $examples = '1 example';
+ } else {
+ $examples = sprintf('%d examples', count($this->examples));
+ }
+ return sprintf(
+ '<DocTest %s from %s:%s (%s)>',
+ $this->name,
+ $this->filename,
+ $this->lineno,
+ $examples
+ );
+ }
+
+ /* Tests examples in the given file.
+ *
+ * The array returned will contain the number of test failures encountered
+ * at offset 0 and the number of tests found at offset 1.
+ *
+ * @param string $filename The name of the file to test.
+ * @param boolean $module_relative Whether to use a module relative path. By
+ * default, the calling module's path will
+ * be used, or the package parameter if set.
+ * @param string $name The name of the test. The basename of the
+ * filename will be used by default.
+ * @param string $package A package whose path should be used for
+ * the relative module base path.
+ * @param array $globs The global variables needed by the tests.
+ * @param boolean $verbose Whether or not to trigger verbose mode.
+ * @param boolean $report Whether or not to output a full report.
+ * @param integer $optionflags The or'ed combination of flags to use.
+ * @param array $extraglobs Extra globals to use for the tests.
+ * @param boolean $raise_on_errors Whether to raise an exception on error.
+ * @param object $parser A parser to use for the tests.
+ * @param string $encoding The encoding to use to convert the file
+ * to unicode.
+ *
+ * @return array
+ */
+ public static function testFile($filename, $module_relative=true,
+ $name=null, $package=null, $globs=null, $verbose=null, $report=true,
+ $optionflags=0, $extraglobs=null, $raise_on_error=false,
+ $parser=null, $encoding=null)
+ {
+ /**
+ * Check for obvious path error.
+ */
+ if (!is_null($package) && !$module_relative) {
+ throw new UnexpectedValueException(
+ 'Package may only be specified for module-relative paths.'
+ );
+ }
+
+ /**
+ * Initialize parser.
+ */
+ if (is_null($parser)) {
+ $parser = new DocTest_Parser();
+ }
+
+ /**
+ * Relativize the path
+ */
+ list($text, $filename) = self::_loadTestFile($filename, $package, $module_relative);
+
+ /**
+ * If no name was given, then use the file's name.
+ */
+ if (is_null($name)) {
+ $name = basename($filename);
+ }
+
+ /**
+ * Assemble the globals.
+ */
+ if (is_null($globs)) {
+ $globs = array();
+ }
+ if (!is_null($extraglobs)) {
+ $globs = array_merge($globs, $extraglobs);
+ }
+
+ if ($raise_on_error) {
+ $runner = new DocTest_DebugRunner(null, $verbose, $optionflags);
+ } else {
+ $runner = new DocTest_Runner(null, $verbose, $optionflags);
+ }
+
+ /**
+ * Convert encoding
+ *
+ * @todo Make this work.
+ */
+ if (!is_null($encoding) && function_exists('mb_convert_encoding')) {
+ $text = mb_convert_encoding($text, $encoding);
+ }
+
+ /**
+ * Read the file, convert it to a test, and run it.
+ */
+ $test = $parser->getDocTest($text, $globs, $name, $filename, 0);
+ $runner->run($test);
+
+ if ($report) {
+ $runner->summarize();
+ }
+
+ return new DocTest_TestResults($runner->failures, $runner->tries);
+ }
+
+ private static function _loadTestFile($filename, $package, $module_relative)
+ {
+ /**
+ * Determine the absolute file path.
+ */
+ if ($module_relative) {
+
+ /**
+ * Use the calling module's dir as a base path if no package is set.
+ */
+ if (is_null($package)) {
+ /**
+ * Get a backtrace.
+ */
+ $bt = debug_backtrace();
+
+ /**
+ * Find the first file in the trace that's not this one.
+ */
+ foreach ($bt as $trace) {
+ if ($trace['file'] !== __FILE__) {
+ /**
+ * Found calling file. Get the dir and kill the loop.
+ */
+ $package = dirname($trace['file']);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Ensure package ends in a slash.
+ */
+ if (substr($package, -1) !== '/') {
+ $package .= '/';
+ }
+
+ /**
+ * Add the filename to the package dir.
+ */
+ $filename = $package . $filename;
+ }
+
+ /**
+ * Return the file as a string along with the resolved filename.
+ */
+ return array(file_get_contents($filename), $filename);
+ }
+
+ /**
+ * Registers available options.
+ */
+ public static function registerOptions()
+ {
+ /**
+ * Protect against multiple calls.
+ */
+ if (!is_null(self::$option_flags_by_name)) {
+ return;
+ }
+
+ /**
+ * Initialize options flag array.
+ */
+ self::$option_flags_by_name = array();
+
+ /**
+ * The possible base options.
+ */
+ $options = array(
+ 'DONT_ACCEPT_TRUE_FOR_1',
+ 'DONT_ACCEPT_BLANKLINE',
+ 'NORMALIZE_WHITESPACE',
+ 'ELLIPSIS',
+ 'SKIP',
+ 'IGNORE_EXCEPTION_DETAIL',
+ 'REPORT_UDIFF',
+ 'REPORT_NDIFF',
+ 'REPORT_CDIFF',
+ 'REPORT_ONLY_FIRST_FAILURE'
+ );
+
+ /**
+ * Define a global namespaced constant for each option and add it
+ * to the static named options array.
+ */
+ foreach ($options as $i => $option) {
+ $namespaced = 'DOCTEST_' . $option;
+ define($namespaced, 1 << $i);
+ self::$option_flags_by_name[$option] = constant($namespaced);
+ }
+
+ /**
+ * Create comparison flags combination.
+ */
+ $comp_flags = DOCTEST_DONT_ACCEPT_TRUE_FOR_1 |
+ DOCTEST_DONT_ACCEPT_BLANKLINE |
+ DOCTEST_NORMALIZE_WHITESPACE |
+ DOCTEST_ELLIPSIS |
+ DOCTEST_SKIP |
+ DOCTEST_IGNORE_EXCEPTION_DETAIL;
+ define('DOCTEST_COMPARISON_FLAGS', $comp_flags);
+
+ /**
+ * Create reporting flags combination.
+ */
+ $rep_flags = DOCTEST_REPORT_UDIFF |
+ DOCTEST_REPORT_CDIFF |
+ DOCTEST_REPORT_NDIFF |
+ DOCTEST_REPORT_ONLY_FIRST_FAILURE;
+ define('DOCTEST_REPORTING_FLAGS', $rep_flags);
+ }
+
+ /**
+ * Returns a string with non-empty lines prefixed with a number of spaces.
+ *
+ * <note>This method is the global "_indent()" function in Python.</note>
+ *
+ * @param string $s The string to indent.
+ * @param integer $indent The number of spaces to indent.
+ *
+ * @return string
+ */
+ public function indent($s, $indent=4) {
+ return preg_replace('/(?m)^(?!$)/', str_repeat(' ', $indent), $s);
+ }
+}
View
51 src/DocTest/Failure.php
@@ -0,0 +1,51 @@
+<?php
+
+class DocTest_Failure extends Exception
+{
+ /**
+ * The test being run when the failure was encountered.
+ *
+ * @var object
+ */
+ public $test;
+
+ /**
+ * The example in the test that failed.
+ *
+ * @var object
+ */
+ public $example;
+
+ /**
+ * The actual output from the example when tested.
+ *
+ * @var string
+ */
+ public $got;
+
+ /**
+ * Creates a failure exception.
+ *
+ * @param object $test The test which failed.
+ * @param object $example The example that caused the test to fail.
+ * @param string $got The actual output from running the example.
+ * @param string $message The optional exception message to send.
+ *
+ * @return null
+ */
+ public function __construct($test, $example, $got,
+ $message='Test failure encountered.'
+ ) {
+ /**
+ * Save the objects that caused this failure.
+ */
+ $this->test = $test;
+ $this->example = $example;
+ $this->got = $got;
+
+ /**
+ * Call the base exception constructor.
+ */
+ parent::__construct($message);
+ }
+}
View
5 src/DocTest/Parser.php
@@ -1,11 +1,6 @@
<?php
/**
- * Import the DocTest_Example source file.
- */
-require dirname(__FILE__) . '/Example.php';
-
-/**
* A class used to parse strings containing doctest examples.
*/
class DocTest_Parser
View
22 src/DocTest/Runner.php
@@ -1,8 +1,5 @@
<?php
-require dirname(__FILE__) . '/OutputChecker.php';
-require dirname(__FILE__) . '/TestResults.php';
-
/**
* A class used to run DocTest test cases, and accumulate statistics.
*/
@@ -137,7 +134,7 @@ public function run($test, $out=null, $clear_globs=true)
$out = array(__CLASS__, 'stdout');
}
- $ret = $this->_run($test, $out);
+ $r = $this->_run($test, $out);
/**
* Clear test globals if necessary.
@@ -146,7 +143,7 @@ public function run($test, $out=null, $clear_globs=true)
$test->globs = array();
}
- return $ret;
+ return $r;
}
/**
@@ -268,11 +265,9 @@ private function _run(&$test, $out)
} else {
$exc_msg = $exception->getMessage() . "\n";
- /**
- * Not doing this yet...
- * if not quiet:
- * got += _exception_traceback(exc_info)
- */
+ if (!$quiet) {
+ $got += (string)$exception;
+ }
if (is_null($example->exc_msg)) {
$outcome = $BOOM;
@@ -292,7 +287,10 @@ private function _run(&$test, $out)
/**
* Another chance if they didn't care about the detail.
- * Not doing this yet...
+ *
+ * <caution>
+ * Not doing this until we separate type and msg.
+ * </caution>
*
* if self.optionflags & IGNORE_EXCEPTION_DETAIL:
* m1 = re.match(r'[^:]*:', example.exc_msg)
@@ -460,7 +458,7 @@ protected function evalWithGlobs($source, &$globs)
$new_glob_vars = get_defined_vars();
/**
- * Unset special variablse that should not be added to globals.
+ * Unset special variables that should not be added to globals.
*/
unset($new_glob_vars['source']);
unset($new_glob_vars['globs']);
View
51 src/DocTest/UnexpectedException.php
@@ -0,0 +1,51 @@
+<?php
+
+class DocTest_UnexpectedException extends Exception
+{
+ /**
+ * The test being run when the failure was encountered.
+ *
+ * @var object
+ */
+ public $test;
+
+ /**
+ * The example in the test that failed.
+ *
+ * @var object
+ */
+ public $example;
+
+ /**
+ * The actual exception that was thrown by the test.
+ *
+ * @var object
+ */
+ public $exception;
+
+ /**
+ * Creates an unexpected exception exception.
+ *
+ * @param object $test The test which failed.
+ * @param object $example The example that caused the test to fail.
+ * @param object $exception The exception generated by the example.
+ * @param string $message The optional exception message to send.
+ *
+ * @return null
+ */
+ public function __construct($test, $example, $exception,
+ $message='Unexpected exception encountered.'
+ ) {
+ /**
+ * Save the objects that caused this failure.
+ */
+ $this->test = $test;
+ $this->example = $example;
+ $this->exception = $exception;
+
+ /**
+ * Call the base exception constructor.
+ */
+ parent::__construct($message);
+ }
+}
View
318 src/DocTest/import.php
@@ -1,293 +1,65 @@
<?php
/**
+ * Create special global constants for use in want strings.
+ */
+define('DOCTEST_BLANKLINE_MARKER', '<BLANKLINE>');
+define('DOCTEST_ELLIPSIS_MARKER', '...');
+
+/**
+ * Define a path constant for includes.
+ */
+define('DOCTEST_PATH', dirname(__FILE__) . '/');
+
+/**
+ * Import the DocTest_Failure exception.
+ */
+require_once DOCTEST_PATH . 'Failure.php';
+
+/**
+ * Import the DocTest_UnexpectedException exception.
+ */
+require_once DOCTEST_PATH . 'UnexpectedException.php';
+
+/**
+ * Import the DocTest_Example source file.
+ */
+require_once DOCTEST_PATH . 'Example.php';
+
+/**
* Import the DocTest_Parser class.
*/
-require_once dirname(__FILE__) . '/Parser.php';
+require_once DOCTEST_PATH . 'Parser.php';
/**
* Import the DocTest_Finder class.
*/
-require_once dirname(__FILE__) . '/Finder.php';
+require_once DOCTEST_PATH . 'Finder.php';
/**
- * Import the DocTest_Finder class.
+ * Import the DocTest_OutputChecker class.
+ */
+require_once DOCTEST_PATH . 'OutputChecker.php';
+
+/**
+ * Import the DocTest_TestResults class.
*/
-require_once dirname(__FILE__) . '/Runner.php';
+require_once DOCTEST_PATH . 'TestResults.php';
-class DocTest
-{
- /**
- * A static array of option flags values keyed by option name.
- *
- * @var array
- */
- public static $option_flags_by_name;
-
- public function __construct($examples, $globs, $name, $filename,
- $lineno, $docstring
- ) {
- $this->examples = $examples;
- $this->docstring = $docstring;
- $this->globs = $globs;
- $this->name = $name;
- $this->filename = $filename;
- $this->lineno = $lineno;
- }
-
- /**
- * Returns a string representation of a DocTest.
- *
- * The string returned will contain the number of examples as well
- * as the filename and line number of the DocTest.
- *
- * @return string
- */
- public function __toString()
- {
- if (!count($this->examples)) {
- $examples = 'no examples';
- } elseif (count($this->examples == 1)) {
- $examples = '1 example';
- } else {
- $examples = sprintf('%d examples', count($this->examples));
- }
- return sprintf(
- '<DocTest %s from %s:%s (%s)>',
- $this->name,
- $this->filename,
- $this->lineno,
- $examples
- );
- }
-
- /* Tests examples in the given file.
- *
- * The array returned will contain the number of test failures encountered
- * at offset 0 and the number of tests found at offset 1.
- *
- * @param string $filename The name of the file to test.
- * @param boolean $module_relative Whether to use a module relative path. By
- * default, the calling module's path will
- * be used, or the package parameter if set.
- * @param string $name The name of the test. The basename of the
- * filename will be used by default.
- * @param string $package A package whose path should be used for
- * the relative module base path.
- * @param array $globs The global variables needed by the tests.
- * @param boolean $verbose Whether or not to trigger verbose mode.
- * @param boolean $report Whether or not to output a full report.
- * @param integer $optionflags The or'ed combination of flags to use.
- * @param array $extraglobs Extra globals to use for the tests.
- * @param boolean $raise_on_errors Whether to raise an exception on error.
- * @param object $parser A parser to use for the tests.
- * @param string $encoding The encoding to use to convert the file
- * to unicode.
- *
- * @return array
- */
- public static function testFile($filename, $module_relative=true,
- $name=null, $package=null, $globs=null, $verbose=null, $report=true,
- $optionflags=0, $extraglobs=null, $raise_on_error=false,
- $parser=null, $encoding=null)
- {
- /**
- * Check for obvious path error.
- */
- if (!is_null($package) && !$module_relative) {
- throw new UnexpectedValueException(
- 'Package may only be specified for module-relative paths.'
- );
- }
-
- /**
- * Initialize parser.
- */
- if (is_null($parser)) {
- $parser = new DocTest_Parser();
- }
-
- /**
- * Relativize the path
- */
- list($text, $filename) = self::_loadTestFile($filename, $package, $module_relative);
-
- /**
- * If no name was given, then use the file's name.
- */
- if (is_null($name)) {
- $name = basename($filename);
- }
-
- /**
- * Assemble the globals.
- */
- if (is_null($globs)) {
- $globs = array();
- }
- if (!is_null($extraglobs)) {
- $globs = array_merge($globs, $extraglobs);
- }
-
- if ($raise_on_error) {
- $runner = new DocTest_DebugRunner(null, $verbose, $optionflags);
- } else {
- $runner = new DocTest_Runner(null, $verbose, $optionflags);
- }
-
- /**
- * Convert encoding
- */
- if (!is_null($encoding) && function_exists('mb_convert_encoding')) {
- $text = mb_convert_encoding($text, $encoding);
- }
-
- /**
- * Read the file, convert it to a test, and run it.
- */
- $test = $parser->getDocTest($text, $globs, $name, $filename, 0);
- $runner->run($test);
-
- if ($report) {
- $runner->summarize();
- }
-
- return new DocTest_TestResults($runner->failures, $runner->tries);
- }
-
- private static function _loadTestFile($filename, $package, $module_relative)
- {
- /**
- * Determine the absolute file path.
- */
- if ($module_relative) {
-
- /**
- * Use the calling module's dir as a base path if no package is set.
- */
- if (is_null($package)) {
- /**
- * Get a backtrace.
- */
- $bt = debug_backtrace();
-
- /**
- * Find the first file in the trace that's not this one.
- */
- foreach ($bt as $trace) {
- if ($trace['file'] !== __FILE__) {
- /**
- * Found calling file. Get the dir and kill the loop.
- */
- $package = dirname($trace['file']);
- break;
- }
- }
- }
-
- /**
- * Ensure package ends in a slash.
- */
- if (substr($package, -1) !== '/') {
- $package .= '/';
- }
-
- /**
- * Add the filename to the package dir.
- */
- $filename = $package . $filename;
- }
-
- /**
- * Return the file as a string along with the resolved filename.
- */
- return array(file_get_contents($filename), $filename);
- }
-
- /**
- * Registers available options.
- */
- public static function registerOptions()
- {
- /**
- * Protect against multiple calls.
- */
- if (!is_null(self::$option_flags_by_name)) {
- return;
- }
+/**
+ * Import the DocTest_Runner class.
+ */
+require_once DOCTEST_PATH . 'Runner.php';
- /**
- * Initialize options flag array.
- */
- self::$option_flags_by_name = array();
-
- /**
- * The possible base options.
- */
- $options = array(
- 'DONT_ACCEPT_TRUE_FOR_1',
- 'DONT_ACCEPT_BLANKLINE',
- 'NORMALIZE_WHITESPACE',
- 'ELLIPSIS',
- 'SKIP',
- 'IGNORE_EXCEPTION_DETAIL',
- 'REPORT_UDIFF',
- 'REPORT_NDIFF',
- 'REPORT_CDIFF',
- 'REPORT_ONLY_FIRST_FAILURE'
- );
-
- /**
- * Define a global namespaced constant for each option and add it
- * to the static named options array.
- */
- foreach ($options as $i => $option) {
- $namespaced = 'DOCTEST_' . $option;
- define($namespaced, 1 << $i);
- self::$option_flags_by_name[$option] = constant($namespaced);
- }
-
- /**
- * Create comparison flags combination.
- */
- $comp_flags = DOCTEST_DONT_ACCEPT_TRUE_FOR_1 |
- DOCTEST_DONT_ACCEPT_BLANKLINE |
- DOCTEST_NORMALIZE_WHITESPACE |
- DOCTEST_ELLIPSIS |
- DOCTEST_SKIP |
- DOCTEST_IGNORE_EXCEPTION_DETAIL;
- define('DOCTEST_COMPARISON_FLAGS', $comp_flags);
-
- /**
- * Create reporting flags combination.
- */
- $rep_flags = DOCTEST_REPORT_UDIFF |
- DOCTEST_REPORT_CDIFF |
- DOCTEST_REPORT_NDIFF |
- DOCTEST_REPORT_ONLY_FIRST_FAILURE;
- define('DOCTEST_REPORTING_FLAGS', $rep_flags);
- }
-
- /**
- * Returns a string with non-empty lines prefixed with a number of spaces.
- *
- * <note>This method is the global "_indent()" function in Python.</note>
- *
- * @param string $s The string to indent.
- * @param integer $indent The number of spaces to indent.
- *
- * @return string
- */
- public function indent($s, $indent=4) {
- return preg_replace('/(?m)^(?!$)/', str_repeat(' ', $indent), $s);
- }
-}
+/**
+ * Import the DocTest_DebugRunner class.
+ */
+require_once DOCTEST_PATH . 'DebugRunner.php';
/**
- * Create special global constants for use in want strings.
+ * Import the main DocTest class.
*/
-define('DOCTEST_BLANKLINE_MARKER', '<BLANKLINE>');
-define('DOCTEST_ELLIPSIS_MARKER', '...');
+require_once DOCTEST_PATH . 'DocTest.php';
/**
* Register option flag constants.
Please sign in to comment.
Something went wrong with that request. Please try again.