/** * Runs this module * * @throws \Exception */ public function run() { // Create a suite to add spec files $suite = new Spec\TestSuite(); // For every argument given check what files it matches foreach ($this->result->args['files'] as $file) { if (is_file($file)) { $suite->addTestFile($file); } else { if (is_dir($file)) { $this->searchForSpecs($suite, $file); } else { $glob = basename($file); $file = dirname($file); $this->searchForSpecs($suite, $file, $glob); } } } // Check if we just want to list the available groups if (!empty($this->result->options['list_groups'])) { print "Available test group(s):\n"; $groups = $suite->getGroups(); sort($groups); foreach ($groups as $group) { print " - {$group}\n"; } exit(0); } // Create a printer instance if ($this->result->options['story']) { $this->result->options['format'] = 'story'; } // @todo Allow custom class names switch (strtolower($this->result->options['format'])) { case 'd': case 'dots': $formatter = '\\DrSlump\\Spec\\Cli\\ResultPrinter\\Dots'; break; case 's': case 'story': $formatter = '\\DrSlump\\Spec\\Cli\\ResultPrinter\\Story'; break; default: throw new \RuntimeException('Unknown format option'); } $printer = new $formatter(NULL, (bool) $this->result->options['verbose'], (bool) $this->result->options['color'], (bool) $this->result->options['debug']); // Create a PHPUnit result manager $result = new \PHPUnit_Framework_TestResult(); // Append our custom printer as a listener $result->addListener($printer); // Register beeping listener if ($this->result->options['beep']) { $result->addListener(new Spec\Cli\BeepListener()); } // Configure filter $filter = false; if (!empty($this->result->options['filter'])) { // Escape delimiters in regular expression. $filter = '/(' . implode(')|(', $this->result->options['filter']) . ')/i'; } // Configure groups $groups = array(); if (!empty($this->result->options['groups'])) { foreach ($this->result->options['groups'] as $opt) { $groups = array_merge($groups, explode(',', $opt)); } $groups = array_map('trim', $groups); $groups = array_filter($groups); $groups = array_unique($groups); } // Configure excluded groups $excluded = array(); if (!empty($this->result->options['exclude_groups'])) { foreach ($this->result->options['exclude_groups'] as $opt) { $excluded = array_merge($excluded, explode(',', $opt)); } $excluded = array_map('trim', $excluded); $excluded = array_filter($excluded); $excluded = array_unique($excluded); } try { // Run the suite $suite->run($result, $filter, $groups, $excluded, false); } catch (\Exception $e) { // Recursive function to flag all tests in a suite as failed $fail = function ($suite) use(&$result, &$fail, &$e) { foreach ($suite->tests() as $test) { if ($test instanceof \PHPUnit_Framework_TestSuite) { $fail($test); continue; } // Only flag as failed the ones that haven't been run yet if (NULL === $test->getStatus()) { $result->addError($test, $e, 0); } } }; // Check starting at the current suite since it's the one that have failed $fail(Spec::suite()); } unset($suite); $result->flushListeners(); $printer->printResult($result); }
// This source file is subject to the MIT license that is bundled // with this package in the file LICENSE. // It is also available through the world-wide-web at this URL: // http://creativecommons.org/licenses/MIT/ use DrSlump\Spec; // Include Hamcrest matchers library require_once 'Hamcrest/MatcherAssert.php'; require_once 'Hamcrest/Matchers.php'; // Hamcrest does not include these ones by default require_once 'Hamcrest/Type/IsNumeric.php'; require_once 'Hamcrest/Type/IsCallable.php'; // Matcher names should be written as if they were to complete the // sentence "value should ...". Words like 'be', 'to', 'at', 'the' ... // will be automatically ignored but when Spec finds two conflicting // matchers they will be used to disambiguate. $matchers = Spec::matchers(); $matchers['be equal to'] = '\\Hamcrest_Matchers::equalTo'; $matchers['be eq to'] = '\\Hamcrest_Matchers::equalTo'; $matchers['be the same to'] = '\\Hamcrest_Matchers::identicalTo'; $matchers['be identical to'] = '\\Hamcrest_Matchers::identicalTo'; $matchers['be exactly'] = '\\Hamcrest_Matchers::identicalTo'; $matchers['be exactly equal to'] = '\\Hamcrest_Matchers::identicalTo'; $matchers['be at most'] = '\\Hamcrest_Matchers::lessThanOrEqualTo'; $matchers['be less equal to'] = '\\Hamcrest_Matchers::lessThanOrEqualTo'; $matchers['be less equal than'] = '\\Hamcrest_Matchers::lessThanOrEqualTo'; $matchers['be le to'] = '\\Hamcrest_Matchers::lessThanOrEqualTo'; $matchers['be le than'] = '\\Hamcrest_Matchers::lessThanOrEqualTo'; $matchers['be at least'] = '\\Hamcrest_Matchers::greaterThanOrEqualTo'; $matchers['be more equal to'] = '\\Hamcrest_Matchers::greaterThanOrEqualTo'; $matchers['be more equal than'] = '\\Hamcrest_Matchers::greaterThanOrEqualTo'; $matchers['be greater equal to'] = '\\Hamcrest_Matchers::greaterThanOrEqualTo';
/** * Prepares a test to be run. This method executes before setUp. * * @param Spec\TestCaseInterface $test */ public function prepareTest(Spec\TestCaseInterface $test) { $ann = $test->annotations; /* @todo Shouldn't this be already done by PHPUnit if (!empty($ann['outputBuffering'])) { $enabled = filter_var($ann['outputBuffering'][0], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); $test->setUseOutputBuffering($enabled); } if (!empty($ann['errorHandler'])) { $enabled = filter_var($ann['errorHandler'][0], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); $test->setUseErrorHandler($enabled); } */ // Check @throws annotation // @todo can this be moved to setUp so it overrides PHPUnit annotations? if (!empty($ann['throws'])) { $regexp = '/([:.\\w\\\\x7f-\\xff]+)(?:[\\t ]+(\\S*))?(?:[\\t ]+(\\S*))?\\s*$/'; if (preg_match($regexp, $ann['throws'][0], $m)) { $class = $code = $message = null; if (is_numeric($m[1])) { $code = $m[1]; } else { $class = $m[1]; } $message = isset($m[2]) ? $m[2] : null; $code = isset($m[3]) ? (int) $m[3] : $code; $test->setExpectedException($class, $message, $code); } } // Register the current test case as the active one Spec::test($test); }
/** * Create a test * * @see Spec::it * @param string $msg * @param callback $cb */ function it($msg, $cb) { Spec::it($msg, $cb); }
public function assert($name, $args) { $name = trim($name, '_'); // Convert camelCase to underscores $name = preg_replace_callback('/([a-z])([A-Z])/', function ($m) { return $m[1] . '_' . $m[2]; }, $name); // Make it all lowercase $name = strtolower($name); // Remove 'to' if it's at the beginning since it might be used // when manually calling expect() $name = preg_replace('/^to_/', '', $name); // Extract ORs/ANDs/BUTs/AS from name if (preg_match('/^[a-z]+_(or|and|but|as)_?$/i', $name)) { // We need to disable implicit assertion if set $origImplicit = $this->implicitAssert; $this->implicitAssert = false; $parts = preg_split('/\\b(or|and|but|as)\\b/i', $name, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); $prefix = ''; do { $part = array_shift($parts); if (empty($part)) { break; } // Should be an "as" $this->assert($prefix . $part, count($parts) ? array() : $args); $prefix = array_shift($parts); } while (count($parts)); if (strtolower($prefix) === 'as') { $this->describedAs($args[0]); } if ($origImplicit) { $this->implicitAssert = true; $this->doAssert(); } return $this; } // Explode by the underscore $parts = explode('_', $name); // Calculate if it's a negation $isNegation = false; foreach ($parts as $part) { if ($part === 'not' || $part == 'no') { $isNegation = !$isNegation; } } // Manage coordination operators switch ($parts[0]) { case 'described': if (empty($parts[1]) || $parts[1] !== 'as') { break; } case 'as': $this->describedAs($args[0]); return $this; case 'and': $this->expression->addOperator('AND', 10); array_shift($parts); break; case 'or': $this->expression->addOperator('OR', 5); array_shift($parts); break; case 'but': $this->expression->addOperator('BUT', 1); array_shift($parts); break; default: // If no operator was given assume OR if (NULL !== $this->prevMatcher) { $this->expression->addOperator('OR', 5); } } // If nothing left reuse previous matcher if (empty($parts)) { if (NULL === $this->prevMatcher) { throw new \RuntimeException("Unable to re-use previous matcher since it's empty"); } else { if (empty($args)) { throw new \RuntimeException("Unable to re-use previous matcher without arguments being given"); } } $matcher = $this->prevMatcher; } else { $matcher = implode(' ', $parts); $this->prevMatcher = $matcher; } // Find the matcher for the given name $callback = Spec::matchers()->find($matcher); if (FALSE === $callback) { // Remove special words from the original matcher name $parts = explode('_', $name); $parts = array_diff($parts, $this->specialWords); $name = implode('_', $parts); $msg = "Matcher '" . str_replace('_', ' ', $name) . "' not found."; $suggestions = Spec::matchers()->suggest($name, 0.5); if (count($suggestions)) { $msg .= " Perhaps you meant to use '" . array_shift($suggestions) . "' ?"; } throw new \Exception($msg); } // Instantiate the matcher $matcher = call_user_func_array($callback, $args); if ($isNegation) { $matcher = \Hamcrest_Core_IsNot::not($matcher); } $this->expression->addOperand($matcher); // Run the assertion now if the implicit flag is set if ($this->implicitAssert) { $this->doAssert(); } return $this; }
/** * Wraps both <code>addTest()</code> and <code>addTestSuite</code> * as well as the separate import statements for the user's convenience. * * If the named file cannot be read or there are no new tests that can be * added, a <code>PHPUnit_Framework_Warning</code> will be created instead, * leaving the current test run untouched. * * @param string $filename * @param boolean $syntaxCheck * @param array $phptOptions Array with ini settings for the php instance * run, key being the name if the setting, * value the ini value. * @throws InvalidArgumentException */ public function addTestFile($filename, $syntaxCheck = FALSE, $phptOptions = array()) { if (!is_string($filename)) { throw \PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); } // Ensure we can read the file if (!$filename || !is_readable($filename)) { throw new \RuntimeException(sprintf('Cannot open file "%s".' . "\n", $filename)); } // Try to convert it to a relative path if (strpos($filename, getcwd()) === 0) { $filename = substr($filename, strlen(getcwd()) + 1); } else { if (strpos($filename, './') === 0) { $filename = substr($filename, strlen('./')); } } // Use stream wrapper for spec files $furl = Spec::SCHEME . '://' . $filename; // Setup the environment to collect tests \DrSlump\Spec::reset($this); \PHPUnit_Util_Fileloader::load($furl); $this->numTests = -1; }