Compare commits

..

2 Commits

Author SHA1 Message Date
5d4d2231fb chore: pin GitHub Actions to commit SHAs 2026-05-12 02:07:57 +01:00
a11a3126f2 chore: pin GitHub Actions to commit SHAs 2026-05-12 02:07:56 +01:00
22 changed files with 92 additions and 96 deletions

13
.github/SECURITY.md vendored
View File

@ -1,13 +0,0 @@
# Security Policy
**PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, [SEE BELOW](#reporting-a-vulnerability).**
## Reporting a Vulnerability
If you discover a security vulnerability in Pest, please report it privately using one of the following channels:
1. **GitHub Private Vulnerability Reporting** (preferred) — go to the repository's **Security** tab and click **"Report a vulnerability"**. This creates a private advisory visible only to maintainers and provides a structured workflow for triage, fix coordination, and CVE assignment.
2. **Email** — send the details to Nuno Maduro at **enunomaduro@gmail.com**.
All security vulnerabilities will be promptly addressed.

View File

@ -1,12 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 5
groups:
github-actions:
patterns:
- "*"

View File

@ -24,14 +24,14 @@ jobs:
strategy:
fail-fast: true
matrix:
dependency-version: [prefer-stable]
dependency-version: [prefer-lowest, prefer-stable]
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2
with:
php-version: 8.3
tools: composer:v2
@ -53,8 +53,6 @@ jobs:
static-php-8.3-composer-
- name: Install Dependencies
env:
COMPOSER_ROOT_VERSION: 4.x-dev
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress --ansi
- name: Profanity Check

View File

@ -35,10 +35,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
@ -66,8 +66,6 @@ jobs:
- name: Install PHP dependencies
shell: bash
env:
COMPOSER_ROOT_VERSION: 4.x-dev
run: composer update --${{ matrix.dependency_version }} --no-interaction --no-progress --ansi --with="symfony/console:^${{ matrix.symfony }}"
- name: Unit Tests

View File

@ -1,7 +1,7 @@
<p align="center">
<img src="https://raw.githubusercontent.com/pestphp/art/master/v4/social.png" width="600" alt="PEST">
<p align="center">
<a href="https://github.com/pestphp/pest/actions"><img alt="GitHub Workflow Status (4.x)" src="https://img.shields.io/github/actions/workflow/status/pestphp/pest/tests.yml?branch=4.x&label=Tests%204.x"></a>
<a href="https://github.com/pestphp/pest/actions"><img alt="GitHub Workflow Status (master)" src="https://img.shields.io/github/actions/workflow/status/pestphp/pest/tests.yml?branch=4.x&label=Tests%204.x"></a>
<a href="https://packagist.org/packages/pestphp/pest"><img alt="Total Downloads" src="https://img.shields.io/packagist/dt/pestphp/pest"></a>
<a href="https://packagist.org/packages/pestphp/pest"><img alt="Latest Version" src="https://img.shields.io/packagist/v/pestphp/pest"></a>
<a href="https://packagist.org/packages/pestphp/pest"><img alt="License" src="https://img.shields.io/packagist/l/pestphp/pest"></a>

View File

@ -26,12 +26,12 @@
"pestphp/pest-plugin-arch": "^4.0.2",
"pestphp/pest-plugin-mutate": "^4.0.1",
"pestphp/pest-plugin-profanity": "^4.2.1",
"phpunit/phpunit": "^12.5.29",
"symfony/process": "^7.4.13|^8.1.0"
"phpunit/phpunit": "^12.5.24",
"symfony/process": "^7.4.8|^8.0.8"
},
"conflict": {
"filp/whoops": "<2.18.3",
"phpunit/phpunit": ">12.5.29",
"phpunit/phpunit": ">12.5.24",
"sebastian/exporter": "<7.0.0",
"webmozart/assert": "<1.11.0"
},
@ -59,11 +59,11 @@
]
},
"require-dev": {
"mrpunyapal/peststan": "^0.2.10",
"mrpunyapal/peststan": "^0.2.9",
"pestphp/pest-dev-tools": "^4.1.0",
"pestphp/pest-plugin-browser": "^4.3.1",
"pestphp/pest-plugin-type-coverage": "^4.0.4",
"psy/psysh": "^0.12.23"
"psy/psysh": "^0.12.22"
},
"minimum-stability": "dev",
"prefer-stable": true,

