mirror of
https://github.com/pestphp/pest.git
synced 2026-07-24 18:40:03 +02:00
Compare commits
11 Commits
f8c4c061f8
..
5.x
| Author | SHA1 | Date | |
|---|---|---|---|
| 759ac63139 | |||
| 333f469efb | |||
| c4ec925aa5 | |||
| 5c8cfed483 | |||
| 962510b7e3 | |||
| e9c268a584 | |||
| fa91fcf744 | |||
| c6073511a2 | |||
| b5e1b83bb4 | |||
| 1c11595504 | |||
| cd0e921158 |
@@ -28,7 +28,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
> Pest v5 Now Available: **[Read the announcement »](https://pestphp.com/docs/pest-v5-is-here)**.
|
> Pest v5 Now Available: **[Read the announcement »](https://pestphp.com/docs/pest-v5-is-here)**.
|
||||||
|
|
||||||
**Pest** is an elegant PHP testing Framework with a focus on simplicity, meticulously designed to bring back the joy of testing in PHP.
|
**Pest** is an elegant testing framework for PHP developers and AI agents.
|
||||||
|
|
||||||
- Explore our docs at **[pestphp.com »](https://pestphp.com)**
|
- Explore our docs at **[pestphp.com »](https://pestphp.com)**
|
||||||
- Follow the creator Nuno Maduro:
|
- Follow the creator Nuno Maduro:
|
||||||
@@ -29,26 +29,12 @@
|
|||||||
|
|
||||||
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)**.
|
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
|
|
||||||
|
|
||||||
- **[CodeRabbit](https://coderabbit.ai/?ref=pestphp)**
|
|
||||||
- **[Mailtrap](https://l.rw.rw/pestphp)**
|
|
||||||
- **[SerpApi](https://serpapi.com/?ref=nunomaduro)**
|
|
||||||
- **[Tighten](https://tighten.com/?ref=nunomaduro)**
|
|
||||||
- **[Redberry](https://redberry.international/laravel-development/?utm_source=pest&utm_medium=banner&utm_campaign=pest_sponsorship)**
|
|
||||||
|
|
||||||
### Gold Sponsors
|
|
||||||
|
|
||||||
- **[CMS Max](https://cmsmax.com/?ref=pestphp)**
|
- **[CMS Max](https://cmsmax.com/?ref=pestphp)**
|
||||||
|
- **[PhpStorm](https://jb.gg/nuno)**
|
||||||
### Premium Sponsors
|
- **[CodeRabbit](https://coderabbit.link/nunomaduro)**
|
||||||
|
- **[SerpApi](https://serpapi.com/?ref=nunomaduro)**
|
||||||
- [Zapiet](https://zapiet.com/?ref=pestphp)
|
- **[Typesense](https://typesense.org/?ref=nunomaduro)**
|
||||||
- [Load Forge](https://loadforge.com/?ref=pestphp)
|
- **[Bento](https://bentonow.com/?ref=nunomaduro)**
|
||||||
- [Route4Me](https://route4me.com/pt?ref=pestphp)
|
- **[Redberry](https://redberry.international/laravel-development/)**
|
||||||
- [Nerdify](https://getnerdify.com/?ref=pestphp)
|
|
||||||
- [Akaunting](https://akaunting.com/?ref=pestphp)
|
|
||||||
- [TestMu AI](https://www.testmuai.com/?utm_medium=sponsor&utm_source=pest)
|
|
||||||
|
|
||||||
Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011 Brian Scaturro
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ParaTest\WrapperRunner;
|
||||||
|
|
||||||
|
use PHPUnit\TextUI\Output\Printer;
|
||||||
|
|
||||||
|
use function preg_match;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* This file is overridden so the "T" progress character — emitted by Pest for
|
||||||
|
* "todo" tests — is routed to the progress file next to the regular characters,
|
||||||
|
* instead of being treated as unexpected output.
|
||||||
|
*/
|
||||||
|
final readonly class ProgressPrinterOutput implements Printer
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Printer $progressPrinter,
|
||||||
|
private Printer $outputPrinter,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function print(string $buffer): void
|
||||||
|
{
|
||||||
|
// Skip anything in \PHPUnit\TextUI\Output\Default\ProgressPrinter\ProgressPrinter::printProgress except $progress
|
||||||
|
if (
|
||||||
|
$buffer === "\n"
|
||||||
|
|| preg_match('/^ +$/', $buffer) === 1
|
||||||
|
|| preg_match('/^ \d+ \/ \d+ \(...%\)$/', $buffer) === 1
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match ($buffer) {
|
||||||
|
'E', 'F', 'I', 'N', 'D', 'R', 'W', 'S', 'T', '.' => $this->progressPrinter->print($buffer),
|
||||||
|
default => $this->outputPrinter->print($buffer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flush(): void
|
||||||
|
{
|
||||||
|
$this->progressPrinter->flush();
|
||||||
|
$this->outputPrinter->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-6
@@ -43,7 +43,7 @@ declare(strict_types=1);
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Pest\Logging\TeamCity\Subscriber;
|
namespace PHPUnit\TextUI\Output\Default\ProgressPrinter;
|
||||||
|
|
||||||
use PHPUnit\Event\Test\Skipped;
|
use PHPUnit\Event\Test\Skipped;
|
||||||
use PHPUnit\Event\Test\SkippedSubscriber;
|
use PHPUnit\Event\Test\SkippedSubscriber;
|
||||||
@@ -51,16 +51,20 @@ use ReflectionClass;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||||
|
*
|
||||||
|
* This file is overridden so PHPUnit's progress output emits a "T" before the
|
||||||
|
* regular "S" for "todo" tests — Pest's parallel result printer consumes the
|
||||||
|
* "T" and swallows the "S" that follows it.
|
||||||
*/
|
*/
|
||||||
final class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber
|
final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber
|
||||||
{
|
{
|
||||||
public function notify(Skipped $event): void
|
public function notify(Skipped $event): void
|
||||||
{
|
{
|
||||||
if (str_contains($event->message(), '__TODO__')) {
|
if ($event->message() === '__TODO__') {
|
||||||
$this->printTodoItem();
|
$this->printTodoItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->logger()->testSkipped($event);
|
$this->printer()->testSkipped();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,7 +73,7 @@ final class TestSkippedSubscriber extends Subscriber implements SkippedSubscribe
|
|||||||
private function printTodoItem(): void
|
private function printTodoItem(): void
|
||||||
{
|
{
|
||||||
$mirror = new ReflectionClass($this->printer());
|
$mirror = new ReflectionClass($this->printer());
|
||||||
$printerMirror = $mirror->getMethod('printProgress');
|
$printProgress = $mirror->getMethod('printProgress');
|
||||||
$printerMirror->invoke($this->printer(), 'T');
|
$printProgress->invoke($this->printer(), 'T');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -23,8 +23,7 @@ return RectorConfig::configure()
|
|||||||
__DIR__.'/tests',
|
__DIR__.'/tests',
|
||||||
])
|
])
|
||||||
->withSets([
|
->withSets([
|
||||||
PestSetList::PEST_CODE_QUALITY,
|
PestSetList::CODING_STYLE,
|
||||||
PestSetList::PEST_CHAIN,
|
|
||||||
])
|
])
|
||||||
->withSkip([
|
->withSkip([
|
||||||
__DIR__.'/src/Plugins/Parallel/Paratest/WrapperRunner.php',
|
__DIR__.'/src/Plugins/Parallel/Paratest/WrapperRunner.php',
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ final class BootOverrides implements Bootstrapper
|
|||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
public const array FILES = [
|
public const array FILES = [
|
||||||
|
'ParaTest/WrapperRunner/ProgressPrinterOutput.php',
|
||||||
'Runner/Filter/NameFilterIterator.php',
|
'Runner/Filter/NameFilterIterator.php',
|
||||||
'Runner/ResultCache/DefaultResultCache.php',
|
'Runner/ResultCache/DefaultResultCache.php',
|
||||||
'Runner/TestSuiteLoader.php',
|
'Runner/TestSuiteLoader.php',
|
||||||
|
|||||||
+21
-22
@@ -15,11 +15,6 @@ use PHPUnit\Event\Test\AfterLastTestMethodErrored;
|
|||||||
use PHPUnit\Event\Test\AfterLastTestMethodFailed;
|
use PHPUnit\Event\Test\AfterLastTestMethodFailed;
|
||||||
use PHPUnit\Event\Test\BeforeFirstTestMethodErrored;
|
use PHPUnit\Event\Test\BeforeFirstTestMethodErrored;
|
||||||
use PHPUnit\Event\Test\BeforeFirstTestMethodFailed;
|
use PHPUnit\Event\Test\BeforeFirstTestMethodFailed;
|
||||||
use PHPUnit\Event\Test\ConsideredRisky;
|
|
||||||
use PHPUnit\Event\Test\Errored;
|
|
||||||
use PHPUnit\Event\Test\Failed;
|
|
||||||
use PHPUnit\Event\Test\MarkedIncomplete;
|
|
||||||
use PHPUnit\Event\Test\Skipped;
|
|
||||||
use PHPUnit\Event\TestSuite\TestSuite;
|
use PHPUnit\Event\TestSuite\TestSuite;
|
||||||
use PHPUnit\Event\TestSuite\TestSuiteForTestMethodWithDataProvider;
|
use PHPUnit\Event\TestSuite\TestSuiteForTestMethodWithDataProvider;
|
||||||
use PHPUnit\Framework\Exception as FrameworkException;
|
use PHPUnit\Framework\Exception as FrameworkException;
|
||||||
@@ -254,25 +249,29 @@ final readonly class Converter
|
|||||||
...$result->testMarkedIncompleteEvents(),
|
...$result->testMarkedIncompleteEvents(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$numberOfNotPassedTests = count(
|
$notPassedTests = [];
|
||||||
array_unique(
|
|
||||||
array_map(
|
|
||||||
function (AfterLastTestMethodErrored|AfterLastTestMethodFailed|BeforeFirstTestMethodErrored|BeforeFirstTestMethodFailed|Errored|Failed|Skipped|ConsideredRisky|MarkedIncomplete $event): string {
|
|
||||||
if ($event instanceof BeforeFirstTestMethodErrored
|
|
||||||
|| $event instanceof AfterLastTestMethodErrored
|
|
||||||
|| $event instanceof BeforeFirstTestMethodFailed
|
|
||||||
|| $event instanceof AfterLastTestMethodFailed) {
|
|
||||||
return $event->testClassName();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->getTestCaseLocation($event->test());
|
foreach ($events as $event) {
|
||||||
},
|
if ($event instanceof AfterLastTestMethodErrored) {
|
||||||
$events
|
// PHPUnit's collector does not count these towards `numberOfTestsRun`...
|
||||||
)
|
continue;
|
||||||
)
|
}
|
||||||
);
|
if ($event instanceof AfterLastTestMethodFailed) {
|
||||||
|
// PHPUnit's collector does not count these towards `numberOfTestsRun`...
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($event instanceof BeforeFirstTestMethodErrored || $event instanceof BeforeFirstTestMethodFailed) {
|
||||||
|
$notPassedTests[] = $event->testClassName();
|
||||||
|
|
||||||
$numberOfPassedTests = $result->numberOfTestsRun() - $numberOfNotPassedTests;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$notPassedTests[] = $this->getTestCaseLocation($event->test());
|
||||||
|
}
|
||||||
|
|
||||||
|
$numberOfPassedTests = $result->numberOfTestsRun()
|
||||||
|
- count(array_unique($notPassedTests))
|
||||||
|
- $result->numberOfTestSkippedByTestSuiteSkippedEvents();
|
||||||
|
|
||||||
return $this->stateGenerator->fromPhpUnitTestResult($numberOfPassedTests, $result);
|
return $this->stateGenerator->fromPhpUnitTestResult($numberOfPassedTests, $result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ final class TestSkippedSubscriber extends Subscriber implements SkippedSubscribe
|
|||||||
{
|
{
|
||||||
public function notify(Skipped $event): void
|
public function notify(Skipped $event): void
|
||||||
{
|
{
|
||||||
|
if ($event->message() === '__TODO__') {
|
||||||
|
return; // "todo" tests are reported in the summary, not as ignored tests...
|
||||||
|
}
|
||||||
|
|
||||||
$this->logger()->testSkipped($event);
|
$this->logger()->testSkipped($event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1219,4 +1219,104 @@ final class Expectation
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is an IP address.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeIpAddress(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertTrue((bool) filter_var($this->value, FILTER_VALIDATE_IP), $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a MAC address.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeMacAddress(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertTrue((bool) filter_var($this->value, FILTER_VALIDATE_MAC), $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a hostname.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeHostname(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertTrue((bool) filter_var($this->value, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME), $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a domain name.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeDomain(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$isValid = filter_var($this->value, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) !== false
|
||||||
|
&& str_contains($this->value, '.');
|
||||||
|
|
||||||
|
Assert::assertTrue($isValid, $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a base64-encoded string.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeBase64(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = base64_decode($this->value, true);
|
||||||
|
Assert::assertTrue($decoded !== false && base64_encode($decoded) === $this->value, $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a hexadecimal string.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeHexadecimal(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertTrue(ctype_xdigit($this->value), $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,10 +120,6 @@ final class ResultPrinter
|
|||||||
|
|
||||||
$unexpectedOutput = $this->tail($outputFile);
|
$unexpectedOutput = $this->tail($outputFile);
|
||||||
if ($unexpectedOutput !== '') {
|
if ($unexpectedOutput !== '') {
|
||||||
if (preg_match('/^T+$/', $unexpectedOutput) > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->output->write($unexpectedOutput);
|
$this->output->write($unexpectedOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use ParaTest\JUnit\LogMerger;
|
|||||||
use ParaTest\JUnit\Writer;
|
use ParaTest\JUnit\Writer;
|
||||||
use ParaTest\Options;
|
use ParaTest\Options;
|
||||||
use ParaTest\RunnerInterface;
|
use ParaTest\RunnerInterface;
|
||||||
|
use ParaTest\WrapperRunner\MissingResultsException;
|
||||||
use ParaTest\WrapperRunner\SuiteLoader;
|
use ParaTest\WrapperRunner\SuiteLoader;
|
||||||
use ParaTest\WrapperRunner\WrapperWorker;
|
use ParaTest\WrapperRunner\WrapperWorker;
|
||||||
use Pest\Result;
|
use Pest\Result;
|
||||||
@@ -42,6 +43,8 @@ use function assert;
|
|||||||
use function count;
|
use function count;
|
||||||
use function dirname;
|
use function dirname;
|
||||||
use function file_get_contents;
|
use function file_get_contents;
|
||||||
|
use function filesize;
|
||||||
|
use function is_file;
|
||||||
use function max;
|
use function max;
|
||||||
use function realpath;
|
use function realpath;
|
||||||
use function str_starts_with;
|
use function str_starts_with;
|
||||||
@@ -54,14 +57,14 @@ use function usleep;
|
|||||||
*/
|
*/
|
||||||
final class WrapperRunner implements RunnerInterface
|
final class WrapperRunner implements RunnerInterface
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The time to sleep between cycles.
|
|
||||||
*/
|
|
||||||
/**
|
/**
|
||||||
* The merged test result from the parallel run.
|
* The merged test result from the parallel run.
|
||||||
*/
|
*/
|
||||||
public static ?TestResult $result = null;
|
public static ?TestResult $result = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time to sleep between cycles.
|
||||||
|
*/
|
||||||
private const int CYCLE_SLEEP = 10000;
|
private const int CYCLE_SLEEP = 10000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,6 +91,18 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
/** @var array<int,int> */
|
/** @var array<int,int> */
|
||||||
private array $batches = [];
|
private array $batches = [];
|
||||||
|
|
||||||
|
/** @var array<non-empty-string,true> */
|
||||||
|
private array $requiredTestResultFiles = [];
|
||||||
|
|
||||||
|
/** @var array<non-empty-string,true> */
|
||||||
|
private array $requiredCoverageFiles = [];
|
||||||
|
|
||||||
|
/** @var list<SplFileInfo> */
|
||||||
|
private array $statusFiles = [];
|
||||||
|
|
||||||
|
/** @var list<SplFileInfo> */
|
||||||
|
private array $progressFiles = [];
|
||||||
|
|
||||||
/** @var list<SplFileInfo> */
|
/** @var list<SplFileInfo> */
|
||||||
private array $unexpectedOutputFiles = [];
|
private array $unexpectedOutputFiles = [];
|
||||||
|
|
||||||
@@ -217,7 +232,7 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
$this->exitcode > 0
|
$this->exitcode > 0
|
||||||
&& $this->options->configuration->stopOnFailure()
|
&& $this->options->configuration->stopOnFailureThreshold() > 0
|
||||||
) {
|
) {
|
||||||
$this->pending = [];
|
$this->pending = [];
|
||||||
} elseif (($pending = array_shift($this->pending)) !== null) {
|
} elseif (($pending = array_shift($this->pending)) !== null) {
|
||||||
@@ -232,6 +247,18 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
|
|
||||||
private function flushWorker(WrapperWorker $worker): void
|
private function flushWorker(WrapperWorker $worker): void
|
||||||
{
|
{
|
||||||
|
if ($worker->hasExecutedTests()) {
|
||||||
|
$testResultFile = $worker->testResultFile->getPathname();
|
||||||
|
|
||||||
|
if ($testResultFile !== '') {
|
||||||
|
$this->requiredTestResultFiles[$testResultFile] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($worker->coverageFile) && $worker->coverageFile->getPathname() !== '') {
|
||||||
|
$this->requiredCoverageFiles[$worker->coverageFile->getPathname()] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->exitcode = max($this->exitcode, $worker->getExitCode());
|
$this->exitcode = max($this->exitcode, $worker->getExitCode());
|
||||||
$this->printer->printFeedback(
|
$this->printer->printFeedback(
|
||||||
$worker->progressFile,
|
$worker->progressFile,
|
||||||
@@ -279,10 +306,15 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
$worker->start();
|
$worker->start();
|
||||||
$this->batches[$token] = 0;
|
$this->batches[$token] = 0;
|
||||||
|
|
||||||
$this->unexpectedOutputFiles[] = $worker->unexpectedOutputFile;
|
$this->statusFiles[] = $worker->statusFile;
|
||||||
|
$this->progressFiles[] = $worker->progressFile;
|
||||||
$this->unexpectedOutputFiles[] = $worker->unexpectedOutputFile;
|
$this->unexpectedOutputFiles[] = $worker->unexpectedOutputFile;
|
||||||
$this->testResultFiles[] = $worker->testResultFile;
|
$this->testResultFiles[] = $worker->testResultFile;
|
||||||
|
|
||||||
|
if (isset($worker->resultCacheFile)) {
|
||||||
|
$this->resultCacheFiles[] = $worker->resultCacheFile;
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($worker->junitFile)) {
|
if (isset($worker->junitFile)) {
|
||||||
$this->junitFiles[] = $worker->junitFile;
|
$this->junitFiles[] = $worker->junitFile;
|
||||||
}
|
}
|
||||||
@@ -315,6 +347,20 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
|
|
||||||
private function complete(TestResult $testResultSum): int
|
private function complete(TestResult $testResultSum): int
|
||||||
{
|
{
|
||||||
|
$missingTestResultFiles = [];
|
||||||
|
|
||||||
|
foreach ($this->requiredTestResultFiles as $filePath => $true) {
|
||||||
|
if (is_file($filePath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$missingTestResultFiles[] = $filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($missingTestResultFiles !== []) {
|
||||||
|
throw MissingResultsException::create($missingTestResultFiles, 'test_result');
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($this->testResultFiles as $testResultFile) {
|
foreach ($this->testResultFiles as $testResultFile) {
|
||||||
if (! $testResultFile->isFile()) {
|
if (! $testResultFile->isFile()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -398,7 +444,7 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
$testResultSum->testRunnerTriggeredNoticeEvents(),
|
$testResultSum->testRunnerTriggeredNoticeEvents(),
|
||||||
array_values(array_filter(
|
array_values(array_filter(
|
||||||
$testResultSum->testRunnerTriggeredWarningEvents(),
|
$testResultSum->testRunnerTriggeredWarningEvents(),
|
||||||
fn (WarningTriggered $event): bool => ! str_contains($event->message(), 'No tests found')
|
fn (WarningTriggered $event): bool => ! str_contains($event->message(), 'No tests found in class')
|
||||||
)),
|
)),
|
||||||
$testResultSum->testRunnerTriggeredIssueDeprecationEvents(),
|
$testResultSum->testRunnerTriggeredIssueDeprecationEvents(),
|
||||||
$testResultSum->testRunnerTriggeredIssueErrorEvents(),
|
$testResultSum->testRunnerTriggeredIssueErrorEvents(),
|
||||||
@@ -442,8 +488,11 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
|
|
||||||
$exitcode = Result::exitCode($this->options->configuration, $testResultSum);
|
$exitcode = Result::exitCode($this->options->configuration, $testResultSum);
|
||||||
|
|
||||||
|
$this->clearFiles($this->statusFiles);
|
||||||
|
$this->clearFiles($this->progressFiles);
|
||||||
$this->clearFiles($this->unexpectedOutputFiles);
|
$this->clearFiles($this->unexpectedOutputFiles);
|
||||||
$this->clearFiles($this->testResultFiles);
|
$this->clearFiles($this->testResultFiles);
|
||||||
|
$this->clearFiles($this->resultCacheFiles);
|
||||||
$this->clearFiles($this->coverageFiles);
|
$this->clearFiles($this->coverageFiles);
|
||||||
$this->clearFiles($this->junitFiles);
|
$this->clearFiles($this->junitFiles);
|
||||||
$this->clearFiles($this->teamcityFiles);
|
$this->clearFiles($this->teamcityFiles);
|
||||||
@@ -458,6 +507,20 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$missingCoverageFiles = [];
|
||||||
|
|
||||||
|
foreach ($this->requiredCoverageFiles as $filePath => $true) {
|
||||||
|
if (is_file($filePath) && filesize($filePath) !== 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$missingCoverageFiles[] = $filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($missingCoverageFiles !== []) {
|
||||||
|
throw MissingResultsException::create($missingCoverageFiles, 'coverage');
|
||||||
|
}
|
||||||
|
|
||||||
$coverageManager = new CodeCoverage;
|
$coverageManager = new CodeCoverage;
|
||||||
$coverageManager->init(
|
$coverageManager->init(
|
||||||
$this->options->configuration,
|
$this->options->configuration,
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ use Pest\Plugins\Tia;
|
|||||||
use Pest\Plugins\Tia\Contracts\State;
|
use Pest\Plugins\Tia\Contracts\State;
|
||||||
use Pest\Support\Container;
|
use Pest\Support\Container;
|
||||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||||
|
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
||||||
|
use SebastianBergmann\CodeCoverage\Filter;
|
||||||
|
use SebastianBergmann\CodeCoverage\Serialization\Unserializer;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -161,7 +164,60 @@ final class CoverageMerger
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value instanceof CodeCoverage ? $value : null;
|
// Legacy `--coverage-php` format: a serialized `CodeCoverage` object.
|
||||||
|
if ($value instanceof CodeCoverage) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since phpunit/php-code-coverage 14, `--coverage-php` writes the report
|
||||||
|
// as a serialized array (`['codeCoverage' => ..., 'testResults' => ...,
|
||||||
|
// 'basePath' => ...]`) rather than a `CodeCoverage` object, so it has to
|
||||||
|
// be rebuilt into one before it can be merged.
|
||||||
|
return self::coverageFromSerializedData($reportPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function coverageFromSerializedData(string $reportPath): ?CodeCoverage
|
||||||
|
{
|
||||||
|
if ($reportPath === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$serialized = new Unserializer()->unserialize($reportPath);
|
||||||
|
} catch (Throwable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $serialized['codeCoverage'];
|
||||||
|
$basePath = $serialized['basePath'];
|
||||||
|
|
||||||
|
if ($basePath !== '') {
|
||||||
|
foreach ($data->coveredFiles() as $relativePath) {
|
||||||
|
$data->renameFile($relativePath, $basePath.DIRECTORY_SEPARATOR.$relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$coverage = self::emptyCoverage();
|
||||||
|
|
||||||
|
if (! $coverage instanceof CodeCoverage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coverage->setData($data);
|
||||||
|
$coverage->setTests($serialized['testResults']);
|
||||||
|
|
||||||
|
return $coverage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function emptyCoverage(): ?CodeCoverage
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$filter = new Filter;
|
||||||
|
|
||||||
|
return new CodeCoverage(new Selector()->forLineCoverage($filter), $filter);
|
||||||
|
} catch (Throwable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function unserializeCoverage(string $bytes): ?CodeCoverage
|
private static function unserializeCoverage(string $bytes): ?CodeCoverage
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ use NunoMaduro\Collision\Exceptions\TestOutcome;
|
|||||||
use PHPUnit\Event\Code\TestDoxBuilder;
|
use PHPUnit\Event\Code\TestDoxBuilder;
|
||||||
use PHPUnit\Event\Code\TestMethod;
|
use PHPUnit\Event\Code\TestMethod;
|
||||||
use PHPUnit\Event\Code\ThrowableBuilder;
|
use PHPUnit\Event\Code\ThrowableBuilder;
|
||||||
|
use PHPUnit\Event\Test\AfterLastTestMethodErrored;
|
||||||
|
use PHPUnit\Event\Test\AfterLastTestMethodFailed;
|
||||||
|
use PHPUnit\Event\Test\BeforeFirstTestMethodErrored;
|
||||||
|
use PHPUnit\Event\Test\BeforeFirstTestMethodFailed;
|
||||||
use PHPUnit\Event\Test\Errored;
|
use PHPUnit\Event\Test\Errored;
|
||||||
use PHPUnit\Event\Test\Failed;
|
use PHPUnit\Event\Test\Failed;
|
||||||
use PHPUnit\Event\Test\PhpunitDeprecationTriggered;
|
use PHPUnit\Event\Test\PhpunitDeprecationTriggered;
|
||||||
@@ -35,8 +39,7 @@ final class StateGenerator
|
|||||||
$testResultEvent->throwable()
|
$testResultEvent->throwable()
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
// @phpstan-ignore-next-line
|
$this->addClassLevelEvent($state, $testResultEvent);
|
||||||
$state->add(TestResult::fromBeforeFirstTestMethodErrored($testResultEvent));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,8 +51,7 @@ final class StateGenerator
|
|||||||
$testResultEvent->throwable()
|
$testResultEvent->throwable()
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
// @phpstan-ignore-next-line
|
$this->addClassLevelEvent($state, $testResultEvent);
|
||||||
$state->add(TestResult::fromBeforeFirstTestMethodErrored($testResultEvent));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,6 +186,36 @@ final class StateGenerator
|
|||||||
return $state;
|
return $state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given class-level "hook" failure to the state. Collision's
|
||||||
|
* `fromBeforeFirstTestMethodErrored` only accepts `BeforeFirstTestMethodErrored`
|
||||||
|
* events, so the remaining class-level events get a synthesized test method.
|
||||||
|
*/
|
||||||
|
private function addClassLevelEvent(State $state, AfterLastTestMethodErrored|AfterLastTestMethodFailed|BeforeFirstTestMethodErrored|BeforeFirstTestMethodFailed $event): void
|
||||||
|
{
|
||||||
|
if ($event instanceof BeforeFirstTestMethodErrored) {
|
||||||
|
$state->add(TestResult::fromBeforeFirstTestMethodErrored($event));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$methodName = $event instanceof BeforeFirstTestMethodFailed ? 'beforeAll' : 'afterAll';
|
||||||
|
|
||||||
|
$state->add(TestResult::fromPestParallelTestCase(
|
||||||
|
new TestMethod(
|
||||||
|
$event->testClassName(), // @phpstan-ignore-line
|
||||||
|
$methodName,
|
||||||
|
'', // @phpstan-ignore-line
|
||||||
|
1,
|
||||||
|
TestDoxBuilder::fromClassNameAndMethodName($event->testClassName(), $methodName), // @phpstan-ignore-line
|
||||||
|
MetadataCollection::fromArray([]),
|
||||||
|
TestDataCollection::fromArray([])
|
||||||
|
),
|
||||||
|
TestResult::FAIL,
|
||||||
|
$event->throwable()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, list<PhpunitDeprecationTriggered|PhpunitErrorTriggered|PhpunitNoticeTriggered|PhpunitWarningTriggered>> $testResultEvents
|
* @param array<string, list<PhpunitDeprecationTriggered|PhpunitErrorTriggered|PhpunitNoticeTriggered|PhpunitWarningTriggered>> $testResultEvents
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -476,6 +476,16 @@
|
|||||||
✓ pass
|
✓ pass
|
||||||
✓ failures
|
✓ failures
|
||||||
✓ failures with custom message
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeBase64
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with invalid padding
|
||||||
|
✓ failures with padding only
|
||||||
|
✓ failures with malformed input
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
✓ not failures
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeBetween
|
PASS Tests\Features\Expect\toBeBetween
|
||||||
@@ -522,6 +532,14 @@
|
|||||||
✓ pass
|
✓ pass
|
||||||
✓ failures
|
✓ failures
|
||||||
✓ failures with custom message
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeDomain
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with leading dot
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
✓ not failures
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeEmail
|
PASS Tests\Features\Expect\toBeEmail
|
||||||
@@ -584,6 +602,24 @@
|
|||||||
✓ passes with strings
|
✓ passes with strings
|
||||||
✓ failures
|
✓ failures
|
||||||
✓ failures with custom message
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeHexadecimal
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with invalid characters
|
||||||
|
✓ failures with zero-x prefix
|
||||||
|
✓ failures with empty string
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeHostname
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with trailing hyphen
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
✓ not failures
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeIn
|
PASS Tests\Features\Expect\toBeIn
|
||||||
@@ -622,6 +658,13 @@
|
|||||||
✓ failure when the class is not invokable
|
✓ failure when the class is not invokable
|
||||||
✓ class is not invokable
|
✓ class is not invokable
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeIpAddress
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeIterable
|
PASS Tests\Features\Expect\toBeIterable
|
||||||
✓ pass
|
✓ pass
|
||||||
✓ failures
|
✓ failures
|
||||||
@@ -666,6 +709,13 @@
|
|||||||
✓ pass
|
✓ pass
|
||||||
✓ failures
|
✓ failures
|
||||||
✓ failures with custom message
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeMacAddress
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
✓ not failures
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeNAN
|
PASS Tests\Features\Expect\toBeNAN
|
||||||
@@ -2136,4 +2186,4 @@
|
|||||||
✓ pass with dataset with ('my-datas-set-value')
|
✓ pass with dataset with ('my-datas-set-value')
|
||||||
✓ within describe → pass with dataset with ('my-datas-set-value')
|
✓ within describe → pass with dataset with ('my-datas-set-value')
|
||||||
|
|
||||||
Tests: 1 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1504 passed (3281 assertions)
|
Tests: 1 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1542 passed (3375 assertions)
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Pest\Expectation;
|
use Pest\Expectation;
|
||||||
use Pest\Plugins\Tia\BaselineSync;
|
use Pest\Plugins\Tia\BaselineSync;
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -1,12 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Pest\Plugin;
|
use Pest\Plugin;
|
||||||
|
|
||||||
trait PluginTrait
|
trait PluginTrait
|
||||||
{
|
{
|
||||||
public function assertPluginTraitGotRegistered(): void
|
public function assertPluginTraitGotRegistered(): void
|
||||||
{
|
{
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,7 +16,7 @@ trait SecondPluginTrait
|
|||||||
{
|
{
|
||||||
public function assertSecondPluginTraitGotRegistered(): void
|
public function assertSecondPluginTraitGotRegistered(): void
|
||||||
{
|
{
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
$file = __DIR__.DIRECTORY_SEPARATOR.'after-all-test';
|
$file = __DIR__.DIRECTORY_SEPARATOR.'after-all-test';
|
||||||
|
|
||||||
beforeAll(function () use ($file): void {
|
beforeAll(function () use ($file): void {
|
||||||
@@ -12,7 +14,7 @@ afterAll(function () use ($file): void {
|
|||||||
|
|
||||||
test('deletes file after all', function () use ($file): void {
|
test('deletes file after all', function () use ($file): void {
|
||||||
file_put_contents($file, 'foo');
|
file_put_contents($file, 'foo');
|
||||||
$this->assertFileExists($file);
|
expect($file)->toBeFile();
|
||||||
register_shutdown_function(function (): void {
|
register_shutdown_function(function (): void {
|
||||||
// $this->assertFileDoesNotExist($file);
|
// $this->assertFileDoesNotExist($file);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
beforeEach()->expect(true)->toBeTrue();
|
beforeEach()->expect(true)->toBeTrue();
|
||||||
|
|
||||||
test('runs 1', function (): void {
|
test('runs 1', function (): void {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
beforeEach()->skip();
|
beforeEach()->skip();
|
||||||
|
|
||||||
test('does not run 1', function (): void {
|
test('does not run 1', function (): void {
|
||||||
|
|||||||
@@ -10,17 +10,12 @@ beforeEach(function (): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('throws exception if dataset does not exist', function (): void {
|
it('throws exception if dataset does not exist', function (): void {
|
||||||
$this->expectException(DatasetDoesNotExist::class);
|
expect(fn () => DatasetsRepository::resolve(['first'], __FILE__))->toThrow(DatasetDoesNotExist::class, "A dataset with the name `first` does not exist. You can create it using `dataset('first', ['a', 'b']);`.");
|
||||||
$this->expectExceptionMessage("A dataset with the name `first` does not exist. You can create it using `dataset('first', ['a', 'b']);`.");
|
|
||||||
|
|
||||||
DatasetsRepository::resolve(['first'], __FILE__);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws exception if dataset already exist', function (): void {
|
it('throws exception if dataset already exist', function (): void {
|
||||||
DatasetsRepository::set('second', [[]], __DIR__);
|
DatasetsRepository::set('second', [[]], __DIR__);
|
||||||
$this->expectException(DatasetAlreadyExists::class);
|
expect(fn () => DatasetsRepository::set('second', [[]], __DIR__))->toThrow(DatasetAlreadyExists::class, 'A dataset with the name `second` already exists in scope ['.__DIR__.'].');
|
||||||
$this->expectExceptionMessage('A dataset with the name `second` already exists in scope ['.__DIR__.'].');
|
|
||||||
DatasetsRepository::set('second', [[]], __DIR__);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets closures', function (): void {
|
it('sets closures', function (): void {
|
||||||
@@ -38,7 +33,7 @@ it('sets arrays', function (): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('gets bound to test case object', function ($value): void {
|
it('gets bound to test case object', function ($value): void {
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
})->with([['a'], ['b']]);
|
})->with([['a'], ['b']]);
|
||||||
|
|
||||||
test('it truncates the description', function (): void {
|
test('it truncates the description', function (): void {
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
it('gives access the the underlying expectException', function (): void {
|
it('gives access the the underlying expectException', function (): void {
|
||||||
$this->expectException(InvalidArgumentException::class);
|
expect(fn () => throw new InvalidArgumentException)->toThrow(InvalidArgumentException::class);
|
||||||
|
|
||||||
throw new InvalidArgumentException;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('catch exceptions', function (): void {
|
it('catch exceptions', function (): void {
|
||||||
@@ -27,7 +25,7 @@ it('can just define the code', function (): void {
|
|||||||
})->throws(1);
|
})->throws(1);
|
||||||
|
|
||||||
it('not catch exceptions if given condition is false', function (): void {
|
it('not catch exceptions if given condition is false', function (): void {
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
})->throwsIf(false, Exception::class);
|
})->throwsIf(false, Exception::class);
|
||||||
|
|
||||||
it('catch exceptions if given condition is true', function (): void {
|
it('catch exceptions if given condition is true', function (): void {
|
||||||
@@ -59,7 +57,7 @@ it('can just define the code if given condition is 1', function (): void {
|
|||||||
})->throwsIf(1, 1);
|
})->throwsIf(1, 1);
|
||||||
|
|
||||||
it('not catch exceptions if given condition is true', function (): void {
|
it('not catch exceptions if given condition is true', function (): void {
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
})->throwsUnless(true, Exception::class);
|
})->throwsUnless(true, Exception::class);
|
||||||
|
|
||||||
it('catch exceptions if given condition is false', function (): void {
|
it('catch exceptions if given condition is false', function (): void {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('pass', function (): void {
|
||||||
|
expect('Zm9v')->toBeBase64() // 'foo' (no padding)
|
||||||
|
->and('Zm9vYg==')->toBeBase64() // 'foob' (with padding)
|
||||||
|
->and('Zm9vYmE=')->toBeBase64() // 'fooba' (with padding)
|
||||||
|
->and('Zm9vYmFy')->toBeBase64() // 'foobar' (no padding)
|
||||||
|
->and('')->toBeBase64(); // empty string
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures', function (): void {
|
||||||
|
expect('not-base64!')->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid padding', function (): void {
|
||||||
|
expect('AAA')->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with padding only', function (): void {
|
||||||
|
expect('====')->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with malformed input', function (): void {
|
||||||
|
expect('!!invalid!!')->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid type', function (): void {
|
||||||
|
expect([])->toBeBase64();
|
||||||
|
})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].');
|
||||||
|
|
||||||
|
test('failures with custom message', function (): void {
|
||||||
|
expect('!!invalid!!')->toBeBase64('oh no!');
|
||||||
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
|
test('not failures', function (): void {
|
||||||
|
expect('Zm9v')->not->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('pass', function (): void {
|
||||||
|
expect('example.com')->toBeDomain() // standard domain
|
||||||
|
->and('sub.example.com')->toBeDomain() // subdomain
|
||||||
|
->and('my-host.io')->toBeDomain() // with hyphen
|
||||||
|
->and('example.co.uk')->toBeDomain(); // multi-level TLD
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures', function (): void {
|
||||||
|
expect('example')->toBeDomain();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with leading dot', function (): void {
|
||||||
|
expect('.example.com')->toBeDomain();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid type', function (): void {
|
||||||
|
expect([])->toBeDomain();
|
||||||
|
})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].');
|
||||||
|
|
||||||
|
test('failures with custom message', function (): void {
|
||||||
|
expect('example')->toBeDomain('oh no!');
|
||||||
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
|
test('not failures', function (): void {
|
||||||
|
expect('example.com')->not->toBeDomain();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('pass', function (): void {
|
||||||
|
expect('abcdef')->toBeHexadecimal() // lowercase
|
||||||
|
->and('ABCDEF')->toBeHexadecimal() // uppercase
|
||||||
|
->and('aBcDeF')->toBeHexadecimal() // mixed case
|
||||||
|
->and('1234567890')->toBeHexadecimal() // numeric
|
||||||
|
->and('deadbeef')->toBeHexadecimal(); // alphanumeric
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures', function (): void {
|
||||||
|
expect('xyz')->toBeHexadecimal();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid characters', function (): void {
|
||||||
|
expect('ghijkl')->toBeHexadecimal();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with zero-x prefix', function (): void {
|
||||||
|
expect('0x1a')->toBeHexadecimal();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with empty string', function (): void {
|
||||||
|
expect('')->toBeHexadecimal();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid type', function (): void {
|
||||||
|
expect([])->toBeHexadecimal();
|
||||||
|
})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].');
|
||||||
|
|
||||||
|
test('failures with custom message', function (): void {
|
||||||
|
expect('xyz')->toBeHexadecimal('oh no!');
|
||||||
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
|
test('not failures', function (): void {
|
||||||
|
expect('abcdef')->not->toBeHexadecimal();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('pass', function (): void {
|
||||||
|
expect('example')->toBeHostname() // single label
|
||||||
|
->and('example.com')->toBeHostname() // multiple labels
|
||||||
|
->and('sub.example.com')->toBeHostname() // subdomain
|
||||||
|
->and('my-host')->toBeHostname(); // with hyphen
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures', function (): void {
|
||||||
|
expect('-example')->toBeHostname();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with trailing hyphen', function (): void {
|
||||||
|
expect('example-')->toBeHostname();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid type', function (): void {
|
||||||
|
expect([])->toBeHostname();
|
||||||
|
})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].');
|
||||||
|
|
||||||
|
test('failures with custom message', function (): void {
|
||||||
|
expect('-example')->toBeHostname('oh no!');
|
||||||
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
|
test('not failures', function (): void {
|
||||||
|
expect('example.com')->not->toBeHostname();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
test('enum is backed by int')
|
test('enum is backed by int')
|
||||||
->expect('Tests\Fixtures\Arch\ToBeIntBackedEnum\HasIntBacking')
|
->expect('Tests\Fixtures\Arch\ToBeIntBackedEnum\HasIntBacking')
|
||||||
->toBeIntBackedEnum();
|
->toBeIntBackedEnum();
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('pass', function (): void {
|
||||||
|
expect('192.168.1.1')->toBeIpAddress()
|
||||||
|
->and('::1')->toBeIpAddress()
|
||||||
|
->and('2001:db8::1')->toBeIpAddress();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures', function (): void {
|
||||||
|
expect('not-an-ip')->toBeIpAddress();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid type', function (): void {
|
||||||
|
expect([])->toBeIpAddress();
|
||||||
|
})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].');
|
||||||
|
|
||||||
|
test('failures with custom message', function (): void {
|
||||||
|
expect('not-an-ip')->toBeIpAddress('oh no!');
|
||||||
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
|
test('not failures', function (): void {
|
||||||
|
expect('192.168.1.1')->not->toBeIpAddress();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('pass', function (): void {
|
||||||
|
expect('00:1a:2b:3c:4d:5e')->toBeMacAddress() // colon-separated
|
||||||
|
->and('00-1a-2b-3c-4d-5e')->toBeMacAddress() // hyphen-separated
|
||||||
|
->and('ff:ff:ff:ff:ff:ff')->toBeMacAddress();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures', function (): void {
|
||||||
|
expect('not-a-mac')->toBeMacAddress();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid type', function (): void {
|
||||||
|
expect([])->toBeMacAddress();
|
||||||
|
})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].');
|
||||||
|
|
||||||
|
test('failures with custom message', function (): void {
|
||||||
|
expect('not-a-mac')->toBeMacAddress('oh no!');
|
||||||
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
|
test('not failures', function (): void {
|
||||||
|
expect('00:1a:2b:3c:4d:5e')->not->toBeMacAddress();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
test('enum is backed by string')
|
test('enum is backed by string')
|
||||||
->expect('Tests\Fixtures\Arch\ToBeStringBackedEnum\HasStringBacking')
|
->expect('Tests\Fixtures\Arch\ToBeStringBackedEnum\HasStringBacking')
|
||||||
->toBeStringBackedEnum();
|
->toBeStringBackedEnum();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Pest\Arch\Exceptions\ArchExpectationFailedException;
|
use Pest\Arch\Exceptions\ArchExpectationFailedException;
|
||||||
use Tests\Fixtures\Arch\ToHaveAttribute\Attributes\AsAttribute;
|
use Tests\Fixtures\Arch\ToHaveAttribute\Attributes\AsAttribute;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
$test_array = [
|
$test_array = [
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Pest\Arch\Exceptions\ArchExpectationFailedException;
|
use Pest\Arch\Exceptions\ArchExpectationFailedException;
|
||||||
|
|
||||||
test('missing prefix')
|
test('missing prefix')
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Pest\Arch\Exceptions\ArchExpectationFailedException;
|
use Pest\Arch\Exceptions\ArchExpectationFailedException;
|
||||||
|
|
||||||
test('missing suffix')
|
test('missing suffix')
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\AssertionFailedError;
|
use PHPUnit\Framework\AssertionFailedError;
|
||||||
|
|
||||||
it('may fail', function (): void {
|
it('may fail', function (): void {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
it('may fail', function (): void {
|
it('may fail', function (): void {
|
||||||
$this->fail();
|
$this->fail();
|
||||||
})->fails();
|
})->fails();
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
test('a test', function (): void {
|
test('a test', function (): void {
|
||||||
$this->assertArrayHasKey('key', ['key' => 'foo']);
|
expect(['key' => 'foo'])->toHaveKey('key');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('higher order message test')->expect(true)->toBeTrue();
|
test('higher order message test')->expect(true)->toBeTrue();
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ class ExampleTest extends Base\ExampleTest
|
|||||||
#[\Override]
|
#[\Override]
|
||||||
public function test_example(): void
|
public function test_example(): void
|
||||||
{
|
{
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class MyCustomClassTest extends TestCase
|
|||||||
{
|
{
|
||||||
public function assertTrueIsTrue(): void
|
public function assertTrueIsTrue(): void
|
||||||
{
|
{
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -10,6 +10,6 @@ class CustomTestCaseInSubFolder extends TestCase
|
|||||||
{
|
{
|
||||||
public function assertCustomInSubFolderTrue(): void
|
public function assertCustomInSubFolderTrue(): void
|
||||||
{
|
{
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ abstract class MyCustomClass extends TestCase
|
|||||||
{
|
{
|
||||||
public function assertTrueIsTrue(): void
|
public function assertTrueIsTrue(): void
|
||||||
{
|
{
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Pest\Plugins\Tia\ContentHash;
|
use Pest\Plugins\Tia\ContentHash;
|
||||||
|
|
||||||
describe('of()', function (): void {
|
describe('of()', function (): void {
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ test('parallel', function () use ($run): void {
|
|||||||
$file = file_get_contents(__FILE__);
|
$file = file_get_contents(__FILE__);
|
||||||
$file = preg_replace(
|
$file = preg_replace(
|
||||||
'/\$expected = \'.*?\';/',
|
'/\$expected = \'.*?\';/',
|
||||||
"\$expected = '1 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1487 passed (3228 assertions)';",
|
"\$expected = '1 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1525 passed (3322 assertions)';",
|
||||||
$file,
|
$file,
|
||||||
);
|
);
|
||||||
file_put_contents(__FILE__, $file);
|
file_put_contents(__FILE__, $file);
|
||||||
}
|
}
|
||||||
|
|
||||||
$expected = '1 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1487 passed (3228 assertions)';
|
$expected = '1 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1525 passed (3322 assertions)';
|
||||||
|
|
||||||
expect($output)
|
expect($output)
|
||||||
->toContain("Tests: {$expected}")
|
->toContain("Tests: {$expected}")
|
||||||
|
|||||||
Reference in New Issue
Block a user