Skip to content

Commit

Permalink
Dry-Run: Mock all module methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Naktibalda committed Jun 13, 2022
1 parent f53fa03 commit 13a1317
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 118 deletions.
123 changes: 121 additions & 2 deletions src/Codeception/Command/DryRun.php
Expand Up @@ -6,12 +6,22 @@
use Codeception\Event\SuiteEvent;
use Codeception\Event\TestEvent;
use Codeception\Events;
use Codeception\Lib\Generator\Actions;
use Codeception\Lib\ModuleContainer;
use Codeception\Module;
use Codeception\Step;
use Codeception\Stub;
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 PHPUnit\Framework\MockObject\MockObject;
use ReflectionIntersectionType;
use ReflectionMethod;
use ReflectionType;
use ReflectionUnionType;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
Expand Down Expand Up @@ -74,11 +84,13 @@ 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) {
$this->mockModule($module, $moduleContainer);
}
$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 Expand Up @@ -128,4 +140,111 @@ protected function dryRunTest(OutputInterface $output, EventDispatcher $dispatch
}
$output->writeln('');
}

/**
* @return Module&MockObject
*/
private function mockModule($moduleName, ModuleContainer $moduleContainer)
{
$module = $moduleContainer->getModule($moduleName);
$class = new \ReflectionClass($module);
$methodResults = [];
foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
if ($method->isConstructor()) {
continue;
}
$methodResults[$method->getName()] = $this->getDefaultResultForMethod($class, $method);
}

$moduleContainer->mock($moduleName, Stub::makeEmpty($module, $methodResults));
}

private function getDefaultResultForMethod(\ReflectionClass $class, ReflectionMethod $method)
{
if (PHP_VERSION_ID < 70000) {
return new Maybe();
}

$returnType = $method->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, $class);
return Stub::makeEmpty($typeName);
}



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());
}
}

private function getDefaultValueOfUnionType($returnType)
{
$unionTypes = $returnType->getTypes();
foreach ($unionTypes as $type) {
if ($type->isBuiltin()) {
return $this->getDefaultValueForBuiltinType($type);
}
}

return Stub::makeEmpty($unionTypes[0]);
}

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::makeEmpty($className);
}
}
118 changes: 2 additions & 116 deletions src/Codeception/Step.php
@@ -1,12 +1,10 @@
<?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 @@ -40,8 +38,6 @@ 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 @@ -286,11 +282,7 @@ public function run(ModuleContainer $container = null)
}

try {
if (self::$dryRun) {
return $this->getDefaultResultForMethod($activeModule, $this->action);
}

return call_user_func_array([$activeModule, $this->action], $this->arguments);
$res = call_user_func_array([$activeModule, $this->action], $this->arguments);
} catch (\Exception $e) {
if ($this->isTry) {
throw $e;
Expand All @@ -301,6 +293,7 @@ public function run(ModuleContainer $container = null)
}
throw $e;
}

return $res;
}

Expand Down Expand Up @@ -365,111 +358,4 @@ 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);
}
}

0 comments on commit 13a1317

Please sign in to comment.