View File

@ -435,11 +435,6 @@ trait Testable
if ($hasOutputExpectation) {
ob_clean();
Closure::bind(function (): void {
$this->outputExpectedString = null;
$this->outputExpectedRegex = null;
}, $this, TestCase::class)();
}
$this->setUp();

View File

@ -59,15 +59,12 @@ final class Project
/**
* Sets the test project to GitLab.
*/
public function gitlab(string $project, string $hostname = 'gitlab.com'): self
public function gitlab(string $project): self
{
// Simple way to ensure only the host is used
$hostname = parse_url($hostname, PHP_URL_HOST) ?? $hostname;
$this->issues = "https://gitlab.com/{$project}/issues/%s";
$this->prs = "https://gitlab.com/{$project}/merge_requests/%s";
$this->issues = "https://{$hostname}/{$project}/-/work_items/%s";
$this->prs = "https://{$hostname}/{$project}/-/merge_requests/%s";
$this->assignees = "https://{$hostname}/%s";
$this->assignees = 'https://gitlab.com/%s';
return $this;
}

View File

@ -112,7 +112,7 @@ final class Expectation
if (function_exists('dump')) {
dump($this->value, ...$arguments);
} else {
var_dump($this->value, ...$arguments);
var_dump($this->value);
}
return $this;
@ -120,22 +120,16 @@ final class Expectation
/**
* Dump the expectation value and end the script.
*
* @return never
*/
public function dd(mixed ...$arguments): never
public function dd(mixed ...$arguments): void
{
if (function_exists('dd')) {
dd($this->value, ...$arguments);
}
if (getenv('PARATEST') !== false || isset($_SERVER['COLLISION_PRINTER'])) {
ob_start();
var_dump($this->value, ...$arguments);
$output = (string) ob_get_clean();
throw new ExpectationFailedException($output);
}
var_dump($this->value, ...$arguments);
var_dump($this->value);
exit(1);
}

View File

