Compare commits

...

17 Commits

Author SHA1 Message Date
709ecb1ba2 chore: adjusts tests 2025-01-19 17:35:09 +00:00
6afb36519d release: 3.7.2 2025-01-19 17:16:25 +00:00
150bb9478d docs: adjusts sponsors 2025-01-08 01:09:20 +00:00
bf3178473d release: 3.7.1 2024-12-12 11:52:01 +00:00
d2eb94d723 chore: bumps phpunit and paratest 2024-12-12 11:50:43 +00:00
9688b83a3d release: 3.7.0 2024-12-10 11:54:49 +00:00
675372c794 chore: fixes types 2024-12-10 11:54:42 +00:00
c18636b3d5 chore: adds phpunit 11.5.0 support 2024-12-10 11:53:59 +00:00
145294a4a3 chore: style 2024-12-01 23:55:15 +00:00
c2cabaeae6 chore: fixes test suite 2024-12-01 23:16:34 +00:00
918a8fc169 release: 3.6.0 2024-12-01 22:46:00 +00:00
5d32dd0641 feat: option to coverage 2024-12-01 22:45:31 +00:00
982353fb38 release: 3.5.2 2024-12-01 21:28:14 +00:00
2eefa8b88d chore: adds phpunit 11.4.4 support 2024-12-01 21:26:11 +00:00
787d5492ac chore: ignores temporary file 2024-12-01 20:41:34 +00:00
06a0bd9b0b Bumps dependencies 2024-11-21 10:46:27 +00:00
91afc81222 Updates sponsors 2024-11-09 14:20:12 +00:00
23 changed files with 145 additions and 43 deletions

2
.gitignore vendored
View File

@ -12,3 +12,5 @@ coverage.xml
*.swp
*.swo
.vscode/
.STREAM.md

View File

