diff --git a/Clockwork/Clockwork.php b/Clockwork/Clockwork.php index a77e5b2f..7afaeffa 100644 --- a/Clockwork/Clockwork.php +++ b/Clockwork/Clockwork.php @@ -64,8 +64,12 @@ public function resolveRequest() // Resolve the current request as a "command" type request with command-specific data public function resolveAsCommand($name, $exitCode = null, $arguments = [], $options = [], $argumentsDefaults = [], $optionsDefaults = [], $output = null) { - $this->resolveRequest(); + return $this->resolveRequest()->asCommand($name, $exitCode, $arguments, $options, $argumentsDefaults, $optionsDefaults, $output); + } + // Set the current request as a "command" type request with command-specific data + public function asCommand($name, $exitCode = null, $arguments = [], $options = [], $argumentsDefaults = [], $optionsDefaults = [], $output = null) + { $this->request->type = RequestType::COMMAND; $this->request->commandName = $name; $this->request->commandArguments = $arguments; @@ -81,8 +85,12 @@ public function resolveAsCommand($name, $exitCode = null, $arguments = [], $opti // Resolve the current request as a "queue-job" type request with queue-job-specific data public function resolveAsQueueJob($name, $description = null, $status = 'processed', $payload = [], $queue = null, $connection = null, $options = []) { - $this->resolveRequest(); + return $this->resolveRequest()->asQueueJob($name, $description, $status, $payload, $queue, $connection, $options); + } + // Set the current request as a "queue-job" type request with queue-job-specific data + public function asQueueJob($name, $description = null, $status = 'processed', $payload = [], $queue = null, $connection = null, $options = []) + { $this->request->type = RequestType::QUEUE_JOB; $this->request->jobName = $name; $this->request->jobDescription = $description; @@ -99,8 +107,12 @@ public function resolveAsQueueJob($name, $description = null, $status = 'process // message in case of failure and array of ran asserts public function resolveAsTest($name, $status = 'passed', $statusMessage = null, $asserts = []) { - $this->resolveRequest(); + return $this->resolveRequest()->asTest($name, $status, $statusMessage, $asserts); + } + // Set the current request as a "test" type request with test-specific data + public function asTest($name, $status = 'passed', $statusMessage = null, $asserts = []) + { $this->request->type = RequestType::TEST; $this->request->testName = $name; $this->request->testStatus = $status; diff --git a/Clockwork/Support/Laravel/Tests/ClockworkExtension.php b/Clockwork/Support/Laravel/Tests/ClockworkExtension.php index e90e3fe4..6e7f8244 100644 --- a/Clockwork/Support/Laravel/Tests/ClockworkExtension.php +++ b/Clockwork/Support/Laravel/Tests/ClockworkExtension.php @@ -8,6 +8,7 @@ class ClockworkExtension implements Runner\Extension\Extension { public static $asserts = []; + public static $tests = []; public function bootstrap( TextUI\Configuration\Configuration $configuration, @@ -15,23 +16,27 @@ public function bootstrap( Runner\Extension\ParameterCollection $parameters ): void { $subscribers = array_filter([ + new class implements Event\Test\PreparedSubscriber { - public function notify($event): void { ClockworkExtension::$asserts = []; } + public function notify($event): void { ClockworkExtension::$asserts = []; ClockworkExtension::prepareTest($event->test()->id()); } }, new class implements Event\Test\ErroredSubscriber { - public function notify($event): void { ClockworkExtension::recordTest('error', $event->throwable()->message()); } + public function notify($event): void { ClockworkExtension::finishTest($event->test()->id(), 'error', $event->throwable()->message()); } }, new class implements Event\Test\FailedSubscriber { - public function notify($event): void { ClockworkExtension::recordTest('failed', $event->throwable()->message()); } + public function notify($event): void { ClockworkExtension::finishTest($event->test()->id(), 'failed', $event->throwable()->message()); } }, new class implements Event\Test\MarkedIncompleteSubscriber { - public function notify($event): void { ClockworkExtension::recordTest('incomplete', $event->throwable()->message()); } + public function notify($event): void { ClockworkExtension::finishTest($event->test()->id(), 'incomplete', $event->throwable()->message()); } }, new class implements Event\Test\PassedSubscriber { - public function notify($event): void { ClockworkExtension::recordTest('passed'); } + public function notify($event): void { ClockworkExtension::finishTest($event->test()->id(), 'passed'); } }, new class implements Event\Test\SkippedSubscriber { - public function notify($event): void { ClockworkExtension::recordTest('skipped', $event->message()); } + public function notify($event): void { ClockworkExtension::finishTest($event->test()->id(), 'skipped', $event->message()); } + }, + new class implements Event\Test\FinishedSubscriber { + public function notify($event): void { ClockworkExtension::storeTest($event->test()->id()); } }, interface_exists(Event\Test\AssertionSucceededSubscriber::class) ? new class implements Event\Test\AssertionSucceededSubscriber { public function notify($event): void { ClockworkExtension::recordAssertion(true); } @@ -44,8 +49,10 @@ public function notify($event): void { ClockworkExtension::recordAssertion(false $facade->registerSubscribers(...$subscribers); } - public static function recordTest($status, $message = null) + public static function prepareTest($id) { + if (static::isPrepared($id)) return; + $testCase = static::resolveTestCase(); if (! $testCase) return; @@ -54,19 +61,74 @@ public static function recordTest($status, $message = null) if (! $app) return; - if (! $app->make('clockwork.support')->isCollectingTests()) return; - if ($app->make('clockwork.support')->isTestFiltered($testCase->toString())) return; + $support = $app->make('clockwork.support'); + + if (! $support->isCollectingTests()) return; + if ($support->isTestFiltered($testCase->toString())) return; + + static::$tests[$id] = [ + 'clockwork' => $app->make('clockwork'), + 'name' => str_replace('__pest_evaluable_', '', $testCase->toString()), + 'status' => 'passed', + 'message' => null + ]; + + static::beforeApplicationDestroyed($testCase, function () use ($id) { + static::resolveTest($id); + }); + } + + public static function finishTest($id, $status, $message = null) + { + static::prepareTest($id); + + if (! static::isPrepared($id)) return; + + static::$tests[$id]['status'] = $status; + static::$tests[$id]['message'] = $message; + } + + public static function resolveTest($id) + { + if (! static::isPrepared($id) || static::isResolved($id)) return; + + static::$tests[$id]['clockwork']->resolveRequest(); + static::$tests[$id]['resolved'] = true; + } - $app->make('clockwork') - ->resolveAsTest( - str_replace('__pest_evaluable_', '', $testCase->toString()), - $status, - $message, - static::$asserts - ) + public static function storeTest($id) + { + if (! static::isPrepared($id)) return; + + static::resolveTest($id); + + $test = static::$tests[$id]; + + unset(static::$tests[$id]); + + $test['clockwork'] + ->asTest($test['name'], $test['status'], $test['message'], static::$asserts) ->storeRequest(); } - + + protected static function isPrepared($id) + { + return isset(static::$tests[$id]); + } + + protected static function isResolved($id) + { + return isset(static::$tests[$id]['resolved']); + } + + protected static function beforeApplicationDestroyed($testCase, $callback) + { + // Call the protected beforeApplicationDestroyed method by binding a closure to the object + (function ($callback) { + $this->beforeApplicationDestroyed($callback); + })->call($testCase, $callback); + } + public static function recordAssertion($passed = true) { $trace = StackTrace::get([ 'arguments' => true, 'limit' => 10 ]); @@ -93,17 +155,11 @@ protected static function resolveTestCase() protected static function resolveApp($testCase) { - $reflectionClass = new \ReflectionClass($testCase); - - if ($reflectionClass->hasProperty('app')) { - $reflectionProperty = $reflectionClass->getProperty('app'); - $reflectionProperty->setAccessible(true); - - if ($reflectionProperty->getValue($testCase)) { - return $reflectionProperty->getValue($testCase); - } - } elseif (method_exists($testCase, 'createApplication')) { - return $testCase->createApplication(); - } + if (! property_exists($testCase, 'app')) return; + + // Retrieve the protected app property by binding a closure to the object + return (function () { + return $this->app; + })->call($testCase); } }