diff --git a/src/Codeception/Command/DryRun.php b/src/Codeception/Command/DryRun.php index ec7e977021..3760887a7a 100644 --- a/src/Codeception/Command/DryRun.php +++ b/src/Codeception/Command/DryRun.php @@ -6,16 +6,15 @@ use Codeception\Event\SuiteEvent; use Codeception\Event\TestEvent; use Codeception\Events; +use Codeception\Step; use Codeception\Subscriber\Bootstrap as BootstrapLoader; use Codeception\Subscriber\Console as ConsolePrinter; use Codeception\SuiteManager; use Codeception\Test\Interfaces\ScenarioDriven; use Codeception\Test\Test; -use Codeception\Util\Maybe; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\EventDispatcher; @@ -75,13 +74,11 @@ public function execute(InputInterface $input, OutputInterface $output) $dispatcher->addSubscriber(new BootstrapLoader()); $suiteManager = new SuiteManager($dispatcher, $suite, $settings); - $moduleContainer = $suiteManager->getModuleContainer(); - foreach (Configuration::modules($settings) as $module) { - $moduleContainer->mock($module, new Maybe()); - } $suiteManager->loadTests($test); $tests = $suiteManager->getSuite()->tests(); + Step::enableDryRun(); + $this->dispatch($dispatcher, Events::SUITE_INIT, new SuiteEvent($suiteManager->getSuite(), null, $settings)); $this->dispatch($dispatcher, Events::SUITE_BEFORE, new SuiteEvent($suiteManager->getSuite(), null, $settings)); foreach ($tests as $test) { diff --git a/src/Codeception/Lib/Generator/Actions.php b/src/Codeception/Lib/Generator/Actions.php index eaa98db21c..981fefed1b 100644 --- a/src/Codeception/Lib/Generator/Actions.php +++ b/src/Codeception/Lib/Generator/Actions.php @@ -253,7 +253,7 @@ private function stringifyType(\ReflectionType $type, \ReflectionClass $moduleCl return sprintf( '%s%s', (PHP_VERSION_ID >= 70100 && $type->allowsNull() && $returnTypeString !== 'mixed') ? '?' : '', - $this->stringifyNamedType($type, $moduleClass) + self::stringifyNamedType($type, $moduleClass) ); } @@ -267,7 +267,7 @@ private function stringifyNamedTypes(array $types, \ReflectionClass $moduleClass { $strings = []; foreach ($types as $type) { - $strings []= $this->stringifyNamedType($type, $moduleClass); + $strings []= self::stringifyNamedType($type, $moduleClass); } return implode($separator, $strings); @@ -278,7 +278,7 @@ private function stringifyNamedTypes(array $types, \ReflectionClass $moduleClass * @return string * @todo param is only \ReflectionNamedType in Codeception 5 */ - private function stringifyNamedType($type, \ReflectionClass $moduleClass) + public static function stringifyNamedType($type, \ReflectionClass $moduleClass) { if (PHP_VERSION_ID < 70100) { $typeName = (string)$type; diff --git a/src/Codeception/Step.php b/src/Codeception/Step.php index 035d1ed7d1..db2734d018 100644 --- a/src/Codeception/Step.php +++ b/src/Codeception/Step.php @@ -1,10 +1,12 @@ action = $action; @@ -282,7 +286,11 @@ public function run(ModuleContainer $container = null) } try { - $res = call_user_func_array([$activeModule, $this->action], $this->arguments); + if (self::$dryRun) { + return $this->getDefaultResultForMethod($activeModule, $this->action); + } + + return call_user_func_array([$activeModule, $this->action], $this->arguments); } catch (\Exception $e) { if ($this->isTry) { throw $e; @@ -357,4 +365,111 @@ public function getPrefix() { return $this->prefix . ' '; } + + public static function enableDryRun() + { + self::$dryRun = true; + } + + private function getDefaultResultForMethod(Module $module, $method) + { + if (PHP_VERSION_ID < 70000) { + return new Maybe(); + } + + $reflection = new \ReflectionMethod($module, $method); + $returnType = $reflection->getReturnType(); + + if ($returnType === null || $returnType->allowsNull()) { + return null; + } + + if ($returnType instanceof \ReflectionUnionType) { + return $this->getDefaultValueOfUnionType($returnType); + } + if ($returnType instanceof \ReflectionIntersectionType) { + return $this->returnDefaultValueForIntersectionType($returnType); + } + + if (PHP_VERSION_ID >= 70100 && $returnType->isBuiltin()) { + return $this->getDefaultValueForBuiltinType($returnType); + } + + $typeName = Actions::stringifyNamedType($returnType, new \ReflectionClass($module)); + return Stub::makeEmpty($typeName); + } + + /** + * @param \ReflectionType $returnType + * @return array|false|float|int|resource|string|null + * @throws \Exception + */ + private function getDefaultValueForBuiltinType(\ReflectionType $returnType) + { + switch ($returnType->getName()) { + case 'mixed': + case 'void': + return null; + case 'string': + return ''; + case 'int': + return 0; + case 'float': + return 0.0; + case 'bool': + return false; + case 'array': + return []; + case 'resource': + return fopen('data://text/plain;base64,', 'r'); + default: + throw new \Exception('Unsupported return type ' . $returnType->getName()); + } + } + + /** + * @param $returnType + * @return array|callable|false|float|int|mixed|object|MockObject|resource|string|null + * @throws \Exception + */ + private function getDefaultValueOfUnionType($returnType) + { + $unionTypes = $returnType->getTypes(); + foreach ($unionTypes as $type) { + if ($type->isBuiltin()) { + return $this->getDefaultValueForBuiltinType($type); + } + } + + return Stub::makeEmpty($unionTypes[0]); + } + + /** + * @param \ReflectionIntersectionType $returnType + * @return \#g#F\Codeception\uniqid|\#π(#g#F\Codeception\uniqid)("anonymous_class_")|mixed|object|MockObject|string + * @throws \ReflectionException + */ + private function returnDefaultValueForIntersectionType(\ReflectionIntersectionType $returnType) + { + $extends = null; + $implements = []; + foreach ($returnType->getTypes() as $type) { + if (class_exists($type)) { + $extends = $type; + } else { + $implements [] = $type; + } + } + $className = uniqid('anonymous_class_'); + $code = "abstract class $className"; + if ($extends !== null) { + $code .= " extends \\$extends"; + } + if (count($implements) > 0) { + $code .= ' implements ' . implode(', ', $implements); + } + $code .= ' {}'; + eval($code); + return Stub::constructEmpty($className); + } } diff --git a/tests/cli/DryRunCest.php b/tests/cli/DryRunCest.php index 1360b4350a..3e092ba334 100644 --- a/tests/cli/DryRunCest.php +++ b/tests/cli/DryRunCest.php @@ -1,4 +1,5 @@ seeInShellOutput('INCOMPLETE'); $I->seeInShellOutput('Step definition for `I have only idea of what\'s going on here` not found'); } + + public function runTestsWithTypedHelper(CliGuy $I) + { + if (PHP_VERSION_ID < 70100) { + $I->markTestSkipped('Requires PHP 8.1'); + } + + $I->amInPath(\codecept_data_dir('typed_helper')); + $I->executeCommand('dry-run unit --no-ansi'); + $I->seeInShellOutput('print comment'); + $I->seeInShellOutput('I get int'); + $I->seeInShellOutput('I get dom document'); + $I->seeInShellOutput('I see something'); + } } diff --git a/tests/data/typed_helper/codeception.yml b/tests/data/typed_helper/codeception.yml new file mode 100644 index 0000000000..23e6178faa --- /dev/null +++ b/tests/data/typed_helper/codeception.yml @@ -0,0 +1,9 @@ +paths: + tests: tests + output: tests/_output + data: tests/_data + support: tests/_support + envs: tests/_envs +actor_suffix: Tester +settings: + colors: false \ No newline at end of file diff --git a/tests/data/typed_helper/tests/_output/.gitignore b/tests/data/typed_helper/tests/_output/.gitignore new file mode 100644 index 0000000000..c96a04f008 --- /dev/null +++ b/tests/data/typed_helper/tests/_output/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/tests/data/typed_helper/tests/_support/Helper/Unit.php b/tests/data/typed_helper/tests/_support/Helper/Unit.php new file mode 100644 index 0000000000..8c5918a1cd --- /dev/null +++ b/tests/data/typed_helper/tests/_support/Helper/Unit.php @@ -0,0 +1,43 @@ +comment('print comment'); + $I->getInt(); + $I->getDomDocument(); + $I->seeSomething(); + $I->getUnion(); + $I->getIntersection(); + $I->getSelf(); + $I->getParent(); + } +}