@ -22,22 +22,27 @@
We cannot thank our sponsors enough for their incredible support in funding Pest's development. Their contributions have been instrumental in making Pest the best it can be. For those who are interested in becoming a sponsor, please visit Nuno Maduro's Sponsor page at **[github.com/sponsors/nunomaduro](https://github.com/sponsors/nunomaduro)**.
### Platinum Sponsors
- **[LaraJobs](https://larajobs.com)**
- **[Brokerchooser](https://brokerchooser.com)**
- **[Forge](https://forge.laravel.com)**
- **[Spatie](https://spatie.be)**
- **[Worksome](https://www.worksome.com/)**
- **[Laracasts](https://laracasts.com/?ref=pestphp)**
### Gold Sponsors
- **[CodeRabbit](https://coderabbit.ai/?ref=pestphp)**
- **[LaraJobs](https://larajobs.com/?ref=pestphp)**
- **[Brokerchooser](https://brokerchooser.com/?ref=pestphp)**
- **[Forge](https://forge.laravel.com/?ref=pestphp)**
### Premium Sponsors
- [Akaunting](https://akaunting.com/?ref=pestphp)
- [Codecourse](https://codecourse.com/?ref=pestphp)
- [DocuWriter.ai](https://www.docuwriter.ai/?ref=pestphp)
- [Laracasts](https://laracasts.com/?ref=pestphp)
- [Localazy](https://localazy.com/?ref=pestphp)
- [Route4Me](https://www.route4me.com/?ref=pestphp)
- [Spatie](https://spatie.be/?ref=pestphp)
- [Worksome](https://www.worksome.com/?ref=pestphp)
- [Zapiet](https://www.zapiet.com/?ref=pestphp)
Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.

View File

@ -32,10 +32,13 @@ $bootPest = (static function (): void {
'status-file:',
'progress-file:',
'unexpected-output-file:',
'testresult-file:',
'test-result-file:',
'result-cache-file:',
'teamcity-file:',
'testdox-file:',
'testdox-color',
'testdox-columns:',
'testdox-summary',
'phpunit-argv:',
]);
@ -61,7 +64,8 @@ $bootPest = (static function (): void {
assert(isset($getopt['progress-file']) && is_string($getopt['progress-file']));
assert(isset($getopt['unexpected-output-file']) && is_string($getopt['unexpected-output-file']));
assert(isset($getopt['testresult-file']) && is_string($getopt['testresult-file']));
assert(isset($getopt['test-result-file']) && is_string($getopt['test-result-file']));
assert(! isset($getopt['result-cache-file']) || is_string($getopt['result-cache-file']));
assert(! isset($getopt['teamcity-file']) || is_string($getopt['teamcity-file']));
assert(! isset($getopt['testdox-file']) || is_string($getopt['testdox-file']));
@ -77,7 +81,8 @@ $bootPest = (static function (): void {
$phpunitArgv,
$getopt['progress-file'],
$getopt['unexpected-output-file'],
$getopt['testresult-file'],
$getopt['test-result-file'],
$getopt['result-cache-file'] ?? null,
$getopt['teamcity-file'] ?? null,
$getopt['testdox-file'] ?? null,
isset($getopt['testdox-color']),

View File

@ -18,17 +18,17 @@
],
"require": {
"php": "^8.2.0",
"brianium/paratest": "^7.6.0",
"brianium/paratest": "^7.7.0",
"nunomaduro/collision": "^8.5.0",
"nunomaduro/termwind": "^2.2.0",
"nunomaduro/termwind": "^2.3.0",
"pestphp/pest-plugin": "^3.0.0",
"pestphp/pest-plugin-arch": "^3.0.0",
"pestphp/pest-plugin-mutate": "^3.0.5",
"phpunit/phpunit": "^11.4.3"
"phpunit/phpunit": "^11.5.3"
},
"conflict": {
"filp/whoops": "<2.16.0",
"phpunit/phpunit": ">11.4.3",
"phpunit/phpunit": ">11.5.3",
"sebastian/exporter": "<6.0.0",
"webmozart/assert": "<1.11.0"
},
@ -54,8 +54,8 @@
},
"require-dev": {
"pestphp/pest-dev-tools": "^3.3.0",
"pestphp/pest-plugin-type-coverage": "^3.1.0",
"symfony/process": "^7.1.6"
"pestphp/pest-plugin-type-coverage": "^3.2.3",
"symfony/process": "^7.2.0"
},
"minimum-stability": "dev",
"prefer-stable": true,

View File

@ -68,7 +68,7 @@ final readonly class ThrowableBuilder
$previous = self::from($previous);
}
$trace = Filter::getFilteredStacktrace($t);
$trace = Filter::stackTraceFromThrowableAsString($t);
if ($t instanceof RenderableOnCollisionEditor && $frame = $t->toCollisionEditor()) {
$file = $frame->getFile();

View File

@ -446,7 +446,7 @@ final class JunitXmlLogger
if ($test->isTestMethod()) {
assert($test instanceof TestMethod);
//$testCase->setAttribute('line', (string) $test->line()); // pest-removed
// $testCase->setAttribute('line', (string) $test->line()); // pest-removed
$className = $this->converter->getTrimmedTestClassName($test); // pest-added
$testCase->setAttribute('class', $className); // pest-changed
$testCase->setAttribute('classname', str_replace('\\', '.', $className)); // pest-changed

View File

@ -19,13 +19,13 @@ final class BootOverrides implements Bootstrapper
*/
public const FILES = [
'53c246e5f416a39817ac81124cdd64ea8403038d01d7a202e1ffa486fbdf3fa7' => 'Runner/Filter/NameFilterIterator.php',
'a4a43de01f641c6944ee83d963795a46d32b5206b5ab3bbc6cce76e67190acbf' => 'Runner/ResultCache/DefaultResultCache.php',
'77ffb7647b583bd82e37962c6fbdc4b04d3344d8a2c1ed103e625ed1ff7cb5c2' => 'Runner/ResultCache/DefaultResultCache.php',
'd0e81317889ad88c707db4b08a94cadee4c9010d05ff0a759f04e71af5efed89' => 'Runner/TestSuiteLoader.php',
'3bb609b0d3bf6dee8df8d6cd62a3c8ece823c4bb941eaaae39e3cb267171b9d2' => 'TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php',
'8abdad6413329c6fe0d7d44a8b9926e390af32c0b3123f3720bb9c5bbc6fbb7e' => 'TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php',
'b4250fc3ffad5954624cb5e682fd940b874e8d3422fa1ee298bd7225e1aa5fc2' => 'TextUI/TestSuiteFilterProcessor.php',
'357d5cd7007f8559b26e1b8cdf43bb6fb15b51b79db981779da6f31b7ec39dad' => 'Event/Value/ThrowableBuilder.php',
'676273f1fe483877cf2d95c5aedbf9ae5d6a8e2f4c12d6ce716df6591e6db023' => 'Logging/JUnit/JunitXmlLogger.php',
'8cfcb4999af79463eca51a42058e502ea4ddc776cba5677bf2f8eb6093e21a5c' => 'Event/Value/ThrowableBuilder.php',
'86cd9bcaa53cdd59c5b13e58f30064a015c549501e7629d93b96893d4dee1eb1' => 'Logging/JUnit/JunitXmlLogger.php',
];
/**

View File

@ -11,6 +11,7 @@ use Pest\Support\Str;
use PHPUnit\Event\Code\Test;
use PHPUnit\Event\Code\TestMethod;
use PHPUnit\Event\Code\Throwable;
use PHPUnit\Event\Test\AfterLastTestMethodErrored;
use PHPUnit\Event\Test\BeforeFirstTestMethodErrored;
use PHPUnit\Event\Test\ConsideredRisky;
use PHPUnit\Event\Test\Errored;
@ -254,8 +255,9 @@ final readonly class Converter
$numberOfNotPassedTests = count(
array_unique(
array_map(
function (BeforeFirstTestMethodErrored|Errored|Failed|Skipped|ConsideredRisky|MarkedIncomplete $event): string {
if ($event instanceof BeforeFirstTestMethodErrored) {
function (AfterLastTestMethodErrored|BeforeFirstTestMethodErrored|Errored|Failed|Skipped|ConsideredRisky|MarkedIncomplete $event): string {
if ($event instanceof BeforeFirstTestMethodErrored
|| $event instanceof AfterLastTestMethodErrored) {
return $event->testClassName();
}

View File

@ -56,7 +56,7 @@ final class AfterEachCall
$afterEachTestCase = ChainableClosure::boundWhen(
fn (): bool => $describing === [] || in_array(Arr::last($describing), $this->__describing, true),
ChainableClosure::bound(fn () => $proxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line
ChainableClosure::bound(fn () => $proxies->chain($this), $this->closure)->bindTo($this, self::class),
)->bindTo($this, self::class);
assert($afterEachTestCase instanceof Closure);

View File

@ -79,7 +79,7 @@ final class BeforeEachCall
$beforeEachTestCase = ChainableClosure::boundWhen(
fn (): bool => $describing === [] || in_array(Arr::last($describing), $this->__describing, true),
ChainableClosure::bound(fn () => $testCaseProxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line
ChainableClosure::bound(fn () => $testCaseProxies->chain($this), $this->closure)->bindTo($this, self::class),
)->bindTo($this, self::class);
assert($beforeEachTestCase instanceof Closure);

View File

@ -6,7 +6,7 @@ namespace Pest;
function version(): string
{
return '3.5.1';
return '3.7.2';
}
function testDirectory(string $file = ''): string

View File

@ -27,6 +27,11 @@ final class Coverage implements AddsOutput, HandlesArguments
*/
private const MIN_OPTION = 'min';
/**
* @var string
*/
private const EXACTLY_OPTION = 'exactly';
/**
* Whether it should show the coverage or not.
*/
@ -37,6 +42,11 @@ final class Coverage implements AddsOutput, HandlesArguments
*/
public float $coverageMin = 0.0;
/**
* The exactly coverage.
*/
public ?float $coverageExactly = null;
/**
* Creates a new Plugin instance.
*/
@ -51,7 +61,7 @@ final class Coverage implements AddsOutput, HandlesArguments
public function handleArguments(array $originals): array
{
$arguments = [...[''], ...array_values(array_filter($originals, function (string $original): bool {
foreach ([self::COVERAGE_OPTION, self::MIN_OPTION] as $option) {
foreach ([self::COVERAGE_OPTION, self::MIN_OPTION, self::EXACTLY_OPTION] as $option) {
if ($original === sprintf('--%s', $option)) {
return true;
}
@ -73,6 +83,7 @@ final class Coverage implements AddsOutput, HandlesArguments
$inputs = [];
$inputs[] = new InputOption(self::COVERAGE_OPTION, null, InputOption::VALUE_NONE);
$inputs[] = new InputOption(self::MIN_OPTION, null, InputOption::VALUE_REQUIRED);
$inputs[] = new InputOption(self::EXACTLY_OPTION, null, InputOption::VALUE_REQUIRED);
$input = new ArgvInput($arguments, new InputDefinition($inputs));
if ((bool) $input->getOption(self::COVERAGE_OPTION)) {
@ -106,6 +117,13 @@ final class Coverage implements AddsOutput, HandlesArguments
$this->coverageMin = (float) $minOption;
}
if ($input->getOption(self::EXACTLY_OPTION) !== null) {
/** @var int|float $exactlyOption */
$exactlyOption = $input->getOption(self::EXACTLY_OPTION);
$this->coverageExactly = (float) $exactlyOption;
}
return $originals;
}
@ -127,10 +145,22 @@ final class Coverage implements AddsOutput, HandlesArguments
}
$coverage = \Pest\Support\Coverage::report($this->output);
$exitCode = (int) ($coverage < $this->coverageMin);
if ($exitCode === 1) {
if ($exitCode === 0 && $this->coverageExactly !== null) {
$comparableCoverage = $this->computeComparableCoverage($coverage);
$comparableCoverageExactly = $this->computeComparableCoverage($this->coverageExactly);
$exitCode = $comparableCoverage === $comparableCoverageExactly ? 0 : 1;
if ($exitCode === 1) {
$this->output->writeln(sprintf(
"\n <fg=white;bg=red;options=bold> FAIL </> Code coverage not exactly <fg=white;options=bold> %s %%</>, currently <fg=red;options=bold> %s %%</>.",
number_format($this->coverageExactly, 1),
number_format(floor($coverage * 10) / 10, 1),
));
}
} elseif ($exitCode === 1) {
$this->output->writeln(sprintf(
"\n <fg=white;bg=red;options=bold> FAIL </> Code coverage below expected <fg=white;options=bold> %s %%</>, currently <fg=red;options=bold> %s %%</>.",
number_format($this->coverageMin, 1),
@ -143,4 +173,12 @@ final class Coverage implements AddsOutput, HandlesArguments
return $exitCode;
}
/**
* Computes the comparable coverage to a percentage with one decimal.
*/
private function computeComparableCoverage(float $coverage): float
{
return floor($coverage * 10) / 10;
}
}

View File

@ -19,6 +19,7 @@ use Pest\TestSuite;
use PHPUnit\Event\Facade as EventFacade;
use PHPUnit\Event\TestRunner\WarningTriggered;
use PHPUnit\Runner\CodeCoverage;
use PHPUnit\Runner\ResultCache\DefaultResultCache;
use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade;
use PHPUnit\TestRunner\TestResult\TestResult;
use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry;
@ -79,7 +80,10 @@ final class WrapperRunner implements RunnerInterface
private array $unexpectedOutputFiles = [];
/** @var list<SplFileInfo> */
private array $testresultFiles = [];
private array $resultCacheFiles = [];
/** @var list<SplFileInfo> */
private array $testResultFiles = [];
/** @var list<SplFileInfo> */
private array $coverageFiles = [];
@ -264,7 +268,8 @@ final class WrapperRunner implements RunnerInterface
$this->batches[$token] = 0;
$this->unexpectedOutputFiles[] = $worker->unexpectedOutputFile;
$this->testresultFiles[] = $worker->testresultFile;
$this->unexpectedOutputFiles[] = $worker->unexpectedOutputFile;
$this->testResultFiles[] = $worker->testResultFile;
if (isset($worker->junitFile)) {
$this->junitFiles[] = $worker->junitFile;
@ -298,12 +303,12 @@ final class WrapperRunner implements RunnerInterface
private function complete(TestResult $testResultSum): int
{
foreach ($this->testresultFiles as $testresultFile) {
if (! $testresultFile->isFile()) {
foreach ($this->testResultFiles as $testResultFile) {
if (! $testResultFile->isFile()) {
continue;
}
$contents = file_get_contents($testresultFile->getPathname());
$contents = file_get_contents($testResultFile->getPathname());
assert($contents !== false);
$testResult = unserialize($contents);
assert($testResult instanceof TestResult);
@ -360,9 +365,20 @@ final class WrapperRunner implements RunnerInterface
$testResultSum->phpNotices(),
$testResultSum->phpWarnings(),
$testResultSum->numberOfIssuesIgnoredByBaseline(),
);
if ($this->options->configuration->cacheResult()) {
$resultCacheSum = new DefaultResultCache($this->options->configuration->testResultCacheFile());
foreach ($this->resultCacheFiles as $resultCacheFile) {
$resultCache = new DefaultResultCache($resultCacheFile->getPathname());
$resultCache->load();
$resultCacheSum->mergeWith($resultCache);
}
$resultCacheSum->persist();
}
$this->printer->printResults(
$testResultSum,
$this->teamcityFiles,
@ -375,7 +391,7 @@ final class WrapperRunner implements RunnerInterface
$exitcode = Result::exitCode($this->options->configuration, $testResultSum);
$this->clearFiles($this->unexpectedOutputFiles);
$this->clearFiles($this->testresultFiles);
$this->clearFiles($this->testResultFiles);
$this->clearFiles($this->coverageFiles);
$this->clearFiles($this->junitFiles);
$this->clearFiles($this->teamcityFiles);

View File

@ -30,6 +30,7 @@ final class StateGenerator
$testResultEvent->throwable()
));
} else {
// @phpstan-ignore-next-line
$state->add(TestResult::fromBeforeFirstTestMethodErrored($testResultEvent));
}
}

View File

@ -1,5 +1,5 @@
Pest Testing Framework 3.5.1.
Pest Testing Framework 3.7.2.
USAGE: pest <file> [options]

View File

@ -1,3 +1,3 @@
Pest Testing Framework 3.5.1.
Pest Testing Framework 3.7.2.

View File

@ -1491,6 +1491,16 @@
PASS Tests\Playground
✓ basic
PASS Tests\Plugins\Coverage
✓ compute comparable coverage with (0, 0)
✓ compute comparable coverage with (0.5, 0.5)
✓ compute comparable coverage with (1.0, 1.0)
✓ compute comparable coverage with (32.51, 32.5)
✓ compute comparable coverage with (32.12312321312312, 32.1)
✓ compute comparable coverage with (32.53333333333333, 32.5)
✓ compute comparable coverage with (32.57777771232132, 32.5)
✓ compute comparable coverage with (100.0, 100.0)
PASS Tests\Plugins\Traits
✓ it allows global uses
✓ it allows multiple global uses registered in the same path
@ -1698,4 +1708,4 @@
WARN Tests\Visual\Version
- visual snapshot of help command output
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1144 passed (2736 assertions)
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1152 passed (2744 assertions)

View File

@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase
{
public function testExample()
public function test_example()
{
$this->markTestSkipped();
}

View File

@ -6,7 +6,7 @@ class ExampleTest extends Base\ExampleTest
{
protected $foo;
public function testExample()
public function test_example()
{
$this->assertTrue(true);
}

View File

@ -13,7 +13,7 @@ class ExecutedTest extends TestCase
public static $executed = false;
#[Test]
public function testThatGetsExecuted(): void
public function test_that_gets_executed(): void
{
self::$executed = true;

View File

@ -17,7 +17,7 @@ class ParentTest extends TestCase
}
#[Test]
public function testOverrideMethod(): void
public function test_override_method(): void
{
assertTrue($this->getEntity() || true);
}

View File

@ -0,0 +1,23 @@
<?php
use Pest\Plugins\Coverage;
use Symfony\Component\Console\Output\NullOutput;
test('compute comparable coverage', function (float $givenValue, float $expectedValue) {
$output = new NullOutput;
$plugin = new Coverage($output);
$comparableCoverage = (fn () => $this->computeComparableCoverage($givenValue))->call($plugin);
expect($comparableCoverage)->toBe($expectedValue);
})->with([
[0, 0],
[0.5, 0.5],
[1.0, 1.0],
[32.51, 32.5],
[32.12312321312312, 32.1],
[32.53333333333333, 32.5],
[32.57777771232132, 32.5],
[100.0, 100.0],
]);

View File

@ -16,7 +16,7 @@ $run = function () {
test('parallel', function () use ($run) {
expect($run('--exclude-group=integration'))
->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1134 passed (2712 assertions)')
->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1142 passed (2720 assertions)')
->toContain('Parallel: 3 processes');
})->skipOnWindows();