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

Add support for Pest test framework #1516

Merged
merged 8 commits into from May 13, 2021
Merged
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
5 changes: 5 additions & 0 deletions devTools/phpstan-src-baseline.neon
Expand Up @@ -305,6 +305,11 @@ parameters:
count: 1
path: ../src/TestFramework/Coverage/XmlReport/XPathFactory.php

-
message: "#^Method Infection\\\\TestFramework\\\\PhpUnit\\\\Adapter\\\\PestAdapterFactory\\:\\:create\\(\\) has parameter \\$sourceDirectories with no value type specified in iterable type array\\.$#"
count: 1
path: ../src/TestFramework/PhpUnit/Adapter/PestAdapterFactory.php

-
message: "#^Only booleans are allowed in an if condition, int given\\.$#"
count: 3
Expand Down
21 changes: 20 additions & 1 deletion src/TestFramework/Factory.php
Expand Up @@ -41,6 +41,7 @@
use Infection\Configuration\Configuration;
use Infection\FileSystem\Finder\TestFrameworkFinder;
use Infection\TestFramework\Config\TestFrameworkConfigLocatorInterface;
use Infection\TestFramework\PhpUnit\Adapter\PestAdapterFactory;
use Infection\TestFramework\PhpUnit\Adapter\PhpUnitAdapterFactory;
use InvalidArgumentException;
use function is_a;
Expand Down Expand Up @@ -105,7 +106,25 @@ public function create(string $adapterName, bool $skipCoverage): TestFrameworkAd
);
}

$availableTestFrameworks = [TestFrameworkTypes::PHPUNIT];
if ($adapterName === TestFrameworkTypes::PEST) {
$pestConfigPath = $this->configLocator->locate(TestFrameworkTypes::PHPUNIT);

return PestAdapterFactory::create(
$this->testFrameworkFinder->find(
TestFrameworkTypes::PEST,
(string) $this->infectionConfig->getPhpUnit()->getCustomPath()
),
$this->tmpDir,
$pestConfigPath,
(string) $this->infectionConfig->getPhpUnit()->getConfigDir(),
$this->jUnitFilePath,
$this->projectDir,
$this->infectionConfig->getSourceDirectories(),
$skipCoverage
);
}

$availableTestFrameworks = [TestFrameworkTypes::PHPUNIT, TestFrameworkTypes::PEST];