@ -163,7 +163,7 @@ final class Kernel
$this->terminate();
if (is_array($error = error_get_last())) {
if (! in_array($error['type'], [E_ERROR, E_COMPILE_ERROR, E_CORE_ERROR], true)) {
if (! in_array($error['type'], [E_ERROR, E_CORE_ERROR], true)) {
return;
}

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Pest;
use Laravel\Pao\Execution;
use Pest\Support\View;
use Symfony\Component\Console\Output\OutputInterface;
@ -29,10 +28,6 @@ final class KernelDump
*/
public function enable(): void
{
if (class_exists(Execution::class) && Execution::running()) {
return;
}
ob_start(function (string $message): string {
$this->buffer .= $message;
@ -73,10 +68,6 @@ final class KernelDump
$type = 'INFO';
if (is_array($error = error_get_last()) && in_array($error['type'], [E_ERROR, E_COMPILE_ERROR, E_CORE_ERROR], true)) {
return;
}
if ($this->isInternalError($this->buffer)) {
$type = 'ERROR';
$this->buffer = str_replace(
@ -116,6 +107,7 @@ final class KernelDump
*/
private function isInternalError(string $output): bool
{
return str_contains($output, 'An error occurred inside PHPUnit.');
return str_contains($output, 'An error occurred inside PHPUnit.')
|| str_contains($output, 'Fatal error');
}
}

View File

@ -954,7 +954,6 @@ final class Expectation
} catch (Throwable $e) {
if ($exception instanceof Throwable) {
// @phpstan-ignore-next-line
expect($e)
->toBeInstanceOf($exception::class, $message)
->and($e->getMessage())->toBe($exceptionMessage ?? $exception->getMessage(), $message);

View File

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

View File

@ -146,6 +146,7 @@ final class WrapperRunner implements RunnerInterface
public function run(): int
{
$directory = dirname(__DIR__);
assert($directory !== '');
ExcludeList::addDirectory($directory);
TestResultFacade::init();
EventFacade::instance()->seal();

View File

@ -15,20 +15,15 @@ use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
final class EnsureTeamCityEnabled implements ConfiguredSubscriber
final readonly class EnsureTeamCityEnabled implements ConfiguredSubscriber
{
/**
* Indicates if the TeamCity logger has already been registered.
*/
private static bool $registered = false;
/**
* Creates a new Configured Subscriber instance.
*/
public function __construct(
private readonly InputInterface $input,
private readonly OutputInterface $output,
private readonly TestSuite $testSuite,
private InputInterface $input,
private OutputInterface $output,
private TestSuite $testSuite,
) {}
/**
@ -36,16 +31,10 @@ final class EnsureTeamCityEnabled implements ConfiguredSubscriber
*/
public function notify(Configured $event): void
{
if (self::$registered) {
return;
}
if (! $this->input->hasParameterOption('--teamcity')) {
return;
}
self::$registered = true;
$flowId = getenv('FLOW_ID');
$flowId = is_string($flowId) ? (int) $flowId : getmypid();

View File

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

View File

@ -1,3 +1,3 @@
Pest Testing Framework 4.7.3.
Pest Testing Framework 4.7.0.

View File

@ -1,28 +1,56 @@
##teamcity[testSuiteStarted name='Tests/tests/Failure' locationHint='pest_qn://tests/.tests/Failure.php' flowId='1234']
##teamcity[testCount count='8' flowId='1234']
##teamcity[testSuiteStarted name='Tests/tests/Failure' locationHint='pest_qn://tests/.tests/Failure.php' flowId='1234']
##teamcity[testCount count='8' flowId='1234']
##teamcity[testStarted name='it can fail with comparison' locationHint='pest_qn://tests/.tests/Failure.php::it can fail with comparison' flowId='1234']
##teamcity[testStarted name='it can fail with comparison' locationHint='pest_qn://tests/.tests/Failure.php::it can fail with comparison' flowId='1234']
##teamcity[testFailed name='it can fail with comparison' message='Failed asserting that true matches expected false.' details='at tests/.tests/Failure.php:6' type='comparisonFailure' actual='true' expected='false' flowId='1234']
##teamcity[testFailed name='it can fail with comparison' message='Failed asserting that true matches expected false.' details='at tests/.tests/Failure.php:6' type='comparisonFailure' actual='true' expected='false' flowId='1234']
##teamcity[testFinished name='it can fail with comparison' duration='100000' flowId='1234']
##teamcity[testFinished name='it can fail with comparison' duration='100000' flowId='1234']
##teamcity[testStarted name='it can be ignored because of no assertions' locationHint='pest_qn://tests/.tests/Failure.php::it can be ignored because of no assertions' flowId='1234']
##teamcity[testStarted name='it can be ignored because of no assertions' locationHint='pest_qn://tests/.tests/Failure.php::it can be ignored because of no assertions' flowId='1234']
##teamcity[testIgnored name='it can be ignored because of no assertions' message='This test did not perform any assertions' details='' flowId='1234']
##teamcity[testIgnored name='it can be ignored because of no assertions' message='This test did not perform any assertions' details='' flowId='1234']
##teamcity[testFinished name='it can be ignored because of no assertions' duration='100000' flowId='1234']
##teamcity[testFinished name='it can be ignored because of no assertions' duration='100000' flowId='1234']
##teamcity[testStarted name='it can be ignored because it is skipped' locationHint='pest_qn://tests/.tests/Failure.php::it can be ignored because it is skipped' flowId='1234']
##teamcity[testStarted name='it can be ignored because it is skipped' locationHint='pest_qn://tests/.tests/Failure.php::it can be ignored because it is skipped' flowId='1234']
##teamcity[testIgnored name='it can be ignored because it is skipped' message='This test was ignored.' details='' flowId='1234']
##teamcity[testIgnored name='it can be ignored because it is skipped' message='This test was ignored.' details='' flowId='1234']
##teamcity[testFinished name='it can be ignored because it is skipped' duration='100000' flowId='1234']
##teamcity[testFinished name='it can be ignored because it is skipped' duration='100000' flowId='1234']
##teamcity[testStarted name='it can fail' locationHint='pest_qn://tests/.tests/Failure.php::it can fail' flowId='1234']
##teamcity[testStarted name='it can fail' locationHint='pest_qn://tests/.tests/Failure.php::it can fail' flowId='1234']
##teamcity[testFailed name='it can fail' message='oh noo' details='at tests/.tests/Failure.php:18' flowId='1234']
##teamcity[testFailed name='it can fail' message='oh noo' details='at tests/.tests/Failure.php:18' flowId='1234']
##teamcity[testFinished name='it can fail' duration='100000' flowId='1234']
##teamcity[testFinished name='it can fail' duration='100000' flowId='1234']
##teamcity[testStarted name='it throws exception' locationHint='pest_qn://tests/.tests/Failure.php::it throws exception' flowId='1234']
##teamcity[testStarted name='it throws exception' locationHint='pest_qn://tests/.tests/Failure.php::it throws exception' flowId='1234']
##teamcity[testFailed name='it throws exception' message='Exception: test error' details='at tests/.tests/Failure.php:22' flowId='1234']
##teamcity[testFailed name='it throws exception' message='Exception: test error' details='at tests/.tests/Failure.php:22' flowId='1234']
##teamcity[testFinished name='it throws exception' duration='100000' flowId='1234']
##teamcity[testFinished name='it throws exception' duration='100000' flowId='1234']
##teamcity[testStarted name='it is not done yet' locationHint='pest_qn://tests/.tests/Failure.php::it is not done yet' flowId='1234']
##teamcity[testStarted name='it is not done yet' locationHint='pest_qn://tests/.tests/Failure.php::it is not done yet' flowId='1234']
##teamcity[testFinished name='it is not done yet' duration='100000' flowId='1234']
##teamcity[testFinished name='it is not done yet' duration='100000' flowId='1234']
##teamcity[testStarted name='build this one.' locationHint='pest_qn://tests/.tests/Failure.php::build this one.' flowId='1234']
##teamcity[testStarted name='build this one.' locationHint='pest_qn://tests/.tests/Failure.php::build this one.' flowId='1234']
##teamcity[testFinished name='build this one.' duration='100000' flowId='1234']
##teamcity[testFinished name='build this one.' duration='100000' flowId='1234']
##teamcity[testStarted name='it is passing' locationHint='pest_qn://tests/.tests/Failure.php::it is passing' flowId='1234']
##teamcity[testStarted name='it is passing' locationHint='pest_qn://tests/.tests/Failure.php::it is passing' flowId='1234']
##teamcity[testFinished name='it is passing' duration='100000' flowId='1234']
##teamcity[testFinished name='it is passing' duration='100000' flowId='1234']
##teamcity[testSuiteFinished name='Tests/tests/Failure' flowId='1234']
##teamcity[testSuiteFinished name='Tests/tests/Failure' flowId='1234']
Tests: 3 failed, 1 risky, 2 todos, 1 skipped, 1 passed (3 assertions)
Duration: 1.00s
Tests: 3 failed, 1 risky, 2 todos, 1 skipped, 1 passed (3 assertions)
Duration: 1.00s

View File

@ -1,19 +1,38 @@
##teamcity[testSuiteStarted name='Tests/tests/SuccessOnly' locationHint='pest_qn://tests/.tests/SuccessOnly.php' flowId='1234']
##teamcity[testCount count='4' flowId='1234']
##teamcity[testSuiteStarted name='Tests/tests/SuccessOnly' locationHint='pest_qn://tests/.tests/SuccessOnly.php' flowId='1234']
##teamcity[testCount count='4' flowId='1234']
##teamcity[testStarted name='it can pass with comparison' locationHint='pest_qn://tests/.tests/SuccessOnly.php::it can pass with comparison' flowId='1234']
##teamcity[testStarted name='it can pass with comparison' locationHint='pest_qn://tests/.tests/SuccessOnly.php::it can pass with comparison' flowId='1234']
##teamcity[testFinished name='it can pass with comparison' duration='100000' flowId='1234']
##teamcity[testFinished name='it can pass with comparison' duration='100000' flowId='1234']
##teamcity[testStarted name='can also pass' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can also pass' flowId='1234']
##teamcity[testStarted name='can also pass' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can also pass' flowId='1234']
##teamcity[testFinished name='can also pass' duration='100000' flowId='1234']
##teamcity[testFinished name='can also pass' duration='100000' flowId='1234']
##teamcity[testSuiteStarted name='can pass with dataset' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can pass with dataset' flowId='1234']
##teamcity[testSuiteStarted name='can pass with dataset' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can pass with dataset' flowId='1234']
##teamcity[testStarted name='can pass with dataset with data set "(true)"' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can pass with dataset with data set "(true)"' flowId='1234']
##teamcity[testStarted name='can pass with dataset with data set "(true)"' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can pass with dataset with data set "(true)"' flowId='1234']
##teamcity[testFinished name='can pass with dataset with data set "(true)"' duration='100000' flowId='1234']
##teamcity[testFinished name='can pass with dataset with data set "(true)"' duration='100000' flowId='1234']
##teamcity[testSuiteFinished name='can pass with dataset' flowId='1234']
##teamcity[testSuiteFinished name='can pass with dataset' flowId='1234']
##teamcity[testSuiteStarted name='`block` → can pass with dataset in describe block' locationHint='pest_qn://tests/.tests/SuccessOnly.php::`block` → can pass with dataset in describe block' flowId='1234']
##teamcity[testSuiteStarted name='`block` → can pass with dataset in describe block' locationHint='pest_qn://tests/.tests/SuccessOnly.php::`block` → can pass with dataset in describe block' flowId='1234']
##teamcity[testStarted name='`block` → can pass with dataset in describe block with data set "(1)"' locationHint='pest_qn://tests/.tests/SuccessOnly.php::`block` → can pass with dataset in describe block with data set "(1)"' flowId='1234']
##teamcity[testStarted name='`block` → can pass with dataset in describe block with data set "(1)"' locationHint='pest_qn://tests/.tests/SuccessOnly.php::`block` → can pass with dataset in describe block with data set "(1)"' flowId='1234']
##teamcity[testFinished name='`block` → can pass with dataset in describe block with data set "(1)"' duration='100000' flowId='1234']
##teamcity[testFinished name='`block` → can pass with dataset in describe block with data set "(1)"' duration='100000' flowId='1234']
##teamcity[testSuiteFinished name='`block` → can pass with dataset in describe block' flowId='1234']
##teamcity[testSuiteFinished name='`block` → can pass with dataset in describe block' flowId='1234']
##teamcity[testSuiteFinished name='Tests/tests/SuccessOnly' flowId='1234']
##teamcity[testSuiteFinished name='Tests/tests/SuccessOnly' flowId='1234']
Tests: 4 passed (4 assertions)
Duration: 1.00s
Tests: 4 passed (4 assertions)
Duration: 1.00s

View File

@ -4,6 +4,7 @@
✓ preset → strict → ignoring ['Pest\Plugins\Tia\BaselineSync', 'usleep']
✓ preset → security → ignoring ['eval', 'str_shuffle', 'exec', …]
✓ globals
✓ contracts
PASS Tests\Environments\Windows
✓ global functions are loaded
@ -1937,4 +1938,4 @@
✓ pass with dataset with ('my-datas-set-value')
✓ within describe → pass with dataset with ('my-datas-set-value')
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1328 passed (3008 assertions)
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1329 passed (3010 assertions)

View File

@ -33,3 +33,13 @@ arch('globals')
->expect(['dd', 'dump', 'ray', 'die', 'var_dump', 'sleep'])
->not->toBeUsed()
->ignoring(Expectation::class);
arch('contracts')
->expect('Pest\Contracts')
->toOnlyUse([
'NunoMaduro\Collision\Contracts',
'Pest\Factories\TestCaseMethodFactory',
'Symfony\Component\Console',
'Pest\Arch\Contracts',
'Pest\PendingCalls',
])->toBeInterfaces();

View File

@ -24,13 +24,13 @@ test('parallel', function () use ($run) {
$file = file_get_contents(__FILE__);
$file = preg_replace(
'/\$expected = \'.*?\';/',
"\$expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1312 passed (2957 assertions)';",
"\$expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1313 passed (2959 assertions)';",
$file,
);
file_put_contents(__FILE__, $file);
}
$expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1312 passed (2957 assertions)';
$expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1313 passed (2959 assertions)';
expect($output)
->toContain("Tests: {$expected}")