From 8733749a0c1b9f09fbd4c2276f5a56d06b4e6d7a Mon Sep 17 00:00:00 2001 From: Gert de Pagter Date: Wed, 17 Feb 2021 16:27:26 +0100 Subject: [PATCH] Allow mutation of functions --- src/PhpParser/Visitor/ReflectionVisitor.php | 32 +++++------- .../AbstractTestFrameworkAdapter.php | 44 ++++++++++++++-- .../Config/MutationConfigBuilder.php | 8 +++ .../Config/Builder/MutationConfigBuilder.php | 50 +++++++++++++++++-- 4 files changed, 108 insertions(+), 26 deletions(-) diff --git a/src/PhpParser/Visitor/ReflectionVisitor.php b/src/PhpParser/Visitor/ReflectionVisitor.php index 5b0fd6c34..f3cff7c12 100644 --- a/src/PhpParser/Visitor/ReflectionVisitor.php +++ b/src/PhpParser/Visitor/ReflectionVisitor.php @@ -42,7 +42,6 @@ use Infection\Reflection\CoreClassReflection; use Infection\Reflection\NullReflection; use PhpParser\Node; -use PhpParser\NodeTraverser; use PhpParser\NodeVisitorAbstract; use Webmozart\Assert\Assert; @@ -82,12 +81,7 @@ public function enterNode(Node $node) $this->classScopeStack[] = $this->getClassReflectionForNode($node); } - // No need to traverse outside of classes - if (count($this->classScopeStack) === 0) { - return null; - } - - if ($node instanceof Node\Stmt\ClassMethod) { + if ($node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) { $this->methodName = $node->name->name; } @@ -95,8 +89,6 @@ public function enterNode(Node $node) if ($isInsideFunction) { $node->setAttribute(self::IS_INSIDE_FUNCTION_KEY, true); - } elseif ($node instanceof Node\Stmt\Function_) { - return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN; } if ($this->isPartOfFunctionSignature($node)) { @@ -105,11 +97,17 @@ public function enterNode(Node $node) if ($this->isFunctionLikeNode($node)) { $this->functionScopeStack[] = $node; - $node->setAttribute(self::REFLECTION_CLASS_KEY, $this->classScopeStack[count($this->classScopeStack) - 1]); + + if ($this->classScopeStack !== []) { + $node->setAttribute(self::REFLECTION_CLASS_KEY, $this->classScopeStack[count($this->classScopeStack) - 1]); + } $node->setAttribute(self::FUNCTION_NAME, $this->methodName); } elseif ($isInsideFunction) { $node->setAttribute(self::FUNCTION_SCOPE_KEY, $this->functionScopeStack[count($this->functionScopeStack) - 1]); - $node->setAttribute(self::REFLECTION_CLASS_KEY, $this->classScopeStack[count($this->classScopeStack) - 1]); + + if ($this->classScopeStack !== []) { + $node->setAttribute(self::REFLECTION_CLASS_KEY, $this->classScopeStack[count($this->classScopeStack) - 1]); + } $node->setAttribute(self::FUNCTION_NAME, $this->methodName); } @@ -168,15 +166,9 @@ private function isInsideFunction(Node $node): bool private function isFunctionLikeNode(Node $node): bool { - if ($node instanceof Node\Stmt\ClassMethod) { - return true; - } - - if ($node instanceof Node\Expr\Closure) { - return true; - } - - return false; + return $node instanceof Node\Stmt\ClassMethod + || $node instanceof Node\Expr\Closure + || $node instanceof Node\Stmt\Function_; } private function getClassReflectionForNode(Node\Stmt\ClassLike $node): ClassReflection diff --git a/src/TestFramework/AbstractTestFrameworkAdapter.php b/src/TestFramework/AbstractTestFrameworkAdapter.php index 9f8fa0336..15a01238f 100644 --- a/src/TestFramework/AbstractTestFrameworkAdapter.php +++ b/src/TestFramework/AbstractTestFrameworkAdapter.php @@ -40,6 +40,7 @@ use Infection\TestFramework\Config\InitialConfigBuilder; use Infection\TestFramework\Config\MutationConfigBuilder; use function Safe\sprintf; +use function str_ends_with; use Symfony\Component\Process\Process; /** @@ -108,15 +109,37 @@ public function getMutantCommandLine( string $mutationOriginalFilePath, string $extraOptions ): array { - return $this->getCommandLine( + if (str_ends_with($this->testFrameworkExecutable, '.bat')) { + return $this->getCommandLine( + $this->buildMutationConfigFile( + $tests, + $mutantFilePath, + $mutationHash, + $mutationOriginalFilePath + ), + $extraOptions, + [] + ); + } + + $frameworkArgs = $this->argumentsAndOptionsBuilder->build( $this->buildMutationConfigFile( $tests, $mutantFilePath, $mutationHash, $mutationOriginalFilePath ), - $extraOptions, - [] + $extraOptions + ); + + return $this->commandLineBuilder->build( + $this->buildMutationExecutableFile($tests, + $mutantFilePath, + $mutationHash, + $mutationOriginalFilePath + ), + [], + $frameworkArgs ); } @@ -152,6 +175,21 @@ protected function buildMutationConfigFile( ); } + protected function buildMutationExecutableFile( + array $tests, + string $mutantFilePath, + string $mutationHash, + string $mutationOriginalFilePath + ): string { + return $this->mutationConfigBuilder->buildExecutable( + $tests, + $mutantFilePath, + $mutationHash, + $mutationOriginalFilePath, + $this->testFrameworkExecutable + ); + } + /** * @param string[] $phpExtraArgs * diff --git a/src/TestFramework/Config/MutationConfigBuilder.php b/src/TestFramework/Config/MutationConfigBuilder.php index 15daf7fc8..bf5313955 100644 --- a/src/TestFramework/Config/MutationConfigBuilder.php +++ b/src/TestFramework/Config/MutationConfigBuilder.php @@ -58,6 +58,14 @@ abstract public function build( string $mutationOriginalFilePath ): string; + abstract public function buildExecutable( + array $tests, + string $mutantFilePath, + string $mutationHash, + string $mutationOriginalFilePath, + string $originalExecutable + ): string; + protected function getInterceptorFileContent(string $interceptorPath, string $originalFilePath, string $mutantFilePath): string { $infectionPhar = ''; diff --git a/src/TestFramework/PhpUnit/Config/Builder/MutationConfigBuilder.php b/src/TestFramework/PhpUnit/Config/Builder/MutationConfigBuilder.php index bc9adb3a7..f4dc8ad86 100644 --- a/src/TestFramework/PhpUnit/Config/Builder/MutationConfigBuilder.php +++ b/src/TestFramework/PhpUnit/Config/Builder/MutationConfigBuilder.php @@ -46,6 +46,7 @@ use Infection\TestFramework\SafeDOMXPath; use function Safe\file_put_contents; use function Safe\sprintf; +use Symfony\Component\Filesystem\Filesystem; use Webmozart\Assert\Assert; /** @@ -131,6 +132,34 @@ public function build( return $path; } + public function buildExecutable( + array $tests, + string $mutantFilePath, + string $mutationHash, + string $mutationOriginalFilePath, + string $originalExecutable + ): string { + $customAutoloadFilePath = sprintf( + '%s/interceptor.execute.%s.infection.php', + $this->tmpDir, + $mutationHash + ); + + file_put_contents( + $customAutoloadFilePath, + $this->createCustomAutoloadWithInterceptor( + $mutationOriginalFilePath, + $mutantFilePath, + $originalExecutable, + true + ) + ); + + \Safe\chmod($customAutoloadFilePath, 0777); + + return $customAutoloadFilePath; + } + private function getDom(): DOMDocument { if ($this->dom !== null) { @@ -150,12 +179,14 @@ private function getDom(): DOMDocument private function createCustomAutoloadWithInterceptor( string $originalFilePath, string $mutantFilePath, - string $originalAutoloadFile + string $originalAutoloadFile, + bool $shebang = false ): string { $interceptorPath = IncludeInterceptor::LOCATION; - return sprintf( + $content = $shebang ? <<<'PHP' +#!/usr/bin/env php getInterceptorFileContent($interceptorPath, $originalFilePath, $mutantFilePath), $originalAutoloadFile );