Skip to content

Commit

Permalink
Make dry-run work with module methods having return types
Browse files Browse the repository at this point in the history
  • Loading branch information
Naktibalda committed Jun 10, 2022
1 parent 1316cdc commit f53fa03
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 10 deletions.
9 changes: 3 additions & 6 deletions src/Codeception/Command/DryRun.php
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions src/Codeception/Lib/Generator/Actions.php
Expand Up @@ -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)
);
}

Expand All @@ -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);
Expand All @@ -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;
Expand Down
117 changes: 116 additions & 1 deletion src/Codeception/Step.php
@@ -1,10 +1,12 @@
<?php
namespace Codeception;

use Codeception\Lib\Generator\Actions;
use Codeception\Lib\ModuleContainer;
use Codeception\Step\Argument\FormattedOutput;
use Codeception\Step\Meta as MetaStep;
use Codeception\Util\Locator;
use Codeception\Util\Maybe;
use PHPUnit\Framework\MockObject\MockObject;

abstract class Step
Expand Down Expand Up @@ -38,6 +40,8 @@ abstract class Step
protected $failed = false;
protected $isTry = false;

private static $dryRun = false;

public function __construct($action, array $arguments = [])
{
$this->action = $action;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
15 changes: 15 additions & 0 deletions tests/cli/DryRunCest.php
@@ -1,4 +1,5 @@
<?php

class DryRunCest
{
public function _before(CliGuy $I)
Expand All @@ -24,4 +25,18 @@ public function runFeature(CliGuy $I)
$I->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 < 80100) {
$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');
}
}
9 changes: 9 additions & 0 deletions 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
2 changes: 2 additions & 0 deletions tests/data/typed_helper/tests/_output/.gitignore
@@ -0,0 +1,2 @@
*
!.gitignore
43 changes: 43 additions & 0 deletions tests/data/typed_helper/tests/_support/Helper/Unit.php
@@ -0,0 +1,43 @@
<?php
namespace Helper;

// here you can define custom actions
// all public methods declared in helper class will be available in $I

class Unit extends \Codeception\Module
{
public function seeSomething(): void
{

}

public function getInt(): int
{
throw new \RuntimeException(__METHOD__ . ' should not be executed');
}

public function getDomDocument(): \DOMDocument
{
throw new \RuntimeException(__METHOD__ . ' should not be executed');
}

public function getUnion(): int|\DOMDocument
{
throw new \RuntimeException(__METHOD__ . ' should not be executed');
}

public function getIntersection(): \Iterator&\Countable&\DOMDocument
{
throw new \RuntimeException(__METHOD__ . ' should not be executed');
}

public function getSelf(): self
{
throw new \RuntimeException(__METHOD__ . ' should not be executed');
}

public function getParent(): parent
{
throw new \RuntimeException(__METHOD__ . ' should not be executed');
}
}
6 changes: 6 additions & 0 deletions tests/data/typed_helper/tests/_support/UnitTester.php
@@ -0,0 +1,6 @@
<?php

class UnitTester extends \Codeception\Actor
{
use _generated\UnitTesterActions;
}
2 changes: 2 additions & 0 deletions tests/data/typed_helper/tests/_support/_generated/.gitignore
@@ -0,0 +1,2 @@
*
!.gitignore
4 changes: 4 additions & 0 deletions tests/data/typed_helper/tests/unit.suite.yml
@@ -0,0 +1,4 @@
actor: UnitTester
modules:
enabled:
- \Helper\Unit
16 changes: 16 additions & 0 deletions tests/data/typed_helper/tests/unit/UseTypedHelperCest.php
@@ -0,0 +1,16 @@
<?php

class UseTypedHelperCest
{
public function executeActions(UnitTester $I)
{
$I->comment('print comment');
$I->getInt();
$I->getDomDocument();
$I->seeSomething();
$I->getUnion();
$I->getIntersection();
$I->getSelf();
$I->getParent();
}
}

0 comments on commit f53fa03

Please sign in to comment.