Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Allow mutation of functions #1483

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 12 additions & 20 deletions src/PhpParser/Visitor/ReflectionVisitor.php
Expand Up @@ -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;

Expand Down Expand Up @@ -82,21 +81,14 @@ 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;
}

$isInsideFunction = $this->isInsideFunction($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)) {
Expand All @@ -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);
}

Expand Down Expand Up @@ -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
Expand Down
44 changes: 41 additions & 3 deletions src/TestFramework/AbstractTestFrameworkAdapter.php
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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
);
}

Expand Down Expand Up @@ -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
*
Expand Down
8 changes: 8 additions & 0 deletions src/TestFramework/Config/MutationConfigBuilder.php
Expand Up @@ -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 = '';
Expand Down
50 changes: 47 additions & 3 deletions src/TestFramework/PhpUnit/Config/Builder/MutationConfigBuilder.php
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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) {
Expand All @@ -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
<?php

if (function_exists('proc_nice')) {
Expand All @@ -165,7 +196,20 @@ private function createCustomAutoloadWithInterceptor(
require_once '%s';

PHP
,
:
<<<'PHP'
<?php

if (function_exists('proc_nice')) {
proc_nice(1);
}
%s
require_once '%s';

PHP;

return sprintf(
$content,
$this->getInterceptorFileContent($interceptorPath, $originalFilePath, $mutantFilePath),
$originalAutoloadFile
);
Expand Down