/** * Runs the test case. * @return void */ public function run($method = NULL) { $rc = new \ReflectionClass($this); $methods = $method ? array($rc->getMethod($method)) : $rc->getMethods(\ReflectionMethod::IS_PUBLIC); foreach ($methods as $method) { if (!preg_match('#^test[A-Z]#', $method->getName())) { continue; } $data = array(); $info = Helpers::parseDocComment($method->getDocComment()) + array('dataprovider' => NULL, 'throws' => NULL); if ($info['throws'] === '') { throw new TestCaseException("Missing class name in @throws annotation for {$method->getName()}()."); } elseif (is_array($info['throws'])) { throw new TestCaseException("Annotation @throws for {$method->getName()}() can be specified only once."); } else { $throws = preg_split('#\\s+#', $info['throws'], 2) + array(NULL, NULL); } foreach ((array) $info['dataprovider'] as $provider) { $res = $this->getData($provider); if (!is_array($res)) { throw new TestCaseException("Data provider {$provider}() doesn't return array."); } $data = array_merge($data, $res); } if (!$info['dataprovider']) { if ($method->getNumberOfRequiredParameters()) { throw new TestCaseException("Method {$method->getName()}() has arguments, but @dataProvider is missing."); } $data[] = array(); } foreach ($data as $args) { try { if ($info['throws']) { $tmp = $this; $e = Assert::error(function () use($tmp, $method, $args) { $tmp->runTest($method->getName(), $args); }, $throws[0], $throws[1]); if ($e instanceof AssertException) { throw $e; } } else { $this->runTest($method->getName(), $args); } } catch (AssertException $e) { $e->message .= " in {$method->getName()}" . substr(Dumper::toLine($args), 5); throw $e; } } } }
/** * Runs the test method. * @return void */ private function runMethod($method) { $method = new \ReflectionMethod($this, $method); if (!$method->isPublic()) { throw new TestCaseException("Method {$method->getName()} is not public. Make it public or rename it."); } $data = array(); $info = Helpers::parseDocComment($method->getDocComment()) + array('dataprovider' => NULL, 'throws' => NULL); if ($info['throws'] === '') { throw new TestCaseException("Missing class name in @throws annotation for {$method->getName()}()."); } elseif (is_array($info['throws'])) { throw new TestCaseException("Annotation @throws for {$method->getName()}() can be specified only once."); } else { $throws = preg_split('#\\s+#', $info['throws'], 2) + array(NULL, NULL); } $defaultParams = array(); foreach ($method->getParameters() as $param) { $defaultParams[$param->getName()] = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : NULL; } foreach ((array) $info['dataprovider'] as $provider) { $res = $this->getData($provider); if (!is_array($res)) { throw new TestCaseException("Data provider {$provider}() doesn't return array."); } foreach ($res as $set) { $data[] = is_string(key($set)) ? array_merge($defaultParams, $set) : $set; } } if (!$info['dataprovider']) { if ($method->getNumberOfRequiredParameters()) { throw new TestCaseException("Method {$method->getName()}() has arguments, but @dataProvider is missing."); } $data[] = array(); } foreach ($data as $args) { try { if ($info['throws']) { $tmp = $this; $e = Assert::error(function () use($tmp, $method, $args) { $tmp->runTest($method->getName(), $args); }, $throws[0], $throws[1]); if ($e instanceof AssertException) { throw $e; } } else { $this->runTest($method->getName(), $args); } } catch (AssertException $e) { throw $e->setMessage("{$e->origMessage} in {$method->getName()}" . substr(Dumper::toLine($args), 5)); } } }
/** * Runs the test method. * @param string test method name * @param array test method parameters (dataprovider bypass) * @return void */ public function runTest($method, array $args = NULL) { $method = new \ReflectionMethod($this, $method); if (!$method->isPublic()) { throw new TestCaseException("Method {$method->getName()} is not public. Make it public or rename it."); } $info = Helpers::parseDocComment($method->getDocComment()) + array('dataprovider' => NULL, 'throws' => NULL); if ($info['throws'] === '') { throw new TestCaseException("Missing class name in @throws annotation for {$method->getName()}()."); } elseif (is_array($info['throws'])) { throw new TestCaseException("Annotation @throws for {$method->getName()}() can be specified only once."); } else { $throws = preg_split('#\\s+#', $info['throws'], 2) + array(NULL, NULL); } $data = array(); if ($args === NULL) { $defaultParams = array(); foreach ($method->getParameters() as $param) { $defaultParams[$param->getName()] = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : NULL; } foreach ((array) $info['dataprovider'] as $provider) { $res = $this->getData($provider); if (!is_array($res)) { throw new TestCaseException("Data provider {$provider}() doesn't return array."); } foreach ($res as $set) { $data[] = is_string(key($set)) ? array_merge($defaultParams, $set) : $set; } } if (!$info['dataprovider']) { if ($method->getNumberOfRequiredParameters()) { throw new TestCaseException("Method {$method->getName()}() has arguments, but @dataProvider is missing."); } $data[] = array(); } } else { $data[] = $args; } $me = $this; $errorHandler = function () use($me, &$prev) { restore_error_handler(); $rm = new \ReflectionMethod($me, 'tearDown'); $rm->setAccessible(TRUE); set_error_handler(function () { }); // mute all errors $rm->invoke($me); restore_error_handler(); return $prev ? call_user_func_array($prev, func_get_args()) : FALSE; }; foreach ($data as $params) { try { $this->setUp(); $prev = set_error_handler($errorHandler); try { if ($info['throws']) { $tmp = $this; $e = Assert::error(function () use($tmp, $method, $params) { call_user_func_array(array($tmp, $method->getName()), $params); }, $throws[0], $throws[1]); if ($e instanceof AssertException) { throw $e; } } else { call_user_func_array(array($this, $method->getName()), $params); } } catch (\Exception $testException) { } restore_error_handler(); try { $this->tearDown(); } catch (\Exception $tearDownException) { } if (isset($testException)) { throw $testException; } elseif (isset($tearDownException)) { throw $tearDownException; } } catch (AssertException $e) { throw $e->setMessage("{$e->origMessage} in {$method->getName()}" . substr(Dumper::toLine($params), 5)); } } }
public function setMessage($message) { $this->origMessage = $message; $this->message = strtr($message, array('%1' => Dumper::toLine($this->actual), '%2' => Dumper::toLine($this->expected))); return $this; }
/** @internal */ public static function dumpException(\Exception $e) { $trace = $e->getTrace(); array_splice($trace, 0, $e instanceof \ErrorException ? 1 : 0, array(array('file' => $e->getFile(), 'line' => $e->getLine()))); $testFile = NULL; foreach (array_reverse($trace) as $item) { if (isset($item['file'])) { // in case of shutdown handler, we want to skip inner-code blocks and debugging calls $testFile = $item['file']; break; } } if ($e instanceof AssertException) { if (is_object($e->expected) || is_array($e->expected) || is_string($e->expected) && strlen($e->expected) > self::$maxLength || is_object($e->actual) || is_array($e->actual) || is_string($e->actual) && strlen($e->actual) > self::$maxLength) { $args = isset($_SERVER['argv'][1]) ? '.[' . preg_replace('#[^a-z0-9-. ]+#i', '_', $_SERVER['argv'][1]) . ']' : ''; $stored[] = self::saveOutput($testFile, $e->expected, $args . '.expected'); $stored[] = self::saveOutput($testFile, $e->actual, $args . '.actual'); } if (is_string($e->actual) && is_string($e->expected)) { for ($i = 0; $i < strlen($e->actual) && isset($e->expected[$i]) && $e->actual[$i] === $e->expected[$i]; $i++) { } $i = max(0, min($i, max(strlen($e->actual), strlen($e->expected)) - self::$maxLength + 3)); for (; $i && $i < count($e->actual) && $e->actual[$i - 1] >= "€" && $e->actual[$i] >= "€" && $e->actual[$i] < "À"; $i--) { } if ($i) { $e->expected = substr_replace($e->expected, '...', 0, $i); $e->actual = substr_replace($e->actual, '...', 0, $i); } } $message = 'Failed: ' . $e->getMessage(); if (is_string($e->actual) && is_string($e->expected) || is_array($e->actual) && is_array($e->expected)) { preg_match('#^(.*)(%\\d)(.*)(%\\d.*)\\z#', $message, $m); if (($delta = strlen($m[1]) - strlen($m[3])) >= 3) { $message = "{$m['1']}{$m['2']}\n" . str_repeat(' ', $delta - 3) . "...{$m['3']}{$m['4']}"; } else { $message = "{$m['1']}{$m['2']}{$m['3']}\n" . str_repeat(' ', strlen($m[1]) - 4) . "... {$m['4']}"; } } $message = strtr($message, array('%1' => "[1;33m" . Dumper::toLine($e->actual) . "[1;37m", '%2' => "[1;33m" . Dumper::toLine($e->expected) . "[1;37m")); } else { $message = ($e instanceof \ErrorException ? Helpers::errorTypeToString($e->getSeverity()) : get_class($e)) . ": {$e->getMessage()}"; } $s = "[1;37m{$message}[0m\n\n" . (isset($stored) ? "diff " . escapeshellarg($stored[0]) . " " . escapeshellarg($stored[1]) . "\n\n" : ''); foreach ($trace as $item) { $item += array('file' => NULL); $s .= 'in ' . ($item['file'] === $testFile ? "[1;37m" : '') . ($item['file'] ? implode(DIRECTORY_SEPARATOR, array_slice(explode(DIRECTORY_SEPARATOR, $item['file']), -3)) . "({$item['line']})" : '[internal function]') . "[1;30m " . (isset($item['class']) ? $item['class'] . $item['type'] : '') . (isset($item['function']) ? $item['function'] . '()' : '') . "[0m\n"; } if ($e->getPrevious()) { $s .= "\n(previous) " . static::dumpException($e->getPrevious()); } return $s; }
/** * Runs the test method. * @param string test method name * @param array test method parameters (dataprovider bypass) * @return void */ public function runTest($method, array $args = NULL) { if (!method_exists($this, $method)) { throw new TestCaseException("Method '{$method}' does not exist."); } elseif (!preg_match(self::METHOD_PATTERN, $method)) { throw new TestCaseException("Method '{$method}' is not a testing method."); } $method = new \ReflectionMethod($this, $method); if (!$method->isPublic()) { throw new TestCaseException("Method {$method->getName()} is not public. Make it public or rename it."); } $info = Helpers::parseDocComment($method->getDocComment()) + ['dataprovider' => NULL, 'throws' => NULL]; if ($info['throws'] === '') { throw new TestCaseException("Missing class name in @throws annotation for {$method->getName()}()."); } elseif (is_array($info['throws'])) { throw new TestCaseException("Annotation @throws for {$method->getName()}() can be specified only once."); } else { $throws = preg_split('#\\s+#', $info['throws'], 2) + [NULL, NULL]; } $data = []; if ($args === NULL) { $defaultParams = []; foreach ($method->getParameters() as $param) { $defaultParams[$param->getName()] = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : NULL; } foreach ((array) $info['dataprovider'] as $provider) { $res = $this->getData($provider); if (!is_array($res) && !$res instanceof \Traversable) { throw new TestCaseException("Data provider {$provider}() doesn't return array or Traversable."); } foreach ($res as $set) { $data[] = is_string(key($set)) ? array_merge($defaultParams, $set) : $set; } } if (!$info['dataprovider']) { if ($method->getNumberOfRequiredParameters()) { throw new TestCaseException("Method {$method->getName()}() has arguments, but @dataProvider is missing."); } $data[] = []; } } else { $data[] = $args; } if ($this->prevErrorHandler === FALSE) { $this->prevErrorHandler = set_error_handler(function ($severity) { if ($this->handleErrors && ($severity & error_reporting()) === $severity) { $this->handleErrors = FALSE; $this->silentTearDown(); } return $this->prevErrorHandler ? call_user_func_array($this->prevErrorHandler, func_get_args()) : FALSE; }); } foreach ($data as $params) { try { $this->setUp(); $this->handleErrors = TRUE; try { if ($info['throws']) { $e = Assert::error(function () use($method, $params) { call_user_func_array([$this, $method->getName()], $params); }, $throws[0], $throws[1]); if ($e instanceof AssertException) { throw $e; } } else { call_user_func_array([$this, $method->getName()], $params); } } catch (\Exception $e) { $this->handleErrors = FALSE; $this->silentTearDown(); throw $e; } $this->handleErrors = FALSE; $this->tearDown(); } catch (AssertException $e) { throw $e->setMessage("{$e->origMessage} in {$method->getName()}(" . substr(Dumper::toLine($params), 1, -1) . ')'); } } }