foreach ($this->installedExtensions as $installedExtension) {
$factory = $installedExtension['extra']['class'];
Expand Down
122 changes: 122 additions & 0 deletions src/TestFramework/PhpUnit/Adapter/PestAdapter.php
@@ -0,0 +1,122 @@
<?php
/**
* This code is licensed under the BSD 3-Clause License.
*
* Copyright (c) 2017, Maks Rafalko
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

declare(strict_types=1);

namespace Infection\TestFramework\PhpUnit\Adapter;

use Infection\AbstractTestFramework\MemoryUsageAware;
use Infection\AbstractTestFramework\TestFrameworkAdapter;
use Infection\TestFramework\ProvidesInitialRunOnlyOptions;
use function Safe\preg_match;
use function Safe\sprintf;

/**
* @internal
*/
final class PestAdapter implements MemoryUsageAware, ProvidesInitialRunOnlyOptions, TestFrameworkAdapter
{
private const NAME = 'Pest';

private PhpUnitAdapter $phpUnitAdapter;

public function __construct(PhpUnitAdapter $phpUnitAdapter)
{
$this->phpUnitAdapter = $phpUnitAdapter;
}

public function getName(): string
{
return self::NAME;
}

public function testsPass(string $output): bool
{
// Tests: 7 failed
if (preg_match('/Tests:\s+(.*?)(\d+\sfailed)/i', $output) === 1) {
return false;
}

// Tests: 4 passed
$isOk = preg_match('/Tests:\s+(.*?)(\d+\spassed)/', $output) === 1;

// Tests: 1 risked
$isOkRisked = preg_match('/Tests:\s+(.*?)(\d+\srisked)/', $output) === 1;

return $isOk || $isOkRisked;
}

public function hasJUnitReport(): bool
{
return $this->phpUnitAdapter->hasJUnitReport();
}

public function getInitialTestRunCommandLine(string $extraOptions, array $phpExtraArgs, bool $skipCoverage): array
{
return $this->phpUnitAdapter->getInitialTestRunCommandLine($extraOptions, $phpExtraArgs, $skipCoverage);
}

public function getMutantCommandLine(array $coverageTests, string $mutatedFilePath, string $mutationHash, string $mutationOriginalFilePath, string $extraOptions): array
{
return $this->phpUnitAdapter->getMutantCommandLine(
$coverageTests,
$mutatedFilePath,
$mutationHash,
$mutationOriginalFilePath,
sprintf('--colors=never %s', $extraOptions)
);
}

public function getVersion(): string
{
return $this->phpUnitAdapter->getVersion();
}

public function getInitialTestsFailRecommendations(string $commandLine): string
{
return $this->phpUnitAdapter->getInitialTestsFailRecommendations($commandLine);
}

public function getMemoryUsed(string $output): float
{
return $this->phpUnitAdapter->getMemoryUsed($output);
}

/**
* @return string[]
*/
public function getInitialRunOnlyOptions(): array
{
return $this->phpUnitAdapter->getInitialRunOnlyOptions();
}
}
117 changes: 117 additions & 0 deletions src/TestFramework/PhpUnit/Adapter/PestAdapterFactory.php
@@ -0,0 +1,117 @@
<?php
/**
* This code is licensed under the BSD 3-Clause License.
*
* Copyright (c) 2017, Maks Rafalko
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

declare(strict_types=1);

namespace Infection\TestFramework\PhpUnit\Adapter;

use Infection\AbstractTestFramework\TestFrameworkAdapter;
use Infection\AbstractTestFramework\TestFrameworkAdapterFactory;
use Infection\Config\ValueProvider\PCOVDirectoryProvider;
use Infection\TestFramework\CommandLineBuilder;
use Infection\TestFramework\Coverage\JUnit\JUnitTestCaseSorter;
use Infection\TestFramework\PhpUnit\CommandLine\ArgumentsAndOptionsBuilder;
use Infection\TestFramework\PhpUnit\Config\Builder\InitialConfigBuilder;
use Infection\TestFramework\PhpUnit\Config\Builder\MutationConfigBuilder;
use Infection\TestFramework\PhpUnit\Config\Path\PathReplacer;
use Infection\TestFramework\PhpUnit\Config\XmlConfigurationManipulator;
use Infection\TestFramework\PhpUnit\Config\XmlConfigurationVersionProvider;
use Infection\TestFramework\VersionParser;
use function Safe\file_get_contents;
use Symfony\Component\Filesystem\Filesystem;
use Webmozart\Assert\Assert;

/**
* @internal
*/
final class PestAdapterFactory implements TestFrameworkAdapterFactory
{
public static function create(
string $testFrameworkExecutable,
string $tmpDir,
string $testFrameworkConfigPath,
?string $testFrameworkConfigDir,
string $jUnitFilePath,
string $projectDir,
array $sourceDirectories,
bool $skipCoverage
): TestFrameworkAdapter {
Assert::string($testFrameworkConfigDir, 'Config dir is not allowed to be `null` for the Pest adapter');

$testFrameworkConfigContent = file_get_contents($testFrameworkConfigPath);

$configManipulator = new XmlConfigurationManipulator(
new PathReplacer(
new Filesystem(),
$testFrameworkConfigDir
),
$testFrameworkConfigDir
);

$phpUnitAdapter = new PhpUnitAdapter(
$testFrameworkExecutable,
$tmpDir,
$jUnitFilePath,
new PCOVDirectoryProvider(),
new InitialConfigBuilder(
$tmpDir,
$testFrameworkConfigContent,
$configManipulator,
new XmlConfigurationVersionProvider(),
$sourceDirectories
),
new MutationConfigBuilder(
$tmpDir,
$testFrameworkConfigContent,
$configManipulator,
$projectDir,
new JUnitTestCaseSorter()
),
new ArgumentsAndOptionsBuilder(),
new VersionParser(),
new CommandLineBuilder()
);

return new PestAdapter($phpUnitAdapter);
}

public static function getAdapterName(): string
{
return 'pest';
}

public static function getExecutableName(): string
{
return 'pest';
}
}
Expand Up @@ -69,7 +69,7 @@ public static function create(
array $sourceDirectories,
bool $skipCoverage
): TestFrameworkAdapter {
Assert::string($testFrameworkConfigDir, 'Config dir is not allowed to be `null` for the phpunit adapter');
Assert::string($testFrameworkConfigDir, 'Config dir is not allowed to be `null` for the Pest adapter');

$testFrameworkConfigContent = file_get_contents($testFrameworkConfigPath);

Expand Down
2 changes: 2 additions & 0 deletions src/TestFramework/TestFrameworkTypes.php
Expand Up @@ -41,10 +41,12 @@
final class TestFrameworkTypes
{
public const PHPUNIT = 'phpunit';
public const PEST = 'pest';
public const PHPSPEC = 'phpspec';
public const CODECEPTION = 'codeception';

public const TYPES = [
self::PEST,
self::PHPUNIT,
self::PHPSPEC,
self::CODECEPTION,
Expand Down
27 changes: 27 additions & 0 deletions tests/e2e/PestTestFramework/README.md
@@ -0,0 +1,27 @@
# Pest Test Framework integration with Infection

* https://github.com/pestphp/pest/pull/291
* https://github.com/infection/infection/issues/1476

## Summary

This test ensures Pest is working correctly with Infection.

Cases:

- `ForPest.php` file is tested only by Pest
- `ForPhpUnit.php` file is tested only by PhpUnit
- `Calculator.php` file is tested by Pest and PhpUnit

To manually check it, run:

```
git clone git@github.com:infection/infection.git
cd infection
git checkout feature/pest-adapter

cd tests/e2e/PestTestFramework
composer install

XDEBUG_MODE=coverage ../../../bin/infection --test-framework=pest --log-verbosity=all -s
```
15 changes: 15 additions & 0 deletions tests/e2e/PestTestFramework/composer.json
@@ -0,0 +1,15 @@
{
"require-dev": {
"pestphp/pest": "^1.2.0"
},
"autoload": {
"psr-4": {
"PestTestFramework\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"PestTestFramework\\Test\\": "tests/"
}
}
}
8 changes: 8 additions & 0 deletions tests/e2e/PestTestFramework/expected-output.txt
@@ -0,0 +1,8 @@
Total: 11

Killed: 9
Errored: 0
Escaped: 2
Timed Out: 0
Skipped: 0
Not Covered: 0
12 changes: 12 additions & 0 deletions tests/e2e/PestTestFramework/infection.json
@@ -0,0 +1,12 @@
{
"timeout": 25,
"source": {
"directories": [
"src"
]
},
"logs": {
"summary": "infection.log"
},
"tmpDir": "."
}
18 changes: 18 additions & 0 deletions tests/e2e/PestTestFramework/phpunit.xml
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/schema/9.2.xsd"
bootstrap="./vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>

<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>