From 1182d68932b6d85477cbf28e1bed7c4c830b99db Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 8 Feb 2019 12:28:11 +0100 Subject: [PATCH 1/4] TestCase: refactoring, added parseOutput() --- src/Framework/TestCase.php | 9 +++++++++ src/Runner/TestHandler.php | 7 ++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Framework/TestCase.php b/src/Framework/TestCase.php index 823f4f27..0c88cd6a 100644 --- a/src/Framework/TestCase.php +++ b/src/Framework/TestCase.php @@ -58,6 +58,15 @@ public function run(): void } + public static function parseOutput(string $output): ?array + { + if (!preg_match('#\[([^[]*)]#', (string) strrchr($output, '['), $m)) { + return null; + } + return $m[1] ? explode(',', $m[1]) : []; + } + + /** * Runs the test method. * @param array $args test method parameters (dataprovider bypass) diff --git a/src/Runner/TestHandler.php b/src/Runner/TestHandler.php index f7311c80..8b0c2583 100644 --- a/src/Runner/TestHandler.php +++ b/src/Runner/TestHandler.php @@ -164,15 +164,16 @@ private function initiateTestCase(Test $test, $foo, PhpInterpreter $interpreter) return $test->withResult($job->getExitCode() === Job::CODE_SKIP ? Test::SKIPPED : Test::FAILED, $job->getTest()->stdout); } - if (!preg_match('#\[([^[]*)]#', (string) strrchr($job->getTest()->stdout, '['), $m)) { + $methods = TestCase::parseOutput($job->getTest()->stdout); + if ($methods === null) { return $test->withResult(Test::FAILED, "Cannot list TestCase methods in file '{$test->getFile()}'. Do you call TestCase::run() in it?"); - } elseif (!strlen($m[1])) { + } elseif (!$methods) { return $test->withResult(Test::SKIPPED, "TestCase in file '{$test->getFile()}' does not contain test methods."); } return array_map(function (string $method) use ($test): Test { return $test->withArguments(['method' => $method]); - }, explode(',', $m[1])); + }, $methods); } From 1c775693afed6b8261c90def5cb92515e3da70e8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 7 Feb 2019 21:04:05 +0100 Subject: [PATCH 2/4] TestCase: refactoring, added findMethods() and runFromCli() --- src/Framework/TestCase.php | 52 +++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/Framework/TestCase.php b/src/Framework/TestCase.php index 0c88cd6a..eeb11260 100644 --- a/src/Framework/TestCase.php +++ b/src/Framework/TestCase.php @@ -15,10 +15,8 @@ */ class TestCase { - /** @internal */ - public const - LIST_METHODS = 'nette-tester-list-methods', - METHOD_PATTERN = '#^test[A-Z0-9_]#'; + public const LIST_METHODS = 'nette-tester-list-methods'; + private const METHOD_PATTERN = '#^test[A-Z0-9_]#'; /** @var bool */ private $handleErrors = false; @@ -36,25 +34,39 @@ public function run(): void throw new \LogicException('Calling TestCase::run($method) is deprecated. Use TestCase::runTest($method) instead.'); } - $methods = array_values(preg_grep(self::METHOD_PATTERN, array_map(function (\ReflectionMethod $rm): string { - return $rm->getName(); - }, (new \ReflectionObject($this))->getMethods()))); - - if (isset($_SERVER['argv']) && ($tmp = preg_filter('#--method=([\w-]+)$#Ai', '$1', $_SERVER['argv']))) { - $method = reset($tmp); - if ($method === self::LIST_METHODS) { - Environment::$checkAssertions = false; - header('Content-Type: text/plain'); - echo '[' . implode(',', $methods) . ']'; - return; - } + if ($this->runFromCli()) { + return; + } + + foreach ($this::findMethods() as $method) { $this->runTest($method); + } + } - } else { - foreach ($methods as $method) { - $this->runTest($method); - } + + public static function findMethods(): array + { + $methods = (new \ReflectionClass(static::class))->getMethods(); + return array_values(preg_grep(self::METHOD_PATTERN, array_map(function (\ReflectionMethod $rm): string { + return $rm->getName(); + }, $methods))); + } + + + private function runFromCli(): bool + { + $args = preg_filter('#--method=([\w-]+)$#Ai', '$1', $_SERVER['argv'] ?? []); + $arg = reset($args); + if (!$arg) { + return false; + } elseif ($arg === self::LIST_METHODS) { + Environment::$checkAssertions = false; + header('Content-Type: text/plain'); + echo '[' . implode(',', $this::findMethods()) . ']'; + } elseif ($arg) { + $this->runTest($arg); } + return true; } From 660ca7e8a10214d19b08d90ab0f239dbcfafc39a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 7 Feb 2019 21:06:36 +0100 Subject: [PATCH 3/4] TestCaseRunner WIP --- demo/TestFour.php | 19 +++++++ demo/TestOne.php | 13 +++++ demo/TestThree.php | 13 +++++ demo/TestTwo.php | 13 +++++ demo/runner.phpt | 14 ++++++ src/Framework/TestCaseRunner.php | 86 ++++++++++++++++++++++++++++++++ src/bootstrap.php | 1 + src/tester.php | 1 + 8 files changed, 160 insertions(+) create mode 100644 demo/TestFour.php create mode 100644 demo/TestOne.php create mode 100644 demo/TestThree.php create mode 100644 demo/TestTwo.php create mode 100644 demo/runner.phpt create mode 100644 src/Framework/TestCaseRunner.php diff --git a/demo/TestFour.php b/demo/TestFour.php new file mode 100644 index 00000000..ef695009 --- /dev/null +++ b/demo/TestFour.php @@ -0,0 +1,19 @@ +findTests(__DIR__ . '/Test*.php') + ->run(); diff --git a/src/Framework/TestCaseRunner.php b/src/Framework/TestCaseRunner.php new file mode 100644 index 00000000..d223b293 --- /dev/null +++ b/src/Framework/TestCaseRunner.php @@ -0,0 +1,86 @@ +isSubclassOf(TestCase::class) && isset($files[$rc->getFileName()])) { + $this->classes[] = $class; + } + } + return $this; + } + + + public function run(): void + { + if ($this->runFromCli()) { + return; + } + + foreach ($this->classes as $class) { + $test = $this->createInstance($class); + $test->run(); + } + } + + + private function runFromCli(): bool + { + $args = preg_filter('#--method=([\w:-]+)$#Ai', '$1', $_SERVER['argv'] ?? []); + $arg = reset($args); + if (!$arg) { + return false; + + } elseif ($arg === self::LIST_METHODS) { + Environment::$checkAssertions = false; + $methods = []; + foreach ($this->classes as $class) { + foreach ($class::findMethods() as $method) { + $methods[] = $class . '::' . $method; + } + } + header('Content-Type: text/plain'); + echo '[' . implode(',', $methods) . ']'; + + } else { + [$class, $method] = explode('::', $arg); + $test = $this->createInstance($class); + $test->runTest($method); + } + return true; + } + + + protected function createInstance(string $class): TestCase + { + // TOO: can be altered via setFactory(callable) + return new $class; + } +} diff --git a/src/bootstrap.php b/src/bootstrap.php index 7c6b9a90..1a059117 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -14,6 +14,7 @@ require __DIR__ . '/Framework/Dumper.php'; require __DIR__ . '/Framework/FileMock.php'; require __DIR__ . '/Framework/TestCase.php'; +require __DIR__ . '/Framework/TestCaseRunner.php'; require __DIR__ . '/Framework/DomQuery.php'; require __DIR__ . '/Framework/FileMutator.php'; require __DIR__ . '/CodeCoverage/Collector.php'; diff --git a/src/tester.php b/src/tester.php index efa15a34..0b369140 100644 --- a/src/tester.php +++ b/src/tester.php @@ -26,6 +26,7 @@ require __DIR__ . '/Framework/Dumper.php'; require __DIR__ . '/Framework/DataProvider.php'; require __DIR__ . '/Framework/TestCase.php'; +require __DIR__ . '/Framework/TestCaseRunner.php'; require __DIR__ . '/CodeCoverage/PhpParser.php'; require __DIR__ . '/CodeCoverage/Generators/AbstractGenerator.php'; require __DIR__ . '/CodeCoverage/Generators/HtmlGenerator.php'; From eacdb436fc5155cb222fc2a7dc5da288fb29e108 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 7 Feb 2019 20:55:13 +0100 Subject: [PATCH 4/4] @testcase is not needed WIP --- demo/runner.phpt | 4 ---- demo2/runner.phpt | 27 +++++++++++++++++++++++ src/Framework/TestCaseRunner.php | 37 ++++++++++++++++++-------------- src/Runner/Job.php | 1 + src/Runner/TestHandler.php | 20 ++++++++++++++--- 5 files changed, 66 insertions(+), 23 deletions(-) create mode 100644 demo2/runner.phpt diff --git a/demo/runner.phpt b/demo/runner.phpt index 71252fb4..38767ec1 100644 --- a/demo/runner.phpt +++ b/demo/runner.phpt @@ -1,9 +1,5 @@ run(new MyTest); + +// this could be the recommended way to run single test, that will replace @testcase diff --git a/src/Framework/TestCaseRunner.php b/src/Framework/TestCaseRunner.php index d223b293..48b1c116 100644 --- a/src/Framework/TestCaseRunner.php +++ b/src/Framework/TestCaseRunner.php @@ -15,8 +15,6 @@ */ class TestCaseRunner { - private const LIST_METHODS = 'nette-tester-list-methods'; - /** @var array */ private $classes = []; @@ -38,43 +36,50 @@ public function findTests(string $fileMask): self } - public function run(): void + public function run(TestCase $test = null): void { - if ($this->runFromCli()) { + if ($this->runFromCli($test)) { return; - } - foreach ($this->classes as $class) { - $test = $this->createInstance($class); + } elseif ($test) { $test->run(); + + } else { + foreach ($this->classes as $class) { + $test = $this->createInstance($class); + $test->run(); + } } } - private function runFromCli(): bool + private function runFromCli(TestCase $test = null): bool { $args = preg_filter('#--method=([\w:-]+)$#Ai', '$1', $_SERVER['argv'] ?? []); $arg = reset($args); - if (!$arg) { - return false; - } elseif ($arg === self::LIST_METHODS) { + if ($arg) { + [$class, $method] = explode('::', $arg); + $test = $test ?: $this->createInstance($class); + $test->runTest($method); + return true; + + } elseif (getenv(Environment::RUNNER)) { Environment::$checkAssertions = false; $methods = []; - foreach ($this->classes as $class) { + $classes = $test ? [get_class($test)] : $this->classes; + foreach ($classes as $class) { foreach ($class::findMethods() as $method) { $methods[] = $class . '::' . $method; } } header('Content-Type: text/plain'); echo '[' . implode(',', $methods) . ']'; + exit(Runner\Job::CODE_TESTCASE); } else { - [$class, $method] = explode('::', $arg); - $test = $this->createInstance($class); - $test->runTest($method); + return false; } - return true; } diff --git a/src/Runner/Job.php b/src/Runner/Job.php index 1ab8a218..43504413 100644 --- a/src/Runner/Job.php +++ b/src/Runner/Job.php @@ -22,6 +22,7 @@ class Job CODE_OK = 0, CODE_SKIP = 177, CODE_FAIL = 178, + CODE_TESTCASE = 179, CODE_ERROR = 255; /** waiting time between process activity check in microseconds */ diff --git a/src/Runner/TestHandler.php b/src/Runner/TestHandler.php index 8b0c2583..d9b9884e 100644 --- a/src/Runner/TestHandler.php +++ b/src/Runner/TestHandler.php @@ -179,16 +179,30 @@ private function initiateTestCase(Test $test, $foo, PhpInterpreter $interpreter) private function assessExitCode(Job $job, $code): ?Test { + $test = $job->getTest(); $code = (int) $code; if ($job->getExitCode() === Job::CODE_SKIP) { - $message = preg_match('#.*Skipped:\n(.*?)\z#s', $output = $job->getTest()->stdout, $m) + $message = preg_match('#.*Skipped:\n(.*?)\z#s', $output = $test->stdout, $m) ? $m[1] : $output; - return $job->getTest()->withResult(Test::SKIPPED, trim($message)); + return $test->withResult(Test::SKIPPED, trim($message)); + + } elseif ($job->getExitCode() === Job::CODE_TESTCASE) { + $methods = TestCase::parseOutput($test->stdout); + if ($methods === null) { + return $test->withResult(Test::FAILED, "Cannot read TestCaseRunner output in file '{$test->getFile()}'."); + } elseif (!$methods) { + return $test->withResult(Test::SKIPPED, "TestCaseRunner in file '{$test->getFile()}' does not contain any test."); + } + foreach ($methods as $method) { + $testVariety = $test->withArguments(['method' => $method]); + $this->runner->prepareTest($testVariety); + $this->runner->addJob(new Job($testVariety, $this->runner->getInterpreter(), $this->runner->getEnvironmentVariables())); + } } elseif ($job->getExitCode() !== $code) { $message = $job->getExitCode() !== Job::CODE_FAIL ? "Exited with error code {$job->getExitCode()} (expected $code)" : ''; - return $job->getTest()->withResult(Test::FAILED, trim($message . "\n" . $job->getTest()->stdout)); + return $test->withResult(Test::FAILED, trim($message . "\n" . $test->stdout)); } return null; }