mirror of
https://github.com/pestphp/pest.git
synced 2026-09-05 14:23:34 +02:00
Compare commits
49 Commits
ad1850b110
...
v5.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 208f447a10 | |||
| 6b7ca13660 | |||
| bf7c6b3134 | |||
| 617226ca01 | |||
| 375b92e93b | |||
| e0e00ca748 | |||
| d70ecb873e | |||
| 638932ddb5 | |||
| ac48185736 | |||
| 2e4f8ad56f | |||
| 8c554a81e2 | |||
| 42d9b777bf | |||
| eb88513baf | |||
| 7b6415ccd0 | |||
| a5d726b678 | |||
| 93223caa56 | |||
| d0572ce138 | |||
| b0d1fc589d | |||
| 5a4f124199 | |||
| c3ceb7d0c7 | |||
| 16d1208344 | |||
| 1cebc84c2d | |||
| 1f79660add | |||
| 37bdf60a1c | |||
| cc7acfe485 | |||
| 7bfb2a6185 | |||
| ca56ca5216 | |||
| b49ba062d5 | |||
| 09699847a2 | |||
| 545d0c2784 | |||
| b795af3b1b | |||
| a40cc6bc0e | |||
| 4d3d0105b7 | |||
| e90e4f70fc | |||
| d112582857 | |||
| 9f3c4e1e82 | |||
| 71f39366c2 | |||
| bfd5b75677 | |||
| 411b9954b5 | |||
| db70017cb2 | |||
| 86adaedbbd | |||
| 92c7677c6e | |||
| 872f0a50c2 | |||
| 668809bc75 | |||
| 8d8f45c843 | |||
| 086b3e9107 | |||
| 43fe26f324 | |||
| 865c1e5113 | |||
| 19eed8d581 |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
.idea/*
|
.idea/*
|
||||||
.idea/codeStyleSettings.xml
|
.idea/codeStyleSettings.xml
|
||||||
.temp/*
|
.temp/*
|
||||||
composer.lock
|
/composer.lock
|
||||||
/vendor/
|
/vendor/
|
||||||
coverage.xml
|
coverage.xml
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
**Do not edit this file.** Agents must never add, remove, or reword anything here. If a change seems needed, say so and let a human do it.
|
||||||
|
|
||||||
|
## Ask before testing
|
||||||
|
|
||||||
|
When asked to review code or build a feature, do not run the test suite and do not write new tests. Make the change, report it, then ask the user whether tests should be added — describing the tests you have in mind — and wait for the user to confirm.
|
||||||
|
|
||||||
|
Two reasons this matters here: the suite takes minutes, and `tests/.snapshots/success.txt` plus the tally in `tests/Visual/Parallel.php` encode the whole suite's result, so a single added test breaks both.
|
||||||
|
|
||||||
|
Once the user confirms:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer test:unit # fast, excludes the visual group
|
||||||
|
composer test:integration # visual and snapshot tests
|
||||||
|
composer test # everything CI runs, in CI's order
|
||||||
|
composer update:snapshots # only when a test was added or removed
|
||||||
|
```
|
||||||
|
|
||||||
|
## TIA scenario tests
|
||||||
|
|
||||||
|
`tests/Features/Tia/*` scaffold a throwaway git project, run a real `pest` subprocess against it, and diff the TIA graph it wrote. They exist because TIA's contract is about what a run *writes* — replay, branch keys, and the COMPLETE / RESULTS-ONLY / HARD-SUPPRESSED tiers are invisible to ordinary assertions, and every case used to be measured by hand against a playground app.
|
||||||
|
|
||||||
|
Add one whenever a change touches branch resolution, replay, filtered mode, or the write tiers. How:
|
||||||
|
|
||||||
|
- `Project::make('master')` scaffolds; `seed('master')` writes a graph and sentinels every cached result (`time=9.999`, `assertions=42`) so any rewrite shows up.
|
||||||
|
- `$project->pest('--tia', …)` runs it; `$project->delta()` compares against that snapshot. `writtenCount()` is the discriminator — `0` means "replayed", not "wrote the same values". `mutateGraph()` bends one entry; overlays in `tests/Fixtures/Tia/overlays/<name>/` supply a different `tests/Pest.php`.
|
||||||
|
- Keep expectations driver-independent: a cold recording run needs pcov/xdebug and behaves differently without one. Seed a graph instead of recording one.
|
||||||
|
|
||||||
|
Run them by file (a directory argument finds nothing) or by `--filter`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php bin/pest tests/Features/Tia/PartialRunWriteTier.php
|
||||||
|
```
|
||||||
@@ -29,13 +29,18 @@
|
|||||||
|
|
||||||
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)**.
|
||||||
|
|
||||||
- **[CMS Max](https://cmsmax.com/?ref=pestphp)**
|
|
||||||
- **[PhpStorm](https://jb.gg/nuno)**
|
- **[PhpStorm](https://jb.gg/nuno)**
|
||||||
- **[CodeRabbit](https://coderabbit.link/nunomaduro)**
|
- **[CodeRabbit](https://coderabbit.link/nunomaduro)**
|
||||||
- **[SerpApi](https://serpapi.com/?ref=nunomaduro)**
|
- **[SerpApi](https://serpapi.com/?ref=nunomaduro)**
|
||||||
- **[Typesense](https://typesense.org/?ref=nunomaduro)**
|
|
||||||
- **[Bento](https://bentonow.com/?ref=nunomaduro)**
|
- **[Bento](https://bentonow.com/?ref=nunomaduro)**
|
||||||
|
- **[CMS Max](https://cmsmax.com/?ref=nunomaduro)**
|
||||||
|
- **[Redberry](https://redberry.international/laravel-development/?utm_source=pest&utm_medium=banner&utm_campaign=pest_sponsorship)**
|
||||||
|
- **[Typesense](https://typesense.org/?ref=nunomaduro)**
|
||||||
- **[Pixel](https://wearepixel.com.au/?ref=nunomaduro)**
|
- **[Pixel](https://wearepixel.com.au/?ref=nunomaduro)**
|
||||||
- **[Redberry](https://redberry.international/laravel-development/?ref=nunomaduro)**
|
- [TestMu AI](https://www.testmuai.com/?utm_medium=sponsor&utm_source=pest)
|
||||||
|
- [Zapiet](https://zapiet.com/?ref=nunomaduro)
|
||||||
|
- [LoadForge](https://loadforge.com/?ref=nunomaduro)
|
||||||
|
- [Route4Me](https://route4me.com/?ref=nunomaduro)
|
||||||
|
- [Nerdify](https://getnerdify.com/?ref=nunomaduro)
|
||||||
|
|
||||||
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)**.
|
||||||
|
|||||||
@@ -190,6 +190,24 @@ async function listPageFiles(pagesDir) {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `existsSync()` ignores casing on APFS and NTFS, and on a macOS bind mount seen from
|
||||||
|
// inside a Linux container, so the `resources/js/Pages` candidate resolves in a project
|
||||||
|
// whose directory is really `resources/js/pages`. Every page path derives from the
|
||||||
|
// directory accepted below, so a wrong-cased one detaches the pages tree from the paths
|
||||||
|
// the PHP side reports. `readdir()` is case-exact everywhere.
|
||||||
|
export async function matchesDiskCasing(projectRoot, rel) {
|
||||||
|
let current = projectRoot
|
||||||
|
|
||||||
|
for (const segment of rel.split('/')) {
|
||||||
|
let entries
|
||||||
|
try { entries = await readdir(current) } catch { return false }
|
||||||
|
if (!entries.includes(segment)) return false
|
||||||
|
current = join(current, segment)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
async function discoverPagesDir() {
|
async function discoverPagesDir() {
|
||||||
const override = process.env.TIA_VITE_PAGES_DIR
|
const override = process.env.TIA_VITE_PAGES_DIR
|
||||||
if (override && override.length > 0) {
|
if (override && override.length > 0) {
|
||||||
@@ -199,6 +217,7 @@ async function discoverPagesDir() {
|
|||||||
for (const rel of PAGE_DIR_CANDIDATES) {
|
for (const rel of PAGE_DIR_CANDIDATES) {
|
||||||
const abs = resolve(PROJECT_ROOT, rel)
|
const abs = resolve(PROJECT_ROOT, rel)
|
||||||
if (!existsSync(abs)) continue
|
if (!existsSync(abs)) continue
|
||||||
|
if (!(await matchesDiskCasing(PROJECT_ROOT, rel))) continue
|
||||||
const files = await listPageFiles(abs)
|
const files = await listPageFiles(abs)
|
||||||
if (files.length > 0) return abs
|
if (files.length > 0) return abs
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ $bootPest = (static function (): void {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It must be a 1 byte string to ensure filesize() is equal to the number of tests executed
|
|
||||||
$exitCode = $application->runTest(realpath(trim($testPath)));
|
$exitCode = $application->runTest(realpath(trim($testPath)));
|
||||||
|
|
||||||
fwrite($statusFile, (string) $exitCode);
|
fwrite($statusFile, (string) $exitCode);
|
||||||
|
|||||||
+12
-10
@@ -18,19 +18,19 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.4",
|
"php": "^8.4",
|
||||||
"brianium/paratest": "^7.23.1",
|
"brianium/paratest": "^7.24.0",
|
||||||
"nunomaduro/collision": "^8.9.5",
|
"nunomaduro/collision": "^8.9.5",
|
||||||
"nunomaduro/termwind": "^2.4.0",
|
"nunomaduro/termwind": "^2.4.0",
|
||||||
"pestphp/pest-plugin": "^5.0.0",
|
"pestphp/pest-plugin": "^5.0.0",
|
||||||
"pestphp/pest-plugin-arch": "^5.0.0",
|
"pestphp/pest-plugin-arch": "^5.0.0",
|
||||||
"pestphp/pest-plugin-mutate": "^5.0.0",
|
"pestphp/pest-plugin-mutate": "^5.0.2",
|
||||||
"pestphp/pest-plugin-profanity": "^5.0.0",
|
"pestphp/pest-plugin-profanity": "^5.0.0",
|
||||||
"phpunit/phpunit": "^13.2.6",
|
"phpunit/phpunit": "^13.3.0",
|
||||||
"symfony/process": "^8.1.0"
|
"symfony/process": "^8.1.0"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"filp/whoops": "<2.18.3",
|
"filp/whoops": "<2.18.3",
|
||||||
"phpunit/phpunit": ">13.2.6",
|
"phpunit/phpunit": ">13.3.0",
|
||||||
"sebastian/exporter": "<7.0.0",
|
"sebastian/exporter": "<7.0.0",
|
||||||
"webmozart/assert": "<1.11.0"
|
"webmozart/assert": "<1.11.0"
|
||||||
},
|
},
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
"Tests\\Fixtures\\Covers\\": "tests/Fixtures/Covers",
|
"Tests\\Fixtures\\Covers\\": "tests/Fixtures/Covers",
|
||||||
"Tests\\Fixtures\\Inheritance\\": "tests/Fixtures/Inheritance",
|
"Tests\\Fixtures\\Inheritance\\": "tests/Fixtures/Inheritance",
|
||||||
"Tests\\Fixtures\\Arch\\": "tests/Fixtures/Arch",
|
"Tests\\Fixtures\\Arch\\": "tests/Fixtures/Arch",
|
||||||
|
"Tests\\Fixtures\\Tia\\": "tests/Fixtures/Tia",
|
||||||
"Tests\\": "tests/PHPUnit/"
|
"Tests\\": "tests/PHPUnit/"
|
||||||
},
|
},
|
||||||
"classmap": [
|
"classmap": [
|
||||||
@@ -58,12 +59,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"laravel/pao": "^1.1.3",
|
|
||||||
"pestphp/pest-dev-tools": "^5.0.0",
|
"pestphp/pest-dev-tools": "^5.0.0",
|
||||||
"pestphp/pest-plugin-browser": "^5.0.0",
|
"pestphp/pest-plugin-browser": "^5.0.1",
|
||||||
"pestphp/pest-plugin-phpstan": "^5.0.0",
|
"pestphp/pest-plugin-phpstan": "^5.0.2",
|
||||||
"pestphp/pest-plugin-rector": "^5.0.0",
|
"pestphp/pest-plugin-rector": "^5.0.3",
|
||||||
"pestphp/pest-plugin-type-coverage": "^5.0.0",
|
"pestphp/pest-plugin-type-coverage": "^5.0.2",
|
||||||
"psy/psysh": "^0.12.24"
|
"psy/psysh": "^0.12.24"
|
||||||
},
|
},
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
@@ -71,6 +71,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
"preferred-install": "dist",
|
"preferred-install": "dist",
|
||||||
|
"process-timeout": 0,
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"pestphp/pest-plugin": true
|
"pestphp/pest-plugin": true
|
||||||
}
|
}
|
||||||
@@ -94,7 +95,8 @@
|
|||||||
"test:inline": "php bin/pest --configuration=phpunit.inline.xml",
|
"test:inline": "php bin/pest --configuration=phpunit.inline.xml",
|
||||||
"test:parallel": "php bin/pest --exclude-group=integration --parallel --processes=3",
|
"test:parallel": "php bin/pest --exclude-group=integration --parallel --processes=3",
|
||||||
"test:integration": "php bin/pest --group=integration -v",
|
"test:integration": "php bin/pest --group=integration -v",
|
||||||
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --update-snapshots",
|
"test:tia": "php bin/pest --group=tia -v",
|
||||||
|
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --update-snapshots --exclude-group=tia",
|
||||||
"test": [
|
"test": [
|
||||||
"@test:lint",
|
"@test:lint",
|
||||||
"@test:type:check",
|
"@test:type:check",
|
||||||
|
|||||||
@@ -1,37 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,37 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -86,7 +54,7 @@ final class JunitXmlLogger
|
|||||||
{
|
{
|
||||||
private readonly Printer $printer;
|
private readonly Printer $printer;
|
||||||
|
|
||||||
private readonly Converter $converter; // pest-added
|
private readonly Converter $converter;
|
||||||
|
|
||||||
private DOMDocument $document;
|
private DOMDocument $document;
|
||||||
|
|
||||||
@@ -144,8 +112,7 @@ final class JunitXmlLogger
|
|||||||
public function __construct(Printer $printer, Facade $facade)
|
public function __construct(Printer $printer, Facade $facade)
|
||||||
{
|
{
|
||||||
$this->printer = $printer;
|
$this->printer = $printer;
|
||||||
$this->converter = new Converter(Container::getInstance()->get(TestSuite::class)->rootPath); // pest-added
|
$this->converter = new Converter(Container::getInstance()->get(TestSuite::class)->rootPath);
|
||||||
|
|
||||||
$this->registerSubscribers($facade);
|
$this->registerSubscribers($facade);
|
||||||
$this->createDocument();
|
$this->createDocument();
|
||||||
}
|
}
|
||||||
@@ -160,10 +127,9 @@ final class JunitXmlLogger
|
|||||||
public function testSuiteStarted(Started $event): void
|
public function testSuiteStarted(Started $event): void
|
||||||
{
|
{
|
||||||
$testSuite = $this->document->createElement('testsuite');
|
$testSuite = $this->document->createElement('testsuite');
|
||||||
$testSuite->setAttribute('name', $this->converter->getTestSuiteName($event->testSuite())); // pest-changed
|
$testSuite->setAttribute('name', $this->converter->getTestSuiteName($event->testSuite()));
|
||||||
|
|
||||||
if ($event->testSuite()->isForTestClass()) {
|
if ($event->testSuite()->isForTestClass()) {
|
||||||
$testSuite->setAttribute('file', $this->converter->getTestSuiteLocation($event->testSuite()) ?? ''); // pest-changed
|
$testSuite->setAttribute('file', $this->converter->getTestSuiteLocation($event->testSuite()) ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->testSuiteLevel > 0) {
|
if ($this->testSuiteLevel > 0) {
|
||||||
@@ -380,12 +346,11 @@ final class JunitXmlLogger
|
|||||||
|
|
||||||
assert($this->currentTestCase !== null);
|
assert($this->currentTestCase !== null);
|
||||||
|
|
||||||
$buffer = $this->converter->getTestCaseMethodName($event->test()); // pest-changed
|
$buffer = $this->converter->getTestCaseMethodName($event->test());
|
||||||
|
|
||||||
$throwable = $event->throwable();
|
$throwable = $event->throwable();
|
||||||
$buffer .= trim(
|
$buffer .= trim(
|
||||||
$this->converter->getExceptionMessage($throwable).PHP_EOL. // pest-changed
|
$this->converter->getExceptionMessage($throwable).PHP_EOL.
|
||||||
$this->converter->getExceptionDetails($throwable), // pest-changed
|
$this->converter->getExceptionDetails($throwable),
|
||||||
);
|
);
|
||||||
|
|
||||||
$fault = $this->document->createElement(
|
$fault = $this->document->createElement(
|
||||||
@@ -485,18 +450,15 @@ final class JunitXmlLogger
|
|||||||
$testCase = $this->document->createElement('testcase');
|
$testCase = $this->document->createElement('testcase');
|
||||||
|
|
||||||
$test = $event->test();
|
$test = $event->test();
|
||||||
$file = $this->converter->getTestCaseLocation($test); // pest-added
|
$file = $this->converter->getTestCaseLocation($test);
|
||||||
|
$testCase->setAttribute('name', $this->converter->getTestCaseMethodName($test));
|
||||||
$testCase->setAttribute('name', $this->converter->getTestCaseMethodName($test)); // pest-changed
|
$testCase->setAttribute('file', $file);
|
||||||
$testCase->setAttribute('file', $file); // pest-changed
|
|
||||||
|
|
||||||
if ($test->isTestMethod()) {
|
if ($test->isTestMethod()) {
|
||||||
assert($test instanceof TestMethod);
|
assert($test instanceof TestMethod);
|
||||||
|
|
||||||
// $testCase->setAttribute('line', (string) $test->line()); // pest-removed
|
$className = $this->converter->getTrimmedTestClassName($test);
|
||||||
$className = $this->converter->getTrimmedTestClassName($test); // pest-added
|
$testCase->setAttribute('class', $className);
|
||||||
$testCase->setAttribute('class', $className); // pest-changed
|
$testCase->setAttribute('classname', str_replace('\\', '.', $className));
|
||||||
$testCase->setAttribute('classname', str_replace('\\', '.', $className)); // pest-changed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->currentTestCase = $testCase;
|
$this->currentTestCase = $testCase;
|
||||||
|
|||||||
@@ -1,29 +1,5 @@
|
|||||||
<?php
|
<?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);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace ParaTest\WrapperRunner;
|
namespace ParaTest\WrapperRunner;
|
||||||
@@ -34,10 +10,6 @@ use function preg_match;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @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
|
final readonly class ProgressPrinterOutput implements Printer
|
||||||
{
|
{
|
||||||
@@ -48,7 +20,6 @@ final readonly class ProgressPrinterOutput implements Printer
|
|||||||
|
|
||||||
public function print(string $buffer): void
|
public function print(string $buffer): void
|
||||||
{
|
{
|
||||||
// Skip anything in \PHPUnit\TextUI\Output\Default\ProgressPrinter\ProgressPrinter::printProgress except $progress
|
|
||||||
if (
|
if (
|
||||||
$buffer === "\n"
|
$buffer === "\n"
|
||||||
|| preg_match('/^ +$/', $buffer) === 1
|
|| preg_match('/^ +$/', $buffer) === 1
|
||||||
|
|||||||
@@ -1,37 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -48,42 +16,32 @@ namespace PHPUnit\Runner\Filter;
|
|||||||
use Pest\Contracts\HasPrintableTestCaseName;
|
use Pest\Contracts\HasPrintableTestCaseName;
|
||||||
use PHPUnit\Framework\Test;
|
use PHPUnit\Framework\Test;
|
||||||
use PHPUnit\Framework\TestSuite;
|
use PHPUnit\Framework\TestSuite;
|
||||||
use PHPUnit\Runner\PhptTestCase;
|
use PHPUnit\Runner\Phpt\TestCase as PhptTestCase;
|
||||||
use RecursiveFilterIterator;
|
use RecursiveFilterIterator;
|
||||||
use RecursiveIterator;
|
use RecursiveIterator;
|
||||||
|
|
||||||
use function end;
|
use function end;
|
||||||
use function preg_match;
|
use function preg_match;
|
||||||
use function sprintf;
|
use function trim;
|
||||||
use function str_replace;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @extends RecursiveFilterIterator<int, Test, RecursiveIterator<int, Test>>
|
||||||
|
*
|
||||||
* @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
|
||||||
*/
|
*/
|
||||||
abstract class NameFilterIterator extends RecursiveFilterIterator
|
abstract class NameFilterIterator extends RecursiveFilterIterator
|
||||||
{
|
{
|
||||||
/**
|
private readonly CompiledNameFilter $filter;
|
||||||
* @psalm-var non-empty-string
|
|
||||||
*/
|
|
||||||
private readonly string $regularExpression;
|
|
||||||
|
|
||||||
private readonly ?int $dataSetMinimum;
|
|
||||||
|
|
||||||
private readonly ?int $dataSetMaximum;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @psalm-param RecursiveIterator<int, Test> $iterator
|
* @param RecursiveIterator<int, Test> $iterator
|
||||||
* @psalm-param non-empty-string $filter
|
* @param non-empty-string $filter
|
||||||
*/
|
*/
|
||||||
public function __construct(RecursiveIterator $iterator, string $filter)
|
public function __construct(RecursiveIterator $iterator, string $filter)
|
||||||
{
|
{
|
||||||
parent::__construct($iterator);
|
parent::__construct($iterator);
|
||||||
|
|
||||||
$preparedFilter = $this->prepareFilter($filter);
|
$this->filter = CompiledNameFilter::from($filter);
|
||||||
|
|
||||||
$this->regularExpression = $preparedFilter['regularExpression'];
|
|
||||||
$this->dataSetMinimum = $preparedFilter['dataSetMinimum'];
|
|
||||||
$this->dataSetMaximum = $preparedFilter['dataSetMaximum'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function accept(): bool
|
public function accept(): bool
|
||||||
@@ -106,75 +64,15 @@ abstract class NameFilterIterator extends RecursiveFilterIterator
|
|||||||
$name = $test::class.'::'.$test->nameWithDataSet();
|
$name = $test::class.'::'.$test->nameWithDataSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
$accepted = @preg_match($this->regularExpression, $name, $matches) === 1;
|
$accepted = @preg_match($this->filter->regularExpression(), $name, $matches) === 1;
|
||||||
|
|
||||||
if ($accepted && isset($this->dataSetMaximum)) {
|
if ($accepted && $this->filter->hasDataSetRange()) {
|
||||||
$set = end($matches);
|
$set = end($matches);
|
||||||
$accepted = $set >= $this->dataSetMinimum && $set <= $this->dataSetMaximum;
|
$accepted = $set >= $this->filter->dataSetMinimum() && $set <= $this->filter->dataSetMaximum();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->doAccept($accepted);
|
return $this->doAccept($accepted);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract protected function doAccept(bool $result): bool;
|
abstract protected function doAccept(bool $result): bool;
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-param non-empty-string $filter
|
|
||||||
*
|
|
||||||
* @psalm-return array{regularExpression: non-empty-string, dataSetMinimum: ?int, dataSetMaximum: ?int}
|
|
||||||
*/
|
|
||||||
private function prepareFilter(string $filter): array
|
|
||||||
{
|
|
||||||
$dataSetMinimum = null;
|
|
||||||
$dataSetMaximum = null;
|
|
||||||
|
|
||||||
if (@preg_match($filter, '') === false) {
|
|
||||||
// Handles:
|
|
||||||
// * testAssertEqualsSucceeds#4
|
|
||||||
// * testAssertEqualsSucceeds#4-8
|
|
||||||
if (preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) {
|
|
||||||
if (isset($matches[3]) && $matches[2] < $matches[3]) {
|
|
||||||
$filter = sprintf(
|
|
||||||
'%s.*with data set #(\d+)$',
|
|
||||||
$matches[1],
|
|
||||||
);
|
|
||||||
|
|
||||||
$dataSetMinimum = (int) $matches[2];
|
|
||||||
$dataSetMaximum = (int) $matches[3];
|
|
||||||
} else {
|
|
||||||
$filter = sprintf(
|
|
||||||
'%s.*with data set #%s$',
|
|
||||||
$matches[1],
|
|
||||||
$matches[2],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} // Handles:
|
|
||||||
// * testDetermineJsonError@JSON_ERROR_NONE
|
|
||||||
// * testDetermineJsonError@JSON.*
|
|
||||||
elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches)) {
|
|
||||||
$filter = sprintf(
|
|
||||||
'%s.*with data set "%s"$',
|
|
||||||
$matches[1],
|
|
||||||
$matches[2],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Escape delimiters in regular expression. Do NOT use preg_quote,
|
|
||||||
// to keep magic characters.
|
|
||||||
$filter = sprintf(
|
|
||||||
'/%s/i',
|
|
||||||
str_replace(
|
|
||||||
'/',
|
|
||||||
'\\/',
|
|
||||||
$filter,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'regularExpression' => $filter,
|
|
||||||
'dataSetMinimum' => $dataSetMinimum,
|
|
||||||
'dataSetMaximum' => $dataSetMaximum,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,206 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of PHPUnit.
|
|
||||||
*
|
|
||||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace PHPUnit\Runner\ResultCache;
|
|
||||||
|
|
||||||
use const DIRECTORY_SEPARATOR;
|
|
||||||
use const LOCK_EX;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestStatus\TestStatus;
|
|
||||||
use PHPUnit\Runner\DirectoryDoesNotExistException;
|
|
||||||
use PHPUnit\Runner\Exception;
|
|
||||||
use PHPUnit\Util\Filesystem;
|
|
||||||
|
|
||||||
use function array_keys;
|
|
||||||
use function assert;
|
|
||||||
use function dirname;
|
|
||||||
use function file_get_contents;
|
|
||||||
use function file_put_contents;
|
|
||||||
use function is_array;
|
|
||||||
use function is_dir;
|
|
||||||
use function is_file;
|
|
||||||
use function json_decode;
|
|
||||||
use function json_encode;
|
|
||||||
use function Pest\version;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
|
|
||||||
*
|
|
||||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
|
||||||
*/
|
|
||||||
final class DefaultResultCache implements ResultCache
|
|
||||||
{
|
|
||||||
private const string DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache';
|
|
||||||
|
|
||||||
private readonly string $cacheFilename;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<string, TestStatus>
|
|
||||||
*/
|
|
||||||
private array $defects = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<string, float>
|
|
||||||
*/
|
|
||||||
private array $times = [];
|
|
||||||
|
|
||||||
public function __construct(?string $filepath = null)
|
|
||||||
{
|
|
||||||
if ($filepath !== null && is_dir($filepath)) {
|
|
||||||
$filepath .= DIRECTORY_SEPARATOR.self::DEFAULT_RESULT_CACHE_FILENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setStatus(ResultCacheId $id, TestStatus $status): void
|
|
||||||
{
|
|
||||||
if ($status->isSuccess()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->defects[$id->asString()] = $status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function status(ResultCacheId $id): TestStatus
|
|
||||||
{
|
|
||||||
return $this->defects[$id->asString()] ?? TestStatus::unknown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTime(ResultCacheId $id, float $time): void
|
|
||||||
{
|
|
||||||
$this->times[$id->asString()] = $time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function time(ResultCacheId $id): float
|
|
||||||
{
|
|
||||||
return $this->times[$id->asString()] ?? 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function mergeWith(self $other): void
|
|
||||||
{
|
|
||||||
foreach ($other->defects as $id => $defect) {
|
|
||||||
$this->defects[$id] = $defect;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($other->times as $id => $time) {
|
|
||||||
$this->times[$id] = $time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function load(): void
|
|
||||||
{
|
|
||||||
if (! is_file($this->cacheFilename)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$contents = file_get_contents($this->cacheFilename);
|
|
||||||
|
|
||||||
if ($contents === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = json_decode(
|
|
||||||
$contents,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($data === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! isset($data['version'])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($data['version'] !== $this->cacheVersion()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(isset($data['defects']) && is_array($data['defects']));
|
|
||||||
assert(isset($data['times']) && is_array($data['times']));
|
|
||||||
|
|
||||||
foreach (array_keys($data['defects']) as $test) {
|
|
||||||
$data['defects'][$test] = TestStatus::from($data['defects'][$test]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->defects = $data['defects'];
|
|
||||||
$this->times = $data['times'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function persist(): void
|
|
||||||
{
|
|
||||||
if (! Filesystem::createDirectory(dirname($this->cacheFilename))) {
|
|
||||||
throw new DirectoryDoesNotExistException(dirname($this->cacheFilename));
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'version' => $this->cacheVersion(),
|
|
||||||
'defects' => [],
|
|
||||||
'times' => $this->times,
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($this->defects as $test => $status) {
|
|
||||||
$data['defects'][$test] = $status->asInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents(
|
|
||||||
$this->cacheFilename,
|
|
||||||
json_encode($data),
|
|
||||||
LOCK_EX,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the cache version.
|
|
||||||
*/
|
|
||||||
private function cacheVersion(): string
|
|
||||||
{
|
|
||||||
return 'pest_'.version();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of PHPUnit.
|
||||||
|
*
|
||||||
|
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace PHPUnit\Runner\TestRunHistory;
|
||||||
|
|
||||||
|
use const DIRECTORY_SEPARATOR;
|
||||||
|
use const LOCK_EX;
|
||||||
|
use const LOCK_UN;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestStatus\TestStatus;
|
||||||
|
use PHPUnit\Runner\DirectoryDoesNotExistException;
|
||||||
|
use PHPUnit\Runner\Exception;
|
||||||
|
use PHPUnit\Util\Filesystem;
|
||||||
|
|
||||||
|
use function array_keys;
|
||||||
|
use function dirname;
|
||||||
|
use function fclose;
|
||||||
|
use function file_get_contents;
|
||||||
|
use function flock;
|
||||||
|
use function fopen;
|
||||||
|
use function ftruncate;
|
||||||
|
use function fwrite;
|
||||||
|
use function is_array;
|
||||||
|
use function is_dir;
|
||||||
|
use function is_file;
|
||||||
|
use function is_float;
|
||||||
|
use function is_int;
|
||||||
|
use function is_string;
|
||||||
|
use function json_decode;
|
||||||
|
use function json_encode;
|
||||||
|
use function Pest\version;
|
||||||
|
use function rewind;
|
||||||
|
use function stream_get_contents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||||
|
*/
|
||||||
|
final class DefaultTestRunHistory implements TestRunHistory
|
||||||
|
{
|
||||||
|
private const string DEFAULT_FILENAME = '.phpunit.result.cache';
|
||||||
|
|
||||||
|
private readonly string $filename;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, TestStatus>
|
||||||
|
*/
|
||||||
|
private array $defects = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, float>
|
||||||
|
*/
|
||||||
|
private array $times = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, true>
|
||||||
|
*/
|
||||||
|
private array $changedDefects = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, true>
|
||||||
|
*/
|
||||||
|
private array $changedTimes = [];
|
||||||
|
|
||||||
|
public function __construct(?string $filepath = null)
|
||||||
|
{
|
||||||
|
if ($filepath !== null && is_dir($filepath)) {
|
||||||
|
$filepath .= DIRECTORY_SEPARATOR.self::DEFAULT_FILENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->filename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_FILENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStatus(TestRunHistoryId $id, TestStatus $status): void
|
||||||
|
{
|
||||||
|
if ($status->isSuccess()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->defects[$id->asString()] = $status;
|
||||||
|
$this->changedDefects[$id->asString()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(TestRunHistoryId $id): void
|
||||||
|
{
|
||||||
|
unset($this->defects[$id->asString()]);
|
||||||
|
|
||||||
|
$this->changedDefects[$id->asString()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function status(TestRunHistoryId $id): TestStatus
|
||||||
|
{
|
||||||
|
return $this->defects[$id->asString()] ?? TestStatus::unknown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTime(TestRunHistoryId $id, float $time): void
|
||||||
|
{
|
||||||
|
$this->times[$id->asString()] = $time;
|
||||||
|
$this->changedTimes[$id->asString()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function time(TestRunHistoryId $id): float
|
||||||
|
{
|
||||||
|
return $this->times[$id->asString()] ?? 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mergeWith(self $other): void
|
||||||
|
{
|
||||||
|
foreach ($other->defects as $id => $defect) {
|
||||||
|
$this->defects[$id] = $defect;
|
||||||
|
$this->changedDefects[$id] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($other->times as $id => $time) {
|
||||||
|
$this->times[$id] = $time;
|
||||||
|
$this->changedTimes[$id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function load(): void
|
||||||
|
{
|
||||||
|
if (! is_file($this->filename)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = file_get_contents($this->filename);
|
||||||
|
|
||||||
|
if ($contents === false) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
return;
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsed = $this->parse($contents);
|
||||||
|
|
||||||
|
if ($parsed === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$this->defects, $this->times] = $parsed;
|
||||||
|
|
||||||
|
$this->changedDefects = [];
|
||||||
|
$this->changedTimes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function persist(): void
|
||||||
|
{
|
||||||
|
$this->writeToFile(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function persistAndPrune(): void
|
||||||
|
{
|
||||||
|
$this->writeToFile(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private function writeToFile(bool $prune): void
|
||||||
|
{
|
||||||
|
if (! Filesystem::createDirectory(dirname($this->filename))) {
|
||||||
|
throw new DirectoryDoesNotExistException(dirname($this->filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
$handle = fopen($this->filename, 'c+');
|
||||||
|
|
||||||
|
if ($handle === false) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
return;
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
flock($handle, LOCK_EX);
|
||||||
|
|
||||||
|
if ($prune) {
|
||||||
|
$defects = [];
|
||||||
|
|
||||||
|
foreach ($this->defects as $id => $status) {
|
||||||
|
if (isset($this->changedDefects[$id])) {
|
||||||
|
$defects[$id] = $status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$times = [];
|
||||||
|
|
||||||
|
foreach ($this->times as $id => $time) {
|
||||||
|
if (isset($this->changedTimes[$id]) || isset($this->changedDefects[$id])) {
|
||||||
|
$times[$id] = $time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$parsed = $this->parse((string) stream_get_contents($handle));
|
||||||
|
|
||||||
|
if ($parsed !== null) {
|
||||||
|
[$defects, $times] = $parsed;
|
||||||
|
|
||||||
|
foreach (array_keys($this->changedDefects) as $id) {
|
||||||
|
if (isset($this->defects[$id])) {
|
||||||
|
$defects[$id] = $this->defects[$id];
|
||||||
|
} else {
|
||||||
|
unset($defects[$id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->times as $id => $time) {
|
||||||
|
if (isset($this->changedTimes[$id])) {
|
||||||
|
$times[$id] = $time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$defects = $this->defects;
|
||||||
|
$times = $this->times;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'version' => $this->version(),
|
||||||
|
'defects' => [],
|
||||||
|
'times' => $times,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($defects as $test => $status) {
|
||||||
|
$data['defects'][$test] = $status->asInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_encode($data);
|
||||||
|
|
||||||
|
if ($json !== false) {
|
||||||
|
ftruncate($handle, 0);
|
||||||
|
rewind($handle);
|
||||||
|
fwrite($handle, $json);
|
||||||
|
}
|
||||||
|
|
||||||
|
flock($handle, LOCK_UN);
|
||||||
|
fclose($handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ?array{0: array<string, TestStatus>, 1: array<string, float>}
|
||||||
|
*/
|
||||||
|
private function parse(string $contents): ?array
|
||||||
|
{
|
||||||
|
$data = json_decode(
|
||||||
|
$contents,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! is_array($data)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($data['version']) || $data['version'] !== $this->version()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($data['defects'], $data['times']) || ! is_array($data['defects']) || ! is_array($data['times'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$defects = [];
|
||||||
|
|
||||||
|
foreach ($data['defects'] as $test => $status) {
|
||||||
|
if (! is_string($test) || ! is_int($status)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$defects[$test] = TestStatus::from($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
$times = [];
|
||||||
|
|
||||||
|
foreach ($data['times'] as $test => $time) {
|
||||||
|
if (! is_string($test) || (! is_float($time) && ! is_int($time))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$times[$test] = (float) $time;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$defects, $times];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function version(): string
|
||||||
|
{
|
||||||
|
return 'pest_'.version();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,37 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace PHPUnit\Runner;
|
namespace PHPUnit\Runner;
|
||||||
@@ -90,11 +58,11 @@ final class TestSuiteLoader
|
|||||||
(static function () use ($suiteClassFile) {
|
(static function () use ($suiteClassFile) {
|
||||||
try {
|
try {
|
||||||
include_once $suiteClassFile;
|
include_once $suiteClassFile;
|
||||||
|
|
||||||
|
TestSuite::getInstance()->tests->makeIfNeeded($suiteClassFile);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
Panic::with($e);
|
Panic::with($e);
|
||||||
}
|
}
|
||||||
|
|
||||||
TestSuite::getInstance()->tests->makeIfNeeded($suiteClassFile);
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
$loadedClasses = array_values(
|
$loadedClasses = array_values(
|
||||||
|
|||||||
@@ -1,37 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -46,13 +14,14 @@ declare(strict_types=1);
|
|||||||
namespace PHPUnit\Runner;
|
namespace PHPUnit\Runner;
|
||||||
|
|
||||||
use PHPUnit\Framework\DataProviderTestSuite;
|
use PHPUnit\Framework\DataProviderTestSuite;
|
||||||
|
use PHPUnit\Framework\IterativeTestSuite;
|
||||||
use PHPUnit\Framework\Reorderable;
|
use PHPUnit\Framework\Reorderable;
|
||||||
use PHPUnit\Framework\Test;
|
use PHPUnit\Framework\Test;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use PHPUnit\Framework\TestSuite;
|
use PHPUnit\Framework\TestSuite;
|
||||||
use PHPUnit\Runner\ResultCache\NullResultCache;
|
use PHPUnit\Runner\TestRunHistory\NullTestRunHistory;
|
||||||
use PHPUnit\Runner\ResultCache\ResultCache;
|
use PHPUnit\Runner\TestRunHistory\TestRunHistory;
|
||||||
use PHPUnit\Runner\ResultCache\ResultCacheId;
|
use PHPUnit\Runner\TestRunHistory\TestRunHistoryId;
|
||||||
|
|
||||||
use function array_diff;
|
use function array_diff;
|
||||||
use function array_merge;
|
use function array_merge;
|
||||||
@@ -78,9 +47,13 @@ final class TestSuiteSorter
|
|||||||
|
|
||||||
public const int ORDER_DEFECTS_FIRST = 3;
|
public const int ORDER_DEFECTS_FIRST = 3;
|
||||||
|
|
||||||
public const int ORDER_DURATION = 4;
|
public const int ORDER_DURATION_ASCENDING = 4;
|
||||||
|
|
||||||
public const int ORDER_SIZE = 5;
|
public const int ORDER_SIZE_ASCENDING = 5;
|
||||||
|
|
||||||
|
public const int ORDER_DURATION_DESCENDING = 6;
|
||||||
|
|
||||||
|
public const int ORDER_SIZE_DESCENDING = 7;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var non-empty-array<non-empty-string, positive-int>
|
* @var non-empty-array<non-empty-string, positive-int>
|
||||||
@@ -97,11 +70,11 @@ final class TestSuiteSorter
|
|||||||
*/
|
*/
|
||||||
private array $defectSortOrder = [];
|
private array $defectSortOrder = [];
|
||||||
|
|
||||||
private readonly ResultCache $cache;
|
private readonly TestRunHistory $testRunHistory;
|
||||||
|
|
||||||
public function __construct(?ResultCache $cache = null)
|
public function __construct(?TestRunHistory $testRunHistory = null)
|
||||||
{
|
{
|
||||||
$this->cache = $cache ?? new NullResultCache;
|
$this->testRunHistory = $testRunHistory ?? new NullTestRunHistory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,8 +86,10 @@ final class TestSuiteSorter
|
|||||||
self::ORDER_DEFAULT,
|
self::ORDER_DEFAULT,
|
||||||
self::ORDER_REVERSED,
|
self::ORDER_REVERSED,
|
||||||
self::ORDER_RANDOMIZED,
|
self::ORDER_RANDOMIZED,
|
||||||
self::ORDER_DURATION,
|
self::ORDER_DURATION_ASCENDING,
|
||||||
self::ORDER_SIZE,
|
self::ORDER_SIZE_ASCENDING,
|
||||||
|
self::ORDER_DURATION_DESCENDING,
|
||||||
|
self::ORDER_SIZE_DESCENDING,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (! in_array($order, $allowedOrders, true)) {
|
if (! in_array($order, $allowedOrders, true)) {
|
||||||
@@ -134,6 +109,10 @@ final class TestSuiteSorter
|
|||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($suite instanceof IterativeTestSuite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($suite instanceof TestSuite) {
|
if ($suite instanceof TestSuite) {
|
||||||
foreach ($suite as $_suite) {
|
foreach ($suite as $_suite) {
|
||||||
$this->reorderTestsInSuite($_suite, $order, $resolveDependencies, $orderDefects);
|
$this->reorderTestsInSuite($_suite, $order, $resolveDependencies, $orderDefects);
|
||||||
@@ -149,31 +128,37 @@ final class TestSuiteSorter
|
|||||||
|
|
||||||
private function sort(TestSuite $suite, int $order, bool $resolveDependencies, int $orderDefects): void
|
private function sort(TestSuite $suite, int $order, bool $resolveDependencies, int $orderDefects): void
|
||||||
{
|
{
|
||||||
if ($suite->tests() === []) {
|
$tests = $suite->tests();
|
||||||
|
|
||||||
|
if ($tests === []) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($order === self::ORDER_REVERSED) {
|
if ($order === self::ORDER_REVERSED) {
|
||||||
$suite->setTests($this->reverse($suite->tests()));
|
$tests = $this->reverse($tests);
|
||||||
} elseif ($order === self::ORDER_RANDOMIZED) {
|
} elseif ($order === self::ORDER_RANDOMIZED) {
|
||||||
$suite->setTests($this->randomize($suite->tests()));
|
$tests = $this->randomize($tests);
|
||||||
} elseif ($order === self::ORDER_DURATION) {
|
} elseif ($order === self::ORDER_DURATION_ASCENDING) {
|
||||||
$suite->setTests($this->sortByDuration($suite->tests()));
|
$tests = $this->sortByDuration($tests);
|
||||||
} elseif ($order === self::ORDER_SIZE) {
|
} elseif ($order === self::ORDER_DURATION_DESCENDING) {
|
||||||
$suite->setTests($this->sortBySize($suite->tests()));
|
$tests = $this->sortByDurationDescending($tests);
|
||||||
|
} elseif ($order === self::ORDER_SIZE_ASCENDING) {
|
||||||
|
$tests = $this->sortBySize($tests);
|
||||||
|
} elseif ($order === self::ORDER_SIZE_DESCENDING) {
|
||||||
|
$tests = $this->sortBySizeDescending($tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($orderDefects === self::ORDER_DEFECTS_FIRST) {
|
if ($orderDefects === self::ORDER_DEFECTS_FIRST) {
|
||||||
$suite->setTests($this->sortDefectsFirst($suite->tests()));
|
$tests = $this->sortDefectsFirst($tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($resolveDependencies && ! ($suite instanceof DataProviderTestSuite)) {
|
if ($resolveDependencies && ! ($suite instanceof DataProviderTestSuite)) {
|
||||||
$tests = $suite->tests();
|
|
||||||
|
|
||||||
/** @noinspection PhpParamsInspection */
|
/** @noinspection PhpParamsInspection */
|
||||||
/** @phpstan-ignore argument.type */
|
/** @phpstan-ignore argument.type */
|
||||||
$suite->setTests($this->resolveDependencies($tests));
|
$tests = $this->resolveDependencies($tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$suite->setTests($tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function addSuiteToDefectSortOrder(TestSuite $suite): void
|
private function addSuiteToDefectSortOrder(TestSuite $suite): void
|
||||||
@@ -186,9 +171,10 @@ final class TestSuiteSorter
|
|||||||
$sortId = $test->sortId();
|
$sortId = $test->sortId();
|
||||||
|
|
||||||
if (! isset($this->defectSortOrder[$sortId])) {
|
if (! isset($this->defectSortOrder[$sortId])) {
|
||||||
$this->defectSortOrder[$sortId] = $this->cache->status(ResultCacheId::fromReorderable($test))->asInt();
|
$this->defectSortOrder[$sortId] = $this->testRunHistory->status(TestRunHistoryId::fromReorderable($test))->sortWeight();
|
||||||
$max = max($max, $this->defectSortOrder[$sortId]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$max = max($max, $this->defectSortOrder[$sortId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->defectSortOrder[$suite->sortId()] = $max;
|
$this->defectSortOrder[$suite->sortId()] = $max;
|
||||||
@@ -242,6 +228,20 @@ final class TestSuiteSorter
|
|||||||
return $tests;
|
return $tests;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<Test> $tests
|
||||||
|
* @return list<Test>
|
||||||
|
*/
|
||||||
|
private function sortByDurationDescending(array $tests): array
|
||||||
|
{
|
||||||
|
usort(
|
||||||
|
$tests,
|
||||||
|
fn (Test $left, Test $right) => $this->cmpDuration($right, $left),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $tests;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param list<Test> $tests
|
* @param list<Test> $tests
|
||||||
* @return list<Test>
|
* @return list<Test>
|
||||||
@@ -257,12 +257,19 @@ final class TestSuiteSorter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Comparator callback function to sort tests for "reach failure as fast as possible".
|
* @param list<Test> $tests
|
||||||
*
|
* @return list<Test>
|
||||||
* 1. sort tests by defect weight defined in self::DEFECT_SORT_WEIGHT
|
|
||||||
* 2. when tests are equally defective, sort the fastest to the front
|
|
||||||
* 3. do not reorder successful tests
|
|
||||||
*/
|
*/
|
||||||
|
private function sortBySizeDescending(array $tests): array
|
||||||
|
{
|
||||||
|
usort(
|
||||||
|
$tests,
|
||||||
|
fn (Test $left, Test $right) => $this->cmpSize($right, $left),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $tests;
|
||||||
|
}
|
||||||
|
|
||||||
private function cmpDefectPriorityAndTime(Test $a, Test $b): int
|
private function cmpDefectPriorityAndTime(Test $a, Test $b): int
|
||||||
{
|
{
|
||||||
assert($a instanceof Reorderable);
|
assert($a instanceof Reorderable);
|
||||||
@@ -271,72 +278,72 @@ final class TestSuiteSorter
|
|||||||
$priorityA = $this->defectSortOrder[$a->sortId()] ?? 0;
|
$priorityA = $this->defectSortOrder[$a->sortId()] ?? 0;
|
||||||
$priorityB = $this->defectSortOrder[$b->sortId()] ?? 0;
|
$priorityB = $this->defectSortOrder[$b->sortId()] ?? 0;
|
||||||
|
|
||||||
if ($priorityA !== $priorityB) {
|
return $priorityB <=> $priorityA;
|
||||||
// Sort defect weight descending
|
|
||||||
return $priorityB <=> $priorityA;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($priorityA > 0 || $priorityB > 0) {
|
|
||||||
return $this->cmpDuration($a, $b);
|
|
||||||
}
|
|
||||||
|
|
||||||
// do not change execution order
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares test duration for sorting tests by duration ascending.
|
|
||||||
*/
|
|
||||||
private function cmpDuration(Test $a, Test $b): int
|
private function cmpDuration(Test $a, Test $b): int
|
||||||
{
|
{
|
||||||
if (! ($a instanceof Reorderable && $b instanceof Reorderable)) {
|
return $this->durationWeight($a) <=> $this->durationWeight($b);
|
||||||
return 0;
|
}
|
||||||
|
|
||||||
|
private function durationWeight(Test $test): float
|
||||||
|
{
|
||||||
|
if ($test instanceof TestSuite) {
|
||||||
|
$sum = 0.0;
|
||||||
|
|
||||||
|
foreach ($test->tests() as $inner) {
|
||||||
|
$sum += $this->durationWeight($inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->cache->time(ResultCacheId::fromReorderable($a)) <=> $this->cache->time(ResultCacheId::fromReorderable($b));
|
if ($test instanceof Reorderable) {
|
||||||
|
return $this->testRunHistory->time(TestRunHistoryId::fromReorderable($test));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares test size for sorting tests small->medium->large->unknown.
|
|
||||||
*/
|
|
||||||
private function cmpSize(Test $a, Test $b): int
|
private function cmpSize(Test $a, Test $b): int
|
||||||
{
|
{
|
||||||
$sizeA = ($a instanceof TestCase || $a instanceof DataProviderTestSuite)
|
return $this->sizeWeight($a) <=> $this->sizeWeight($b);
|
||||||
? $a->size()->asString()
|
|
||||||
: 'unknown';
|
|
||||||
$sizeB = ($b instanceof TestCase || $b instanceof DataProviderTestSuite)
|
|
||||||
? $b->size()->asString()
|
|
||||||
: 'unknown';
|
|
||||||
|
|
||||||
return self::SIZE_SORT_WEIGHT[$sizeA] <=> self::SIZE_SORT_WEIGHT[$sizeB];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reorder Tests within a TestCase in such a way as to resolve as many dependencies as possible.
|
* @return positive-int
|
||||||
* The algorithm will leave the tests in original running order when it can.
|
*/
|
||||||
* For more details see the documentation for test dependencies.
|
private function sizeWeight(Test $test): int
|
||||||
*
|
{
|
||||||
* Short description of algorithm:
|
if ($test instanceof TestCase || $test instanceof DataProviderTestSuite) {
|
||||||
* 1. Pick the next Test from remaining tests to be checked for dependencies.
|
return self::SIZE_SORT_WEIGHT[$test->size()->asString()];
|
||||||
* 2. If the test has no dependencies: mark done, start again from the top
|
}
|
||||||
* 3. If the test has dependencies but none left to do: mark done, start again from the top
|
|
||||||
* 4. When we reach the end add any leftover tests to the end. These will be marked 'skipped' during execution.
|
if ($test instanceof TestSuite) {
|
||||||
*
|
$max = 0;
|
||||||
* @param array<TestCase> $tests
|
|
||||||
* @return array<TestCase>
|
foreach ($test->tests() as $inner) {
|
||||||
|
$weight = $this->sizeWeight($inner);
|
||||||
|
|
||||||
|
if ($weight > $max) {
|
||||||
|
$max = $weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($max > 0) {
|
||||||
|
return $max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::SIZE_SORT_WEIGHT['unknown'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<TestCase> $tests
|
||||||
|
* @return list<TestCase>
|
||||||
*/
|
*/
|
||||||
private function resolveDependencies(array $tests): array
|
private function resolveDependencies(array $tests): array
|
||||||
{
|
{
|
||||||
// Pest: Fast-path. If no test in this suite declares dependencies, the
|
|
||||||
// original O(N^2) algorithm is wasted work — it would splice each test
|
|
||||||
// one-by-one back into the same order. The check deliberately walks
|
|
||||||
// TestCase instances directly instead of calling TestSuite::requires(),
|
|
||||||
// because the latter lazily builds TestSuite::provides() via
|
|
||||||
// ExecutionOrderDependency::mergeUnique, which is O(N^2) in the total
|
|
||||||
// number of tests. With thousands of tests that single call alone can
|
|
||||||
// burn several seconds before the sort even begins. Reading the
|
|
||||||
// cached TestCase::$dependencies property stays O(N) and costs nothing
|
|
||||||
// when no test uses `->depends()` / PHPUnit `@depends`.
|
|
||||||
if (! $this->anyTestHasDependencies($tests)) {
|
if (! $this->anyTestHasDependencies($tests)) {
|
||||||
return $tests;
|
return $tests;
|
||||||
}
|
}
|
||||||
@@ -345,7 +352,7 @@ final class TestSuiteSorter
|
|||||||
$i = 0;
|
$i = 0;
|
||||||
$provided = [];
|
$provided = [];
|
||||||
|
|
||||||
do {
|
while ($tests !== [] && $i < count($tests)) {
|
||||||
if (array_diff($tests[$i]->requires(), $provided) === []) {
|
if (array_diff($tests[$i]->requires(), $provided) === []) {
|
||||||
$provided = array_merge($provided, $tests[$i]->provides());
|
$provided = array_merge($provided, $tests[$i]->provides());
|
||||||
$newTestOrder = array_merge($newTestOrder, array_splice($tests, $i, 1));
|
$newTestOrder = array_merge($newTestOrder, array_splice($tests, $i, 1));
|
||||||
@@ -353,18 +360,12 @@ final class TestSuiteSorter
|
|||||||
} else {
|
} else {
|
||||||
$i++;
|
$i++;
|
||||||
}
|
}
|
||||||
} while ($tests !== [] && ($i < count($tests)));
|
}
|
||||||
|
|
||||||
return array_merge($newTestOrder, $tests);
|
return array_merge($newTestOrder, $tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cheaply determines whether any test in the tree declares @depends.
|
|
||||||
*
|
|
||||||
* Walks `TestSuite` containers recursively and inspects each `TestCase`
|
|
||||||
* directly so it never triggers `TestSuite::provides()`, which is O(N^2)
|
|
||||||
* in the total number of aggregated tests.
|
|
||||||
*
|
|
||||||
* @param iterable<Test> $tests
|
* @param iterable<Test> $tests
|
||||||
*/
|
*/
|
||||||
private function anyTestHasDependencies(iterable $tests): bool
|
private function anyTestHasDependencies(iterable $tests): bool
|
||||||
|
|||||||
@@ -1,37 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,37 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -51,10 +19,6 @@ 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 readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber
|
final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber
|
||||||
{
|
{
|
||||||
@@ -67,9 +31,6 @@ final readonly class TestSkippedSubscriber extends Subscriber implements Skipped
|
|||||||
$this->printer()->testSkipped();
|
$this->printer()->testSkipped();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints a "T" to the standard PHPUnit output to indicate a todo item.
|
|
||||||
*/
|
|
||||||
private function printTodoItem(): void
|
private function printTodoItem(): void
|
||||||
{
|
{
|
||||||
$mirror = new ReflectionClass($this->printer());
|
$mirror = new ReflectionClass($this->printer());
|
||||||
|
|||||||
@@ -1,37 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
|
||||||
* BSD 3-Clause License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2001-2023, Sebastian Bergmann
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
/*
|
/*
|
||||||
* This file is part of PHPUnit.
|
* This file is part of PHPUnit.
|
||||||
|
|||||||
+2
-1
@@ -18,8 +18,9 @@
|
|||||||
<directory suffix=".php">./tests</directory>
|
<directory suffix=".php">./tests</directory>
|
||||||
<directory suffix=".php">./tests-external</directory>
|
<directory suffix=".php">./tests-external</directory>
|
||||||
<exclude>./tests/.snapshots</exclude>
|
<exclude>./tests/.snapshots</exclude>
|
||||||
<exclude>./tests/.tests</exclude>
|
|
||||||
<exclude>./tests/Fixtures/Inheritance</exclude>
|
<exclude>./tests/Fixtures/Inheritance</exclude>
|
||||||
|
<exclude>./tests/Fixtures/Suites</exclude>
|
||||||
|
<exclude>./tests/Fixtures/Tia</exclude>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
<source>
|
<source>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"exclude": [
|
||||||
|
"tests/Fixtures/Suites"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"Pint/phpdoc_type_annotations_only": true
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-6
@@ -28,6 +28,7 @@ return RectorConfig::configure()
|
|||||||
->withSkip([
|
->withSkip([
|
||||||
__DIR__.'/src/Plugins/Parallel/Paratest/WrapperRunner.php',
|
__DIR__.'/src/Plugins/Parallel/Paratest/WrapperRunner.php',
|
||||||
__DIR__.'/tests/Fixtures/Arch',
|
__DIR__.'/tests/Fixtures/Arch',
|
||||||
|
__DIR__.'/tests/Fixtures/Suites',
|
||||||
ReturnNeverTypeRector::class,
|
ReturnNeverTypeRector::class,
|
||||||
ArrowFunctionDelegatingCallToFirstClassCallableRector::class,
|
ArrowFunctionDelegatingCallToFirstClassCallableRector::class,
|
||||||
NarrowObjectReturnTypeRector::class,
|
NarrowObjectReturnTypeRector::class,
|
||||||
@@ -37,13 +38,7 @@ return RectorConfig::configure()
|
|||||||
RemoveReturnTagIncompatibleWithNativeTypeRector::class => [
|
RemoveReturnTagIncompatibleWithNativeTypeRector::class => [
|
||||||
__DIR__.'/src/Expectations/HigherOrderExpectation.php',
|
__DIR__.'/src/Expectations/HigherOrderExpectation.php',
|
||||||
],
|
],
|
||||||
// Merges unrelated expectations into a single `toMatchArray()`, turning
|
|
||||||
// `toContain()` into exact matches, dropping `->not`, and mistaking a
|
|
||||||
// `toBeTrue()` failure message for an expected value. Unsafe here.
|
|
||||||
UseToMatchArrayRector::class,
|
UseToMatchArrayRector::class,
|
||||||
// Test fixtures rely on "unused" constructors, params and properties
|
|
||||||
// (resolved via the container or read through reflection), so the
|
|
||||||
// dead-code and return-type rules below must not touch the test suite.
|
|
||||||
RemoveEmptyClassMethodRector::class => [
|
RemoveEmptyClassMethodRector::class => [
|
||||||
__DIR__.'/tests',
|
__DIR__.'/tests',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -13,15 +13,11 @@ use Pest\Expectation;
|
|||||||
abstract class AbstractPreset // @pest-arch-ignore-line
|
abstract class AbstractPreset // @pest-arch-ignore-line
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The expectations.
|
|
||||||
*
|
|
||||||
* @var array<int, Expectation<mixed>|ArchExpectation>
|
* @var array<int, Expectation<mixed>|ArchExpectation>
|
||||||
*/
|
*/
|
||||||
protected array $expectations = [];
|
protected array $expectations = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new preset instance.
|
|
||||||
*
|
|
||||||
* @param array<int, string> $userNamespaces
|
* @param array<int, string> $userNamespaces
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -31,15 +27,11 @@ abstract class AbstractPreset // @pest-arch-ignore-line
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the arch preset.
|
|
||||||
*
|
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
abstract public function execute(): void;
|
abstract public function execute(): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ignores the given "targets" or "dependencies".
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $targetsOrDependencies
|
* @param array<int, string>|string $targetsOrDependencies
|
||||||
*/
|
*/
|
||||||
final public function ignoring(array|string $targetsOrDependencies): void
|
final public function ignoring(array|string $targetsOrDependencies): void
|
||||||
@@ -51,8 +43,6 @@ abstract class AbstractPreset // @pest-arch-ignore-line
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the given callback for each namespace.
|
|
||||||
*
|
|
||||||
* @param callable(Expectation<string>): ArchExpectation ...$callbacks
|
* @param callable(Expectation<string>): ArchExpectation ...$callbacks
|
||||||
*/
|
*/
|
||||||
final public function eachUserNamespace(callable ...$callbacks): void
|
final public function eachUserNamespace(callable ...$callbacks): void
|
||||||
@@ -64,9 +54,6 @@ abstract class AbstractPreset // @pest-arch-ignore-line
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Flushes the expectations.
|
|
||||||
*/
|
|
||||||
final public function flush(): void
|
final public function flush(): void
|
||||||
{
|
{
|
||||||
$this->expectations = [];
|
$this->expectations = [];
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ use Pest\Expectation;
|
|||||||
final class Custom extends AbstractPreset
|
final class Custom extends AbstractPreset
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Creates a new preset instance.
|
|
||||||
*
|
|
||||||
* @param array<int, string> $userNamespaces
|
* @param array<int, string> $userNamespaces
|
||||||
* @param Closure(array<int, string>): array<Expectation<mixed>|ArchExpectation> $execute
|
* @param Closure(array<int, string>): array<Expectation<mixed>|ArchExpectation> $execute
|
||||||
*/
|
*/
|
||||||
@@ -27,17 +25,11 @@ final class Custom extends AbstractPreset
|
|||||||
parent::__construct($userNamespaces);
|
parent::__construct($userNamespaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the preset.
|
|
||||||
*/
|
|
||||||
public function name(): string
|
public function name(): string
|
||||||
{
|
{
|
||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the arch preset.
|
|
||||||
*/
|
|
||||||
public function execute(): void
|
public function execute(): void
|
||||||
{
|
{
|
||||||
$this->expectations = ($this->execute)($this->userNamespaces);
|
$this->expectations = ($this->execute)($this->userNamespaces);
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ use Throwable;
|
|||||||
*/
|
*/
|
||||||
final class Laravel extends AbstractPreset
|
final class Laravel extends AbstractPreset
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Executes the arch preset.
|
|
||||||
*/
|
|
||||||
public function execute(): void
|
public function execute(): void
|
||||||
{
|
{
|
||||||
$this->expectations[] = expect('App\Traits')
|
$this->expectations[] = expect('App\Traits')
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ namespace Pest\ArchPresets;
|
|||||||
*/
|
*/
|
||||||
final class Php extends AbstractPreset
|
final class Php extends AbstractPreset
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Executes the arch preset.
|
|
||||||
*/
|
|
||||||
public function execute(): void
|
public function execute(): void
|
||||||
{
|
{
|
||||||
$this->expectations[] = expect([
|
$this->expectations[] = expect([
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ use Pest\Expectation;
|
|||||||
*/
|
*/
|
||||||
final class Relaxed extends AbstractPreset
|
final class Relaxed extends AbstractPreset
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Executes the arch preset.
|
|
||||||
*/
|
|
||||||
public function execute(): void
|
public function execute(): void
|
||||||
{
|
{
|
||||||
$this->eachUserNamespace(
|
$this->eachUserNamespace(
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ namespace Pest\ArchPresets;
|
|||||||
*/
|
*/
|
||||||
final class Security extends AbstractPreset
|
final class Security extends AbstractPreset
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Executes the arch preset.
|
|
||||||
*/
|
|
||||||
public function execute(): void
|
public function execute(): void
|
||||||
{
|
{
|
||||||
$this->expectations[] = expect([
|
$this->expectations[] = expect([
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ use Pest\Expectation;
|
|||||||
*/
|
*/
|
||||||
final class Strict extends AbstractPreset
|
final class Strict extends AbstractPreset
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Executes the arch preset.
|
|
||||||
*/
|
|
||||||
public function execute(): void
|
public function execute(): void
|
||||||
{
|
{
|
||||||
$this->eachUserNamespace(
|
$this->eachUserNamespace(
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ use PHPUnit\Util\ExcludeList;
|
|||||||
final class BootExcludeList implements Bootstrapper
|
final class BootExcludeList implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The directories to exclude.
|
|
||||||
*
|
|
||||||
* @var array<int, non-empty-string>
|
* @var array<int, non-empty-string>
|
||||||
*/
|
*/
|
||||||
private const array EXCLUDE_LIST = [
|
private const array EXCLUDE_LIST = [
|
||||||
@@ -25,9 +23,6 @@ final class BootExcludeList implements Bootstrapper
|
|||||||
'stubs',
|
'stubs',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Boots the "exclude list" for PHPUnit to ignore Pest files.
|
|
||||||
*/
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
$baseDirectory = dirname(__DIR__, 2);
|
$baseDirectory = dirname(__DIR__, 2);
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ use function Pest\testDirectory;
|
|||||||
final class BootFiles implements Bootstrapper
|
final class BootFiles implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The structure of the tests directory.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
private const array STRUCTURE = [
|
private const array STRUCTURE = [
|
||||||
@@ -33,9 +31,6 @@ final class BootFiles implements Bootstrapper
|
|||||||
'Pest.php',
|
'Pest.php',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Boots the structure of the tests directory.
|
|
||||||
*/
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
$rootPath = TestSuite::getInstance()->rootPath;
|
$rootPath = TestSuite::getInstance()->rootPath;
|
||||||
@@ -67,9 +62,6 @@ final class BootFiles implements Bootstrapper
|
|||||||
$this->bootDatasets($testsPath);
|
$this->bootDatasets($testsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads, if possible, the given file.
|
|
||||||
*/
|
|
||||||
private function load(string $filename): void
|
private function load(string $filename): void
|
||||||
{
|
{
|
||||||
if (! Str::endsWith($filename, '.php')) {
|
if (! Str::endsWith($filename, '.php')) {
|
||||||
|
|||||||
@@ -14,18 +14,12 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
*/
|
*/
|
||||||
final readonly class BootKernelDump implements Bootstrapper
|
final readonly class BootKernelDump implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Boot Kernel Dump instance.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private OutputInterface $output,
|
private OutputInterface $output,
|
||||||
) {
|
) {
|
||||||
// ...
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Boots the kernel dump.
|
|
||||||
*/
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
Container::getInstance()->add(KernelDump::class, $kernelDump = new KernelDump(
|
Container::getInstance()->add(KernelDump::class, $kernelDump = new KernelDump(
|
||||||
|
|||||||
@@ -13,14 +13,12 @@ use Pest\Exceptions\ShouldNotHappen;
|
|||||||
final class BootOverrides implements Bootstrapper
|
final class BootOverrides implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The list of files to be overridden.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
public const array FILES = [
|
public const array FILES = [
|
||||||
'ParaTest/WrapperRunner/ProgressPrinterOutput.php',
|
'ParaTest/WrapperRunner/ProgressPrinterOutput.php',
|
||||||
'Runner/Filter/NameFilterIterator.php',
|
'Runner/Filter/NameFilterIterator.php',
|
||||||
'Runner/ResultCache/DefaultResultCache.php',
|
'Runner/TestRunHistory/DefaultTestRunHistory.php',
|
||||||
'Runner/TestSuiteLoader.php',
|
'Runner/TestSuiteLoader.php',
|
||||||
'Runner/TestSuiteSorter.php',
|
'Runner/TestSuiteSorter.php',
|
||||||
'TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php',
|
'TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php',
|
||||||
@@ -30,9 +28,6 @@ final class BootOverrides implements Bootstrapper
|
|||||||
'Logging/JUnit/JunitXmlLogger.php',
|
'Logging/JUnit/JunitXmlLogger.php',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Boots the list of files to be overridden.
|
|
||||||
*/
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
foreach (self::FILES as $file) {
|
foreach (self::FILES as $file) {
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ use PHPUnit\Event\Subscriber;
|
|||||||
final readonly class BootSubscribers implements Bootstrapper
|
final readonly class BootSubscribers implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The list of Subscribers.
|
|
||||||
*
|
|
||||||
* @var array<int, class-string<Subscriber>>
|
* @var array<int, class-string<Subscriber>>
|
||||||
*/
|
*/
|
||||||
private const array SUBSCRIBERS = [
|
private const array SUBSCRIBERS = [
|
||||||
@@ -35,19 +33,19 @@ final readonly class BootSubscribers implements Bootstrapper
|
|||||||
Subscribers\EnsureTiaResultIsRecordedOnSkipped::class,
|
Subscribers\EnsureTiaResultIsRecordedOnSkipped::class,
|
||||||
Subscribers\EnsureTiaResultIsRecordedOnIncomplete::class,
|
Subscribers\EnsureTiaResultIsRecordedOnIncomplete::class,
|
||||||
Subscribers\EnsureTiaResultIsRecordedOnRisky::class,
|
Subscribers\EnsureTiaResultIsRecordedOnRisky::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnNoticeTriggered::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnPhpNoticeTriggered::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnDeprecationTriggered::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnPhpDeprecationTriggered::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnWarningTriggered::class,
|
||||||
|
Subscribers\EnsureTiaResultIsRecordedOnPhpWarningTriggered::class,
|
||||||
Subscribers\EnsureTiaAssertionsAreRecordedOnFinished::class,
|
Subscribers\EnsureTiaAssertionsAreRecordedOnFinished::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance of the Boot Subscribers.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private Container $container,
|
private Container $container,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Boots the list of Subscribers.
|
|
||||||
*/
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
foreach (self::SUBSCRIBERS as $subscriber) {
|
foreach (self::SUBSCRIBERS as $subscriber) {
|
||||||
|
|||||||
@@ -13,18 +13,12 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
*/
|
*/
|
||||||
final readonly class BootView implements Bootstrapper
|
final readonly class BootView implements Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new instance of the Boot View.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private OutputInterface $output
|
private OutputInterface $output
|
||||||
) {
|
) {
|
||||||
// ..
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Boots the view renderer.
|
|
||||||
*/
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
View::renderUsing($this->output);
|
View::renderUsing($this->output);
|
||||||
|
|||||||
@@ -16,22 +16,13 @@ use function Termwind\renderUsing;
|
|||||||
*/
|
*/
|
||||||
final class Events
|
final class Events
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Sets the output.
|
|
||||||
*/
|
|
||||||
private static ?OutputInterface $output = null;
|
private static ?OutputInterface $output = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the output.
|
|
||||||
*/
|
|
||||||
public static function setOutput(OutputInterface $output): void
|
public static function setOutput(OutputInterface $output): void
|
||||||
{
|
{
|
||||||
self::$output = $output;
|
self::$output = $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fires before the test method description is printed.
|
|
||||||
*/
|
|
||||||
public static function beforeTestMethodDescription(TestResult $result, string $description): string
|
public static function beforeTestMethodDescription(TestResult $result, string $description): string
|
||||||
{
|
{
|
||||||
if (($context = $result->context) === []) {
|
if (($context = $result->context) === []) {
|
||||||
@@ -73,9 +64,6 @@ final class Events
|
|||||||
return $description;
|
return $description;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fires after the test method description is printed.
|
|
||||||
*/
|
|
||||||
public static function afterTestMethodDescription(TestResult $result): void
|
public static function afterTestMethodDescription(TestResult $result): void
|
||||||
{
|
{
|
||||||
if (($context = $result->context) === []) {
|
if (($context = $result->context) === []) {
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ trait Expectable
|
|||||||
/**
|
/**
|
||||||
* @template TValue
|
* @template TValue
|
||||||
*
|
*
|
||||||
* Creates a new Expectation.
|
|
||||||
*
|
|
||||||
* @param TValue $value
|
* @param TValue $value
|
||||||
* @return Expectation<TValue>
|
* @return Expectation<TValue>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,15 +14,11 @@ use Closure;
|
|||||||
trait Extendable
|
trait Extendable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The list of extends.
|
|
||||||
*
|
|
||||||
* @var array<string, Closure>
|
* @var array<string, Closure>
|
||||||
*/
|
*/
|
||||||
private static array $extends = [];
|
private static array $extends = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new extend.
|
|
||||||
*
|
|
||||||
* @param-closure-this T $extend
|
* @param-closure-this T $extend
|
||||||
*/
|
*/
|
||||||
public function extend(string $name, Closure $extend): void
|
public function extend(string $name, Closure $extend): void
|
||||||
@@ -30,9 +26,6 @@ trait Extendable
|
|||||||
static::$extends[$name] = $extend;
|
static::$extends[$name] = $extend;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if given extend name is registered.
|
|
||||||
*/
|
|
||||||
public static function hasExtend(string $name): bool
|
public static function hasExtend(string $name): bool
|
||||||
{
|
{
|
||||||
return array_key_exists($name, static::$extends);
|
return array_key_exists($name, static::$extends);
|
||||||
|
|||||||
@@ -9,33 +9,21 @@ namespace Pest\Concerns\Logging;
|
|||||||
*/
|
*/
|
||||||
trait WritesToConsole
|
trait WritesToConsole
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Writes the given success message to the console.
|
|
||||||
*/
|
|
||||||
private function writeSuccess(string $message): void
|
private function writeSuccess(string $message): void
|
||||||
{
|
{
|
||||||
$this->writePestTestOutput($message, 'fg-green, bold', '✓');
|
$this->writePestTestOutput($message, 'fg-green, bold', '✓');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the given error message to the console.
|
|
||||||
*/
|
|
||||||
private function writeError(string $message): void
|
private function writeError(string $message): void
|
||||||
{
|
{
|
||||||
$this->writePestTestOutput($message, 'fg-red, bold', '⨯');
|
$this->writePestTestOutput($message, 'fg-red, bold', '⨯');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the given warning message to the console.
|
|
||||||
*/
|
|
||||||
private function writeWarning(string $message): void
|
private function writeWarning(string $message): void
|
||||||
{
|
{
|
||||||
$this->writePestTestOutput($message, 'fg-yellow, bold', '-');
|
$this->writePestTestOutput($message, 'fg-yellow, bold', '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the give message to the console.
|
|
||||||
*/
|
|
||||||
private function writePestTestOutput(string $message, string $color, string $symbol): void
|
private function writePestTestOutput(string $message, string $color, string $symbol): void
|
||||||
{
|
{
|
||||||
$this->writeWithColor($color, "$symbol ", false);
|
$this->writeWithColor($color, "$symbol ", false);
|
||||||
|
|||||||
@@ -12,30 +12,21 @@ use Closure;
|
|||||||
trait Pipeable
|
trait Pipeable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The list of pipes.
|
|
||||||
*
|
|
||||||
* @var array<string, array<Closure(Closure, mixed ...$arguments): void>>
|
* @var array<string, array<Closure(Closure, mixed ...$arguments): void>>
|
||||||
*/
|
*/
|
||||||
private static array $pipes = [];
|
private static array $pipes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of interceptors.
|
|
||||||
*
|
|
||||||
* @var array<string, array<Closure(Closure, mixed ...$arguments): void>>
|
* @var array<string, array<Closure(Closure, mixed ...$arguments): void>>
|
||||||
*/
|
*/
|
||||||
private static array $interceptors = [];
|
private static array $interceptors = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a pipe to be applied before an expectation is checked.
|
|
||||||
*/
|
|
||||||
public function pipe(string $name, Closure $pipe): void
|
public function pipe(string $name, Closure $pipe): void
|
||||||
{
|
{
|
||||||
self::$pipes[$name][] = $pipe;
|
self::$pipes[$name][] = $pipe;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register an interceptor that should replace an existing expectation.
|
|
||||||
*
|
|
||||||
* @param string|Closure(mixed $value, mixed ...$arguments):bool $filter
|
* @param string|Closure(mixed $value, mixed ...$arguments):bool $filter
|
||||||
*/
|
*/
|
||||||
public function intercept(string $name, string|Closure $filter, Closure $handler): void
|
public function intercept(string $name, string|Closure $filter, Closure $handler): void
|
||||||
@@ -60,8 +51,6 @@ trait Pipeable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of pipes by the given name.
|
|
||||||
*
|
|
||||||
* @return array<int, Closure>
|
* @return array<int, Closure>
|
||||||
*/
|
*/
|
||||||
private function pipes(string $name, object $context, string $scope): array
|
private function pipes(string $name, object $context, string $scope): array
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ trait Retrievable
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @template TRetrievableValue
|
* @template TRetrievableValue
|
||||||
*
|
|
||||||
* Safely retrieve the value at the given key from an object or array.
|
|
||||||
* @template TRetrievableValue
|
* @template TRetrievableValue
|
||||||
*
|
*
|
||||||
* @param array<string, TRetrievableValue>|object $value
|
* @param array<string, TRetrievableValue>|object $value
|
||||||
|
|||||||
+2
-114
@@ -37,105 +37,53 @@ use Throwable;
|
|||||||
*/
|
*/
|
||||||
trait Testable
|
trait Testable
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The test's description.
|
|
||||||
*/
|
|
||||||
private string $__description;
|
private string $__description;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's latest description.
|
|
||||||
*/
|
|
||||||
private static string $__latestDescription;
|
private static string $__latestDescription;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's assignees.
|
|
||||||
*/
|
|
||||||
private static array $__latestAssignees = [];
|
private static array $__latestAssignees = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's notes.
|
|
||||||
*/
|
|
||||||
private static array $__latestNotes = [];
|
private static array $__latestNotes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The test's issues.
|
|
||||||
*
|
|
||||||
* @var array<int, int>
|
* @var array<int, int>
|
||||||
*/
|
*/
|
||||||
private static array $__latestIssues = [];
|
private static array $__latestIssues = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The test's PRs.
|
|
||||||
*
|
|
||||||
* @var array<int, int>
|
* @var array<int, int>
|
||||||
*/
|
*/
|
||||||
private static array $__latestPrs = [];
|
private static array $__latestPrs = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The test's describing, if any.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
public array $__describing = [];
|
public array $__describing = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the test has ran or not.
|
|
||||||
*/
|
|
||||||
public bool $__ran = false;
|
public bool $__ran = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* The active replay mode for this test, set in `setUp()` and checked
|
|
||||||
* in `__runTest()` / `tearDown()` to skip the body and after-each.
|
|
||||||
*/
|
|
||||||
private ReplayType $__replay = ReplayType::None;
|
private ReplayType $__replay = ReplayType::None;
|
||||||
|
|
||||||
/**
|
|
||||||
* The cached assertion count to replay, captured when entering replay mode.
|
|
||||||
*/
|
|
||||||
private int $__replayAssertions = 0;
|
private int $__replayAssertions = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's test closure.
|
|
||||||
*/
|
|
||||||
private Closure $__test;
|
private Closure $__test;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's before each closure.
|
|
||||||
*/
|
|
||||||
private ?Closure $__beforeEach = null;
|
private ?Closure $__beforeEach = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's after each closure.
|
|
||||||
*/
|
|
||||||
private ?Closure $__afterEach = null;
|
private ?Closure $__afterEach = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's before all closure.
|
|
||||||
*/
|
|
||||||
private static ?Closure $__beforeAll = null;
|
private static ?Closure $__beforeAll = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's after all closure.
|
|
||||||
*/
|
|
||||||
private static ?Closure $__afterAll = null;
|
private static ?Closure $__afterAll = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of snapshot changes, if any.
|
|
||||||
*/
|
|
||||||
private array $__snapshotChanges = [];
|
private array $__snapshotChanges = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the test case static properties.
|
|
||||||
*/
|
|
||||||
public static function flush(): void
|
public static function flush(): void
|
||||||
{
|
{
|
||||||
self::$__beforeAll = null;
|
self::$__beforeAll = null;
|
||||||
self::$__afterAll = null;
|
self::$__afterAll = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new "note" to the Test Case.
|
|
||||||
*/
|
|
||||||
public function note(array|string $note): self
|
public function note(array|string $note): self
|
||||||
{
|
{
|
||||||
$note = is_array($note) ? $note : [$note];
|
$note = is_array($note) ? $note : [$note];
|
||||||
@@ -145,9 +93,6 @@ trait Testable
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new "setUpBeforeClass" to the Test Case.
|
|
||||||
*/
|
|
||||||
public function __addBeforeAll(?Closure $hook): void
|
public function __addBeforeAll(?Closure $hook): void
|
||||||
{
|
{
|
||||||
if (! $hook instanceof Closure) {
|
if (! $hook instanceof Closure) {
|
||||||
@@ -159,9 +104,6 @@ trait Testable
|
|||||||
: $hook;
|
: $hook;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new "tearDownAfterClass" to the Test Case.
|
|
||||||
*/
|
|
||||||
public function __addAfterAll(?Closure $hook): void
|
public function __addAfterAll(?Closure $hook): void
|
||||||
{
|
{
|
||||||
if (! $hook instanceof Closure) {
|
if (! $hook instanceof Closure) {
|
||||||
@@ -173,25 +115,16 @@ trait Testable
|
|||||||
: $hook;
|
: $hook;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new "setUp" to the Test Case.
|
|
||||||
*/
|
|
||||||
public function __addBeforeEach(?Closure $hook): void
|
public function __addBeforeEach(?Closure $hook): void
|
||||||
{
|
{
|
||||||
$this->__addHook('__beforeEach', $hook);
|
$this->__addHook('__beforeEach', $hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new "tearDown" to the Test Case.
|
|
||||||
*/
|
|
||||||
public function __addAfterEach(?Closure $hook): void
|
public function __addAfterEach(?Closure $hook): void
|
||||||
{
|
{
|
||||||
$this->__addHook('__afterEach', $hook);
|
$this->__addHook('__afterEach', $hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new "hook" to the Test Case.
|
|
||||||
*/
|
|
||||||
private function __addHook(string $property, ?Closure $hook): void
|
private function __addHook(string $property, ?Closure $hook): void
|
||||||
{
|
{
|
||||||
if (! $hook instanceof Closure) {
|
if (! $hook instanceof Closure) {
|
||||||
@@ -203,9 +136,6 @@ trait Testable
|
|||||||
: $hook;
|
: $hook;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called before the first test of this Test Case is run.
|
|
||||||
*/
|
|
||||||
public static function setUpBeforeClass(): void
|
public static function setUpBeforeClass(): void
|
||||||
{
|
{
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
@@ -223,9 +153,6 @@ trait Testable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called after the last test of this Test Case is run.
|
|
||||||
*/
|
|
||||||
public static function tearDownAfterClass(): void
|
public static function tearDownAfterClass(): void
|
||||||
{
|
{
|
||||||
$afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename);
|
$afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename);
|
||||||
@@ -239,9 +166,6 @@ trait Testable
|
|||||||
parent::tearDownAfterClass();
|
parent::tearDownAfterClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets executed before the Test Case.
|
|
||||||
*/
|
|
||||||
protected function setUp(...$arguments): void
|
protected function setUp(...$arguments): void
|
||||||
{
|
{
|
||||||
TestSuite::getInstance()->test = $this;
|
TestSuite::getInstance()->test = $this;
|
||||||
@@ -286,6 +210,8 @@ trait Testable
|
|||||||
if ($replay !== ReplayType::None) {
|
if ($replay !== ReplayType::None) {
|
||||||
assert($status !== null);
|
assert($status !== null);
|
||||||
|
|
||||||
|
$this->__replay = $replay;
|
||||||
|
|
||||||
match ($replay) {
|
match ($replay) {
|
||||||
ReplayType::Pass, ReplayType::Risky => $this->__beginReplay($replay, $tia),
|
ReplayType::Pass, ReplayType::Risky => $this->__beginReplay($replay, $tia),
|
||||||
ReplayType::Skipped => $this->markTestSkipped($status->message()),
|
ReplayType::Skipped => $this->markTestSkipped($status->message()),
|
||||||
@@ -323,12 +249,8 @@ trait Testable
|
|||||||
$this->__ran = true;
|
$this->__ran = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize test case properties from TestSuite.
|
|
||||||
*/
|
|
||||||
public function __initializeTestCase(): void
|
public function __initializeTestCase(): void
|
||||||
{
|
{
|
||||||
// Return if the test case has already been initialized
|
|
||||||
if (isset($this->__test)) {
|
if (isset($this->__test)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -350,9 +272,6 @@ trait Testable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets executed after the Test Case.
|
|
||||||
*/
|
|
||||||
protected function tearDown(...$arguments): void
|
protected function tearDown(...$arguments): void
|
||||||
{
|
{
|
||||||
if ($this->__replay !== ReplayType::None) {
|
if ($this->__replay !== ReplayType::None) {
|
||||||
@@ -380,8 +299,6 @@ trait Testable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the Test Case current test.
|
|
||||||
*
|
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
private function __runTest(Closure $closure, ...$args): mixed
|
private function __runTest(Closure $closure, ...$args): mixed
|
||||||
@@ -452,9 +369,6 @@ trait Testable
|
|||||||
throw $lastException;
|
throw $lastException;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the given exception matches PHPUnit's expected exception.
|
|
||||||
*/
|
|
||||||
private function __isExpectedException(Throwable $e): bool
|
private function __isExpectedException(Throwable $e): bool
|
||||||
{
|
{
|
||||||
$expectation = Closure::bind(fn () => $this->exceptionExpectation, $this, TestCase::class)();
|
$expectation = Closure::bind(fn () => $this->exceptionExpectation, $this, TestCase::class)();
|
||||||
@@ -489,8 +403,6 @@ trait Testable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the passed arguments. Any Closures will be bound to the testcase and resolved.
|
|
||||||
*
|
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
private function __resolveTestArguments(array $arguments): array
|
private function __resolveTestArguments(array $arguments): array
|
||||||
@@ -498,10 +410,6 @@ trait Testable
|
|||||||
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
|
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
|
||||||
|
|
||||||
if ($method->repetitions > 1) {
|
if ($method->repetitions > 1) {
|
||||||
// If the test is repeated, the first argument is the iteration number
|
|
||||||
// we need to move it to the end of the arguments list
|
|
||||||
// so that the datasets are the first n arguments
|
|
||||||
// and the iteration number is the last argument
|
|
||||||
$firstArgument = array_shift($arguments);
|
$firstArgument = array_shift($arguments);
|
||||||
$arguments[] = $firstArgument;
|
$arguments[] = $firstArgument;
|
||||||
}
|
}
|
||||||
@@ -550,8 +458,6 @@ trait Testable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures dataset items count matches underlying test case required parameters
|
|
||||||
*
|
|
||||||
* @throws ReflectionException
|
* @throws ReflectionException
|
||||||
* @throws DatasetArgumentsMismatch
|
* @throws DatasetArgumentsMismatch
|
||||||
*/
|
*/
|
||||||
@@ -591,9 +497,6 @@ trait Testable
|
|||||||
return ExceptionTrace::ensure(fn (): mixed => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments));
|
return ExceptionTrace::ensure(fn (): mixed => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the given preset on the test.
|
|
||||||
*/
|
|
||||||
public function preset(): Preset
|
public function preset(): Preset
|
||||||
{
|
{
|
||||||
return new Preset;
|
return new Preset;
|
||||||
@@ -609,33 +512,21 @@ trait Testable
|
|||||||
$this->markTestIncomplete(implode('. ', $this->__snapshotChanges));
|
$this->markTestIncomplete(implode('. ', $this->__snapshotChanges));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The printable test case name.
|
|
||||||
*/
|
|
||||||
public static function getPrintableTestCaseName(): string
|
public static function getPrintableTestCaseName(): string
|
||||||
{
|
{
|
||||||
return preg_replace('/P\\\/', '', self::class, 1);
|
return preg_replace('/P\\\/', '', self::class, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The printable test case method name.
|
|
||||||
*/
|
|
||||||
public function getPrintableTestCaseMethodName(): string
|
public function getPrintableTestCaseMethodName(): string
|
||||||
{
|
{
|
||||||
return $this->__description;
|
return $this->__description;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The latest printable test case method name.
|
|
||||||
*/
|
|
||||||
public static function getLatestPrintableTestCaseMethodName(): string
|
public static function getLatestPrintableTestCaseMethodName(): string
|
||||||
{
|
{
|
||||||
return self::$__latestDescription ?? '';
|
return self::$__latestDescription ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The printable test case method context.
|
|
||||||
*/
|
|
||||||
public static function getPrintableContext(): array
|
public static function getPrintableContext(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@@ -646,9 +537,6 @@ trait Testable
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a shell for the test case.
|
|
||||||
*/
|
|
||||||
public function shell(): void
|
public function shell(): void
|
||||||
{
|
{
|
||||||
Shell::open();
|
Shell::open();
|
||||||
|
|||||||
@@ -14,31 +14,19 @@ use Pest\PendingCalls\UsesCall;
|
|||||||
*/
|
*/
|
||||||
final readonly class Configuration
|
final readonly class Configuration
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The filename of the configuration.
|
|
||||||
*/
|
|
||||||
private string $filename;
|
private string $filename;
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new configuration instance.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
string $filename,
|
string $filename,
|
||||||
) {
|
) {
|
||||||
$this->filename = str_ends_with($filename, DIRECTORY_SEPARATOR.'Pest.php') ? dirname($filename) : $filename;
|
$this->filename = str_ends_with($filename, DIRECTORY_SEPARATOR.'Pest.php') ? dirname($filename) : $filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Use the given classes and traits in the given targets.
|
|
||||||
*/
|
|
||||||
public function in(string ...$targets): UsesCall
|
public function in(string ...$targets): UsesCall
|
||||||
{
|
{
|
||||||
return new UsesCall($this->filename, [])->in(...$targets);
|
return new UsesCall($this->filename, [])->in(...$targets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Depending on where is called, it will extend the given classes and traits globally or locally.
|
|
||||||
*/
|
|
||||||
public function extend(string ...$classAndTraits): UsesCall
|
public function extend(string ...$classAndTraits): UsesCall
|
||||||
{
|
{
|
||||||
return new UsesCall(
|
return new UsesCall(
|
||||||
@@ -47,97 +35,62 @@ final readonly class Configuration
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Depending on where is called, it will extend the given classes and traits globally or locally.
|
|
||||||
*/
|
|
||||||
public function extends(string ...$classAndTraits): UsesCall
|
public function extends(string ...$classAndTraits): UsesCall
|
||||||
{
|
{
|
||||||
return $this->extend(...$classAndTraits);
|
return $this->extend(...$classAndTraits);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Depending on where is called, it will add the given groups globally or locally.
|
|
||||||
*/
|
|
||||||
public function group(string ...$groups): UsesCall
|
public function group(string ...$groups): UsesCall
|
||||||
{
|
{
|
||||||
return new UsesCall($this->filename, [])->group(...$groups);
|
return new UsesCall($this->filename, [])->group(...$groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks all tests in the current file to be run exclusively.
|
|
||||||
*/
|
|
||||||
public function only(): void
|
public function only(): void
|
||||||
{
|
{
|
||||||
new BeforeEachCall(TestSuite::getInstance(), $this->filename)->only();
|
new BeforeEachCall(TestSuite::getInstance(), $this->filename)->only();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Depending on where is called, it will extend the given classes and traits globally or locally.
|
|
||||||
*/
|
|
||||||
public function use(string ...$classAndTraits): UsesCall
|
public function use(string ...$classAndTraits): UsesCall
|
||||||
{
|
{
|
||||||
return $this->extend(...$classAndTraits);
|
return $this->extend(...$classAndTraits);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Depending on where is called, it will extend the given classes and traits globally or locally.
|
|
||||||
*/
|
|
||||||
public function uses(string ...$classAndTraits): UsesCall
|
public function uses(string ...$classAndTraits): UsesCall
|
||||||
{
|
{
|
||||||
return $this->extends(...$classAndTraits);
|
return $this->extends(...$classAndTraits);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the printer configuration.
|
|
||||||
*/
|
|
||||||
public function printer(): Configuration\Printer
|
public function printer(): Configuration\Printer
|
||||||
{
|
{
|
||||||
return new Configuration\Printer;
|
return new Configuration\Printer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the presets configuration.
|
|
||||||
*/
|
|
||||||
public function presets(): Configuration\Presets
|
public function presets(): Configuration\Presets
|
||||||
{
|
{
|
||||||
return new Configuration\Presets;
|
return new Configuration\Presets;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the project configuration.
|
|
||||||
*/
|
|
||||||
public function project(): Configuration\Project
|
public function project(): Configuration\Project
|
||||||
{
|
{
|
||||||
return Configuration\Project::getInstance();
|
return Configuration\Project::getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the browser configuration.
|
|
||||||
*/
|
|
||||||
public function browser(): Browser\Configuration
|
public function browser(): Browser\Configuration
|
||||||
{
|
{
|
||||||
return new Browser\Configuration;
|
return new Browser\Configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the evals configuration.
|
|
||||||
*/
|
|
||||||
public function evals(): Evals\Configuration // @phpstan-ignore-line
|
public function evals(): Evals\Configuration // @phpstan-ignore-line
|
||||||
{
|
{
|
||||||
return new Evals\Configuration; // @phpstan-ignore-line
|
return new Evals\Configuration; // @phpstan-ignore-line
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the TIA (Test Impact Analysis) configuration.
|
|
||||||
*/
|
|
||||||
public function tia(): Plugins\Tia\Configuration
|
public function tia(): Plugins\Tia\Configuration
|
||||||
{
|
{
|
||||||
return new Plugins\Tia\Configuration;
|
return new Plugins\Tia\Configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proxies calls to the uses method.
|
|
||||||
*
|
|
||||||
* @param array<array-key, mixed> $arguments
|
* @param array<array-key, mixed> $arguments
|
||||||
*/
|
*/
|
||||||
public function __call(string $name, array $arguments): mixed
|
public function __call(string $name, array $arguments): mixed
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ use Pest\Preset;
|
|||||||
|
|
||||||
final class Presets
|
final class Presets
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a custom preset instance, and adds it to the list of presets.
|
|
||||||
*/
|
|
||||||
public function custom(string $name, Closure $execute): void
|
public function custom(string $name, Closure $execute): void
|
||||||
{
|
{
|
||||||
Preset::custom($name, $execute);
|
Preset::custom($name, $execute);
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ use NunoMaduro\Collision\Adapters\Phpunit\Printers\DefaultPrinter;
|
|||||||
*/
|
*/
|
||||||
final readonly class Printer
|
final readonly class Printer
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Sets the theme to compact.
|
|
||||||
*/
|
|
||||||
public function compact(): self
|
public function compact(): self
|
||||||
{
|
{
|
||||||
DefaultPrinter::compact(true);
|
DefaultPrinter::compact(true);
|
||||||
|
|||||||
@@ -10,42 +10,27 @@ namespace Pest\Configuration;
|
|||||||
final class Project
|
final class Project
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The assignees link.
|
|
||||||
*
|
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
public string $assignees = '';
|
public string $assignees = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The issues link.
|
|
||||||
*
|
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
public string $issues = '';
|
public string $issues = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PRs link.
|
|
||||||
*
|
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
public string $prs = '';
|
public string $prs = '';
|
||||||
|
|
||||||
/**
|
|
||||||
* The singleton instance.
|
|
||||||
*/
|
|
||||||
private static ?self $instance = null;
|
private static ?self $instance = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance of the project.
|
|
||||||
*/
|
|
||||||
public static function getInstance(): self
|
public static function getInstance(): self
|
||||||
{
|
{
|
||||||
return self::$instance ??= new self;
|
return self::$instance ??= new self;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the test project to GitHub.
|
|
||||||
*/
|
|
||||||
public function github(string $project): self
|
public function github(string $project): self
|
||||||
{
|
{
|
||||||
$this->issues = "https://github.com/{$project}/issues/%s";
|
$this->issues = "https://github.com/{$project}/issues/%s";
|
||||||
@@ -56,12 +41,8 @@ final class Project
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the test project to GitLab.
|
|
||||||
*/
|
|
||||||
public function gitlab(string $project, string $hostname = 'gitlab.com'): self
|
public function gitlab(string $project, string $hostname = 'gitlab.com'): self
|
||||||
{
|
{
|
||||||
// Simple way to ensure only the host is used
|
|
||||||
$hostname = parse_url($hostname, PHP_URL_HOST) ?? $hostname;
|
$hostname = parse_url($hostname, PHP_URL_HOST) ?? $hostname;
|
||||||
|
|
||||||
$this->issues = "https://{$hostname}/{$project}/-/work_items/%s";
|
$this->issues = "https://{$hostname}/{$project}/-/work_items/%s";
|
||||||
@@ -72,9 +53,6 @@ final class Project
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the test project to Bitbucket.
|
|
||||||
*/
|
|
||||||
public function bitbucket(string $project): self
|
public function bitbucket(string $project): self
|
||||||
{
|
{
|
||||||
$this->issues = "https://bitbucket.org/{$project}/issues/%s";
|
$this->issues = "https://bitbucket.org/{$project}/issues/%s";
|
||||||
@@ -85,9 +63,6 @@ final class Project
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the test project to Jira.
|
|
||||||
*/
|
|
||||||
public function jira(string $namespace, string $project): self
|
public function jira(string $namespace, string $project): self
|
||||||
{
|
{
|
||||||
$this->issues = "https://{$namespace}.atlassian.net/browse/{$project}-%s";
|
$this->issues = "https://{$namespace}.atlassian.net/browse/{$project}-%s";
|
||||||
@@ -97,9 +72,6 @@ final class Project
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the test project to custom.
|
|
||||||
*/
|
|
||||||
public function custom(string $issues, string $prs, string $assignees): self
|
public function custom(string $issues, string $prs, string $assignees): self
|
||||||
{
|
{
|
||||||
$this->issues = $issues;
|
$this->issues = $issues;
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
final readonly class Help
|
final readonly class Help
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The Command messages.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
private const array HELP_MESSAGES = [
|
private const array HELP_MESSAGES = [
|
||||||
@@ -24,17 +22,11 @@ final readonly class Help
|
|||||||
' <info>--group=<fg=cyan><name></></info> Only runs tests from the specified group(s)',
|
' <info>--group=<fg=cyan><name></></info> Only runs tests from the specified group(s)',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Console Command instance.
|
|
||||||
*/
|
|
||||||
public function __construct(private OutputInterface $output)
|
public function __construct(private OutputInterface $output)
|
||||||
{
|
{
|
||||||
// ..
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the Console Command.
|
|
||||||
*/
|
|
||||||
public function __invoke(): void
|
public function __invoke(): void
|
||||||
{
|
{
|
||||||
foreach (self::HELP_MESSAGES as $message) {
|
foreach (self::HELP_MESSAGES as $message) {
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ use Symfony\Component\Console\Question\ConfirmationQuestion;
|
|||||||
final readonly class Thanks
|
final readonly class Thanks
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The support options.
|
|
||||||
*
|
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
private const array FUNDING_MESSAGES = [
|
private const array FUNDING_MESSAGES = [
|
||||||
@@ -33,19 +31,13 @@ final readonly class Thanks
|
|||||||
'Sponsor' => 'https://github.com/sponsors/nunomaduro',
|
'Sponsor' => 'https://github.com/sponsors/nunomaduro',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Console Command instance.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private InputInterface $input,
|
private InputInterface $input,
|
||||||
private OutputInterface $output
|
private OutputInterface $output
|
||||||
) {
|
) {
|
||||||
// ..
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the Console Command.
|
|
||||||
*/
|
|
||||||
public function __invoke(): void
|
public function __invoke(): void
|
||||||
{
|
{
|
||||||
$bootstrapper = new BootView($this->output);
|
$bootstrapper = new BootView($this->output);
|
||||||
|
|||||||
@@ -9,8 +9,5 @@ namespace Pest\Contracts;
|
|||||||
*/
|
*/
|
||||||
interface Bootstrapper
|
interface Bootstrapper
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Boots the bootstrapper.
|
|
||||||
*/
|
|
||||||
public function boot(): void;
|
public function boot(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ use NunoMaduro\Collision\Contracts\Adapters\Phpunit\HasPrintableTestCaseName as
|
|||||||
*/
|
*/
|
||||||
interface HasPrintableTestCaseName extends BaseHasPrintableTestCaseName
|
interface HasPrintableTestCaseName extends BaseHasPrintableTestCaseName
|
||||||
{
|
{
|
||||||
// ..
|
//
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
*/
|
*/
|
||||||
interface Panicable
|
interface Panicable
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Renders the panic on the given output.
|
|
||||||
*/
|
|
||||||
public function render(OutputInterface $output): void;
|
public function render(OutputInterface $output): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* The exit code to be used.
|
|
||||||
*/
|
|
||||||
public function exitCode(): int;
|
public function exitCode(): int;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,5 @@ namespace Pest\Contracts\Plugins;
|
|||||||
*/
|
*/
|
||||||
interface AddsOutput
|
interface AddsOutput
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Adds output after the Test Suite execution.
|
|
||||||
*/
|
|
||||||
public function addOutput(int $exitCode): int;
|
public function addOutput(int $exitCode): int;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,5 @@ namespace Pest\Contracts\Plugins;
|
|||||||
*/
|
*/
|
||||||
interface Bootable
|
interface Bootable
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Boots the plugin.
|
|
||||||
*/
|
|
||||||
public function boot(): void;
|
public function boot(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ namespace Pest\Contracts\Plugins;
|
|||||||
interface HandlesArguments
|
interface HandlesArguments
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Adds arguments before the Test Suite execution.
|
|
||||||
*
|
|
||||||
* @param array<int, string> $arguments
|
* @param array<int, string> $arguments
|
||||||
* @return array<int, string>
|
* @return array<int, string>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ namespace Pest\Contracts\Plugins;
|
|||||||
interface HandlesOriginalArguments
|
interface HandlesOriginalArguments
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Adds original arguments before the Test Suite execution.
|
|
||||||
*
|
|
||||||
* @param array<int, string> $arguments
|
* @param array<int, string> $arguments
|
||||||
*/
|
*/
|
||||||
public function handleOriginalArguments(array $arguments): void;
|
public function handleOriginalArguments(array $arguments): void;
|
||||||
|
|||||||
@@ -9,8 +9,5 @@ namespace Pest\Contracts\Plugins;
|
|||||||
*/
|
*/
|
||||||
interface Terminable
|
interface Terminable
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Terminates the plugin.
|
|
||||||
*/
|
|
||||||
public function terminate(): void;
|
public function terminate(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,5 @@ namespace Pest\Contracts;
|
|||||||
|
|
||||||
interface TestCaseFilter
|
interface TestCaseFilter
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Whether the test case is accepted.
|
|
||||||
*/
|
|
||||||
public function accept(string $testCaseFilename): bool;
|
public function accept(string $testCaseFilename): bool;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,5 @@ use Pest\Factories\TestCaseMethodFactory;
|
|||||||
|
|
||||||
interface TestCaseMethodFilter
|
interface TestCaseMethodFilter
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Whether the test case method is accepted.
|
|
||||||
*/
|
|
||||||
public function accept(TestCaseMethodFactory $factory): bool;
|
public function accept(TestCaseMethodFactory $factory): bool;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ use Pest\Factories\Attribute;
|
|||||||
final class Attributes
|
final class Attributes
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Evaluates the given attributes and returns the code.
|
|
||||||
*
|
|
||||||
* @param iterable<int, Attribute> $attributes
|
* @param iterable<int, Attribute> $attributes
|
||||||
*/
|
*/
|
||||||
public static function code(iterable $attributes): string
|
public static function code(iterable $attributes): string
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class AfterAllAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class AfterAllAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $filename)
|
public function __construct(string $filename)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('The afterAll already exists in the filename `%s`.', $filename));
|
parent::__construct(sprintf('The [afterAll] hook is already defined in [%s]. Each test file may only define it once.', $filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class AfterAllWithinDescribe extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class AfterAllWithinDescribe extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $filename)
|
public function __construct(string $filename)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('The afterAll method can not be used within describe functions. Filename `%s`.', $filename));
|
parent::__construct(sprintf('The [afterAll] hook may not be used inside a [describe] block. Please move it to the top level of [%s].', $filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class AfterBeforeTestFunction extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class AfterBeforeTestFunction extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $filename)
|
public function __construct(string $filename)
|
||||||
{
|
{
|
||||||
parent::__construct('After method cannot be used with before the [test|it] functions in the filename `['.$filename.']`.');
|
parent::__construct(sprintf('The [after] hook may only be chained onto [beforeEach] inside a [describe] block. Please move it inside one in [%s].', $filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class BeforeAllAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class BeforeAllAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $filename)
|
public function __construct(string $filename)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('The beforeAll already exists in the filename `%s`.', $filename));
|
parent::__construct(sprintf('The [beforeAll] hook is already defined in [%s]. Each test file may only define it once.', $filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class BeforeAllWithinDescribe extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class BeforeAllWithinDescribe extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $filename)
|
public function __construct(string $filename)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('The beforeAll method can not be used within describe functions. Filename `%s`.', $filename));
|
parent::__construct(sprintf('The [beforeAll] hook may not be used inside a [describe] block. Please move it to the top level of [%s].', $filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class DatasetAlreadyExists extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class DatasetAlreadyExists extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $name, string $scope)
|
public function __construct(string $name, string $scope)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('A dataset with the name `%s` already exists in scope [%s].', $name, $scope));
|
parent::__construct(sprintf('A dataset named [%s] is already registered in [%s]. Please choose a different name.', $name, $scope));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,9 @@ final class DatasetArgumentsMismatch extends Exception
|
|||||||
public function __construct(int $requiredCount, int $suppliedCount)
|
public function __construct(int $requiredCount, int $suppliedCount)
|
||||||
{
|
{
|
||||||
if ($requiredCount <= $suppliedCount) {
|
if ($requiredCount <= $suppliedCount) {
|
||||||
parent::__construct('Test argument names and dataset keys do not match');
|
parent::__construct('The test arguments do not match the dataset keys. Please make sure each argument is named after a key in the dataset.');
|
||||||
} else {
|
} else {
|
||||||
parent::__construct(sprintf('Test expects %d arguments but dataset only provides %d', $requiredCount, $suppliedCount));
|
parent::__construct(sprintf('The test expects [%d] argument(s), but the dataset only provides [%d].', $requiredCount, $suppliedCount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class DatasetDoesNotExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class DatasetDoesNotExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $name)
|
public function __construct(string $name)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf("A dataset with the name `%s` does not exist. You can create it using `dataset('%s', ['a', 'b']);`.", $name, $name));
|
parent::__construct(sprintf("A dataset named [%s] does not exist. You may create one using `dataset('%s', ['a', 'b']);`.", $name, $name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,18 +15,16 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
final class DatasetMissing extends BadFunctionCallException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class DatasetMissing extends BadFunctionCallException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Creates a new Exception instance.
|
|
||||||
*
|
|
||||||
* @param array<string, string> $arguments
|
* @param array<string, string> $arguments
|
||||||
*/
|
*/
|
||||||
public function __construct(string $file, string $name, array $arguments)
|
public function __construct(string $file, string $name, array $arguments)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf(
|
parent::__construct(sprintf(
|
||||||
'A test with the description [%s] has [%d] argument(s) ([%s]) and no dataset(s) provided in [%s]',
|
'The test [%s] in [%s] expects [%d] argument(s) ([%s]), but no dataset was provided. Please chain [with()] onto the test to supply one.',
|
||||||
$name,
|
$name,
|
||||||
|
$file,
|
||||||
count($arguments),
|
count($arguments),
|
||||||
implode(', ', array_map(static fn (string $arg, string $type): string => sprintf('%s $%s', $type, $arg), array_keys($arguments), $arguments)),
|
implode(', ', array_map(static fn (string $arg, string $type): string => sprintf('%s $%s', $type, $arg), array_keys($arguments), $arguments)),
|
||||||
$file,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,8 @@ use Exception;
|
|||||||
*/
|
*/
|
||||||
final class ExpectationNotFound extends Exception
|
final class ExpectationNotFound extends Exception
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new ExpectationNotFound instance from the given name.
|
|
||||||
*/
|
|
||||||
public static function fromName(string $name): ExpectationNotFound
|
public static function fromName(string $name): ExpectationNotFound
|
||||||
{
|
{
|
||||||
return new self("Expectation [$name] does not exist.");
|
return new self("The expectation [$name] does not exist. You may register it using [expect()->extend()].");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class FileOrFolderNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class FileOrFolderNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $filename)
|
public function __construct(string $filename)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('The file or folder with the name `%s` could not be found.', $filename));
|
parent::__construct(sprintf('The file or folder [%s] could not be found. Please check the path and try again.', $filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $message)
|
public function __construct(string $message)
|
||||||
{
|
{
|
||||||
parent::__construct($message, 1);
|
parent::__construct($message, 1);
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ final class InvalidExpectation extends LogicException implements ExceptionInterf
|
|||||||
*/
|
*/
|
||||||
public static function fromMethods(array $methods): never
|
public static function fromMethods(array $methods): never
|
||||||
{
|
{
|
||||||
throw new self(sprintf('Expectation [%s] is not valid.', implode('->', $methods)));
|
throw new self(sprintf('The expectation [%s] does not exist. Please check the spelling, or register it using [expect()->extend()].', implode('->', $methods)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,6 @@ final class InvalidExpectationValue extends InvalidArgumentException
|
|||||||
*/
|
*/
|
||||||
public static function expected(string $type): never
|
public static function expected(string $type): never
|
||||||
{
|
{
|
||||||
throw new self(sprintf('Invalid expectation value type. Expected [%s].', $type));
|
throw new self(sprintf('This expectation may only be used on a value of type [%s].', $type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class InvalidOption extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class InvalidOption extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $message)
|
public function __construct(string $message)
|
||||||
{
|
{
|
||||||
parent::__construct($message, 1);
|
parent::__construct($message, 1);
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class InvalidPestCommand extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class InvalidPestCommand extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('Please run [./vendor/bin/pest] instead.');
|
parent::__construct('Pest must be run through its own binary. Please run [./vendor/bin/pest] instead.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||||
|
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class InvalidTestClassName extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
|
{
|
||||||
|
public static function fromClassName(string $filename, string $className): self
|
||||||
|
{
|
||||||
|
return new self(sprintf(
|
||||||
|
'The test file [%s] would create the class [%s], which is not a valid PHP class name. Please rename the file.',
|
||||||
|
$filename,
|
||||||
|
$className,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromNamespace(string $filename, string $namespace, string $part): self
|
||||||
|
{
|
||||||
|
return new self(sprintf(
|
||||||
|
'The test file [%s] would create the namespace [%s], which is not a valid PHP namespace, because [%s] may not be used as a namespace name. Please rename that folder.',
|
||||||
|
$filename,
|
||||||
|
$namespace,
|
||||||
|
$part,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class MissingDependency extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class MissingDependency extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $feature, string $dependency)
|
public function __construct(string $feature, string $dependency)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('The feature "%s" requires "%s".', $feature, $dependency));
|
parent::__construct(sprintf('The [%s] feature requires [%s]. Please install it and try again.', $feature, $dependency));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
*/
|
*/
|
||||||
final class NoDirtyTestsFound extends InvalidArgumentException implements ExceptionInterface, Panicable, RenderlessEditor, RenderlessTrace
|
final class NoDirtyTestsFound extends InvalidArgumentException implements ExceptionInterface, Panicable, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Renders the panic on the given output.
|
|
||||||
*/
|
|
||||||
public function render(OutputInterface $output): void
|
public function render(OutputInterface $output): void
|
||||||
{
|
{
|
||||||
$output->writeln([
|
$output->writeln([
|
||||||
@@ -28,9 +25,6 @@ final class NoDirtyTestsFound extends InvalidArgumentException implements Except
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The exit code to be used.
|
|
||||||
*/
|
|
||||||
public function exitCode(): int
|
public function exitCode(): int
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -12,15 +12,12 @@ use RuntimeException;
|
|||||||
*/
|
*/
|
||||||
final class ShouldNotHappen extends RuntimeException
|
final class ShouldNotHappen extends RuntimeException
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(Exception $exception)
|
public function __construct(Exception $exception)
|
||||||
{
|
{
|
||||||
$message = $exception->getMessage();
|
$message = $exception->getMessage();
|
||||||
|
|
||||||
parent::__construct(sprintf(<<<'EOF'
|
parent::__construct(sprintf(<<<'EOF'
|
||||||
This should not happen - please create an new issue here: https://github.com/pestphp/pest/issues
|
This should not have happened. Please report it here: https://github.com/pestphp/pest/issues
|
||||||
|
|
||||||
Issue: %s
|
Issue: %s
|
||||||
PHP version: %s
|
PHP version: %s
|
||||||
@@ -29,9 +26,6 @@ EOF
|
|||||||
, $message, phpversion(), PHP_OS), 1, $exception);
|
, $message, phpversion(), PHP_OS), 1, $exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance of should not happen without a specific exception.
|
|
||||||
*/
|
|
||||||
public static function fromMessage(string $message): ShouldNotHappen
|
public static function fromMessage(string $message): ShouldNotHappen
|
||||||
{
|
{
|
||||||
return new ShouldNotHappen(new Exception($message));
|
return new ShouldNotHappen(new Exception($message));
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class TestAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class TestAlreadyExist extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $fileName, string $description)
|
public function __construct(string $fileName, string $description)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('A test with the description `%s` already exists in the filename `%s`.', $description, $fileName));
|
parent::__construct(sprintf('A test named [%s] already exists in [%s]. Please give this test a different description.', $description, $fileName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,10 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class TestCaseAlreadyInUse extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class TestCaseAlreadyInUse extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $inUse, string $newOne, string $folder)
|
public function __construct(string $inUse, string $newOne, string $folder)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf(
|
parent::__construct(sprintf(
|
||||||
'Test case [%s] can not be used. The folder [%s] already uses the test case [%s].',
|
'The test case [%s] may not be used here. The folder [%s] is already bound to the test case [%s].',
|
||||||
$newOne,
|
$newOne,
|
||||||
$folder,
|
$folder,
|
||||||
$inUse,
|
$inUse,
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class TestCaseClassOrTraitNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class TestCaseClassOrTraitNotFound extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $testCaseClass)
|
public function __construct(string $testCaseClass)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('The class `%s` was not found.', $testCaseClass));
|
parent::__construct(sprintf('The class or trait [%s] could not be found. Please check the name, and make sure it is autoloadable.', $testCaseClass));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,14 +15,11 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class TestClosureMustNotBeStatic extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class TestClosureMustNotBeStatic extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(TestCaseMethodFactory $method)
|
public function __construct(TestCaseMethodFactory $method)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
sprintf(
|
sprintf(
|
||||||
'Test closure must not be static. Please remove the [static] keyword from the [%s] method in [%s].',
|
'Test closures may not be static. Please remove the [static] keyword from the test [%s] in [%s].',
|
||||||
$method->description,
|
$method->description,
|
||||||
$method->filename
|
$method->filename
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ use Symfony\Component\Console\Exception\ExceptionInterface;
|
|||||||
*/
|
*/
|
||||||
final class TestDescriptionMissing extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
final class TestDescriptionMissing extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Exception instance.
|
|
||||||
*/
|
|
||||||
public function __construct(string $fileName)
|
public function __construct(string $fileName)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf('Test description is missing in the filename `%s`.', $fileName));
|
parent::__construct(sprintf('A test in [%s] is missing its description. Please give every test a description.', $fileName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||||
|
use Pest\Contracts\Panicable;
|
||||||
|
use RuntimeException;
|
||||||
|
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class TiaRequiresCommit extends RuntimeException implements ExceptionInterface, Panicable, RenderlessEditor, RenderlessTrace
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct(
|
||||||
|
'Tia mode requires a repository with at least one commit, so the baseline it records can be anchored to a revision.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(OutputInterface $output): void
|
||||||
|
{
|
||||||
|
$output->writeln([
|
||||||
|
'',
|
||||||
|
' <fg=white;options=bold;bg=red> ERROR </> Tia mode requires at least one commit.',
|
||||||
|
'',
|
||||||
|
' A baseline is anchored to the revision it was recorded at, and this repository',
|
||||||
|
' has none yet, so there is nothing to record against and nothing to compare a',
|
||||||
|
' later run to.',
|
||||||
|
'',
|
||||||
|
' Commit once, then run again:',
|
||||||
|
'',
|
||||||
|
' <fg=yellow>git add . && git commit -m "Initial commit"</>',
|
||||||
|
'',
|
||||||
|
' Runs without <fg=yellow>--tia</> are unaffected.',
|
||||||
|
'',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exitCode(): int
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||||
|
use Pest\Contracts\Panicable;
|
||||||
|
use RuntimeException;
|
||||||
|
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class TiaRequiresDefaultBranch extends RuntimeException implements ExceptionInterface, Panicable, RenderlessEditor, RenderlessTrace
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct(
|
||||||
|
'Tia mode could not determine the default branch every other branch falls back to reading.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(OutputInterface $output): void
|
||||||
|
{
|
||||||
|
$output->writeln([
|
||||||
|
'',
|
||||||
|
' <fg=white;options=bold;bg=red> ERROR </> Tia mode could not determine the default branch.',
|
||||||
|
'',
|
||||||
|
' It is the branch whose baseline every other branch falls back to reading, and',
|
||||||
|
' nothing in this checkout names it: the repository has a remote, but no',
|
||||||
|
' <fg=yellow>origin/HEAD</>, and no CI provider stated it either. Guessing would re-run the',
|
||||||
|
' whole suite on every new branch while reporting it as a cache hit.',
|
||||||
|
'',
|
||||||
|
' Name the branch in <fg=yellow>tests/Pest.php</>:',
|
||||||
|
'',
|
||||||
|
' <fg=yellow>pest()->tia()->defaultBranch(\'master\');</>',
|
||||||
|
'',
|
||||||
|
' Or let git answer, once per clone:',
|
||||||
|
'',
|
||||||
|
' <fg=yellow>git remote set-head origin --auto</>',
|
||||||
|
'',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exitCode(): int
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ final class TiaRequiresPestTests extends RuntimeException implements ExceptionIn
|
|||||||
public function __construct(private readonly string $className, string $filename)
|
public function __construct(private readonly string $className, string $filename)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf(
|
parent::__construct(sprintf(
|
||||||
'Tia mode requires only functional based Pest tests, but encountered PHPUnit class [%s] in [%s].',
|
'Tia mode supports functional Pest tests only, but found the PHPUnit class [%s] in [%s]. Please convert it to a Pest test, or run without Tia.',
|
||||||
$className,
|
$className,
|
||||||
$filename,
|
$filename,
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||||
|
use Pest\Contracts\Panicable;
|
||||||
|
use RuntimeException;
|
||||||
|
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class TiaRequiresRemote extends RuntimeException implements ExceptionInterface, Panicable, RenderlessEditor, RenderlessTrace
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct(
|
||||||
|
'Tia mode requires a repository with a remote, so the default branch every other branch falls back to reading can be resolved.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(OutputInterface $output): void
|
||||||
|
{
|
||||||
|
$output->writeln([
|
||||||
|
'',
|
||||||
|
' <fg=white;options=bold;bg=red> ERROR </> Tia mode requires a repository with a remote.',
|
||||||
|
'',
|
||||||
|
' Without one there is no way to tell which branch is the default — the branch',
|
||||||
|
' whose baseline every other branch falls back to reading — so the first run on',
|
||||||
|
' each new branch would silently re-run the whole suite.',
|
||||||
|
'',
|
||||||
|
' Add a remote, or name the branch yourself in <fg=yellow>tests/Pest.php</>:',
|
||||||
|
'',
|
||||||
|
' <fg=yellow>pest()->tia()->defaultBranch(\'master\');</>',
|
||||||
|
'',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exitCode(): int
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ final class TiaRequiresRepositoryRoot extends RuntimeException implements Except
|
|||||||
public function __construct(private readonly string $subdirectoryPrefix)
|
public function __construct(private readonly string $subdirectoryPrefix)
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf(
|
parent::__construct(sprintf(
|
||||||
'Tia mode requires the project root to be the git repository root, but it sits in the subdirectory [%s] of a larger repo.',
|
'Tia mode requires the project root to be the git repository root, but this project sits in the subdirectory [%s] of a larger repository. Please give it its own repository to use Tia.',
|
||||||
$this->subdirectoryPrefix,
|
$this->subdirectoryPrefix,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-204
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest;
|
namespace Pest;
|
||||||
|
|
||||||
use Attribute;
|
|
||||||
use BadMethodCallException;
|
use BadMethodCallException;
|
||||||
use Closure;
|
use Closure;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
@@ -60,19 +59,15 @@ final class Expectation
|
|||||||
use Retrievable;
|
use Retrievable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new expectation.
|
|
||||||
*
|
|
||||||
* @param TValue $value
|
* @param TValue $value
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public mixed $value
|
public mixed $value
|
||||||
) {
|
) {
|
||||||
// ..
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new expectation.
|
|
||||||
*
|
|
||||||
* @template TAndValue
|
* @template TAndValue
|
||||||
*
|
*
|
||||||
* @param TAndValue $value
|
* @param TAndValue $value
|
||||||
@@ -84,8 +79,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new expectation with the decoded JSON value.
|
|
||||||
*
|
|
||||||
* @return self<array<int|string, mixed>|bool>
|
* @return self<array<int|string, mixed>|bool>
|
||||||
*/
|
*/
|
||||||
public function json(): Expectation
|
public function json(): Expectation
|
||||||
@@ -103,8 +96,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dump the expectation value.
|
|
||||||
*
|
|
||||||
* @return self<TValue>
|
* @return self<TValue>
|
||||||
*/
|
*/
|
||||||
public function dump(mixed ...$arguments): self
|
public function dump(mixed ...$arguments): self
|
||||||
@@ -118,9 +109,6 @@ final class Expectation
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Dump the expectation value and end the script.
|
|
||||||
*/
|
|
||||||
public function dd(mixed ...$arguments): never
|
public function dd(mixed ...$arguments): never
|
||||||
{
|
{
|
||||||
if (function_exists('dd')) {
|
if (function_exists('dd')) {
|
||||||
@@ -141,8 +129,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dump the expectation value when the result of the condition is truthy.
|
|
||||||
*
|
|
||||||
* @param (Closure(TValue): bool)|bool $condition
|
* @param (Closure(TValue): bool)|bool $condition
|
||||||
* @return self<TValue>
|
* @return self<TValue>
|
||||||
*/
|
*/
|
||||||
@@ -158,8 +144,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dump the expectation value when the result of the condition is falsy.
|
|
||||||
*
|
|
||||||
* @param (Closure(TValue): bool)|bool $condition
|
* @param (Closure(TValue): bool)|bool $condition
|
||||||
* @return self<TValue>
|
* @return self<TValue>
|
||||||
*/
|
*/
|
||||||
@@ -175,8 +159,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the expectation value to Ray along with all given arguments.
|
|
||||||
*
|
|
||||||
* @return self<TValue>
|
* @return self<TValue>
|
||||||
*/
|
*/
|
||||||
public function ray(mixed ...$arguments): self
|
public function ray(mixed ...$arguments): self
|
||||||
@@ -189,8 +171,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the opposite expectation for the value.
|
|
||||||
*
|
|
||||||
* @return OppositeExpectation<TValue>
|
* @return OppositeExpectation<TValue>
|
||||||
*/
|
*/
|
||||||
public function not(): OppositeExpectation
|
public function not(): OppositeExpectation
|
||||||
@@ -199,8 +179,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an expectation on each item of the iterable "value".
|
|
||||||
*
|
|
||||||
* @return EachExpectation<TValue>
|
* @return EachExpectation<TValue>
|
||||||
*/
|
*/
|
||||||
public function each(?callable $callback = null): EachExpectation
|
public function each(?callable $callback = null): EachExpectation
|
||||||
@@ -219,8 +197,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows you to specify a sequential set of expectations for each item in a iterable "value".
|
|
||||||
*
|
|
||||||
* @template TSequenceValue
|
* @template TSequenceValue
|
||||||
*
|
*
|
||||||
* @param (callable(self<TValue>, self<string|int>): void)|TSequenceValue ...$callbacks
|
* @param (callable(self<TValue>, self<string|int>): void)|TSequenceValue ...$callbacks
|
||||||
@@ -258,8 +234,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the subject matches one of the given "expressions", the expression callback will run.
|
|
||||||
*
|
|
||||||
* @template TMatchSubject of array-key
|
* @template TMatchSubject of array-key
|
||||||
*
|
*
|
||||||
* @param (callable(): TMatchSubject)|TMatchSubject $subject
|
* @param (callable(): TMatchSubject)|TMatchSubject $subject
|
||||||
@@ -298,8 +272,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply the callback if the given "condition" is falsy.
|
|
||||||
*
|
|
||||||
* @param (callable(): bool)|bool $condition
|
* @param (callable(): bool)|bool $condition
|
||||||
* @param callable(Expectation<TValue>): mixed $callback
|
* @param callable(Expectation<TValue>): mixed $callback
|
||||||
* @return self<TValue>
|
* @return self<TValue>
|
||||||
@@ -314,8 +286,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply the callback if the given "condition" is truthy.
|
|
||||||
*
|
|
||||||
* @param (callable(): bool)|bool $condition
|
* @param (callable(): bool)|bool $condition
|
||||||
* @param callable(self<TValue>): mixed $callback
|
* @param callable(self<TValue>): mixed $callback
|
||||||
* @return self<TValue>
|
* @return self<TValue>
|
||||||
@@ -334,8 +304,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically calls methods on the class or creates a new higher order expectation.
|
|
||||||
*
|
|
||||||
* @param array<int, mixed> $parameters
|
* @param array<int, mixed> $parameters
|
||||||
* @return Expectation<TValue>|HigherOrderExpectation<Expectation<TValue>, TValue>
|
* @return Expectation<TValue>|HigherOrderExpectation<Expectation<TValue>, TValue>
|
||||||
*/
|
*/
|
||||||
@@ -350,7 +318,7 @@ final class Expectation
|
|||||||
|
|
||||||
if (! is_object($this->value)) {
|
if (! is_object($this->value)) {
|
||||||
throw new BadMethodCallException(sprintf(
|
throw new BadMethodCallException(sprintf(
|
||||||
'Method "%s" does not exist in %s.',
|
'Method [%s] does not exist in [%s].',
|
||||||
$method,
|
$method,
|
||||||
gettype($this->value)
|
gettype($this->value)
|
||||||
));
|
));
|
||||||
@@ -379,8 +347,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new expectation closure from the given name.
|
|
||||||
*
|
|
||||||
* @throws ExpectationNotFound
|
* @throws ExpectationNotFound
|
||||||
*/
|
*/
|
||||||
private function getExpectationClosure(string $name): Closure
|
private function getExpectationClosure(string $name): Closure
|
||||||
@@ -402,8 +368,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically calls methods on the class without any arguments or creates a new higher order expectation.
|
|
||||||
*
|
|
||||||
* @return Expectation<TValue>|OppositeExpectation<TValue>|EachExpectation<TValue>|HigherOrderExpectation<Expectation<TValue>, TValue|null>|TValue
|
* @return Expectation<TValue>|OppositeExpectation<TValue>|EachExpectation<TValue>|HigherOrderExpectation<Expectation<TValue>, TValue|null>|TValue
|
||||||
*/
|
*/
|
||||||
public function __get(string $name): mixed
|
public function __get(string $name): mixed
|
||||||
@@ -422,9 +386,6 @@ final class Expectation
|
|||||||
return $this->{$name}();
|
return $this->{$name}();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the given expectation method exists.
|
|
||||||
*/
|
|
||||||
public static function hasMethod(string $name): bool
|
public static function hasMethod(string $name): bool
|
||||||
{
|
{
|
||||||
return method_exists(self::class, $name)
|
return method_exists(self::class, $name)
|
||||||
@@ -432,17 +393,12 @@ final class Expectation
|
|||||||
|| self::hasExtend($name);
|
|| self::hasExtend($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Matches any value.
|
|
||||||
*/
|
|
||||||
public function any(): Any
|
public function any(): Any
|
||||||
{
|
{
|
||||||
return new Any;
|
return new Any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target use the given dependencies.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $targets
|
* @param array<int, string>|string $targets
|
||||||
*/
|
*/
|
||||||
public function toUse(array|string $targets): ArchExpectation
|
public function toUse(array|string $targets): ArchExpectation
|
||||||
@@ -450,9 +406,6 @@ final class Expectation
|
|||||||
return ToUse::make($this, $targets);
|
return ToUse::make($this, $targets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target does have the given permissions
|
|
||||||
*/
|
|
||||||
public function toHaveFileSystemPermissions(string $permissions): ArchExpectation
|
public function toHaveFileSystemPermissions(string $permissions): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -463,9 +416,6 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target to have line count less than the given number.
|
|
||||||
*/
|
|
||||||
public function toHaveLineCountLessThan(int $lines): ArchExpectation
|
public function toHaveLineCountLessThan(int $lines): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -476,9 +426,6 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target have all methods documented.
|
|
||||||
*/
|
|
||||||
public function toHaveMethodsDocumented(): ArchExpectation
|
public function toHaveMethodsDocumented(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -495,9 +442,6 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target have all properties documented.
|
|
||||||
*/
|
|
||||||
public function toHavePropertiesDocumented(): ArchExpectation
|
public function toHavePropertiesDocumented(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -515,9 +459,6 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target use the "declare(strict_types=1)" declaration.
|
|
||||||
*/
|
|
||||||
public function toUseStrictTypes(): ArchExpectation
|
public function toUseStrictTypes(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -528,9 +469,6 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target uses strict equality.
|
|
||||||
*/
|
|
||||||
public function toUseStrictEquality(): ArchExpectation
|
public function toUseStrictEquality(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -541,9 +479,6 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is final.
|
|
||||||
*/
|
|
||||||
public function toBeFinal(): ArchExpectation
|
public function toBeFinal(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -554,9 +489,6 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is readonly.
|
|
||||||
*/
|
|
||||||
public function toBeReadonly(): ArchExpectation
|
public function toBeReadonly(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -567,9 +499,6 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is trait.
|
|
||||||
*/
|
|
||||||
public function toBeTrait(): ArchExpectation
|
public function toBeTrait(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -580,17 +509,11 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation targets are traits.
|
|
||||||
*/
|
|
||||||
public function toBeTraits(): ArchExpectation
|
public function toBeTraits(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeTrait();
|
return $this->toBeTrait();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is abstract.
|
|
||||||
*/
|
|
||||||
public function toBeAbstract(): ArchExpectation
|
public function toBeAbstract(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -602,8 +525,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target has a specific method.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $method
|
* @param array<int, string>|string $method
|
||||||
*/
|
*/
|
||||||
public function toHaveMethod(array|string $method): ArchExpectation
|
public function toHaveMethod(array|string $method): ArchExpectation
|
||||||
@@ -613,14 +534,12 @@ final class Expectation
|
|||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
$this,
|
$this,
|
||||||
fn (ObjectDescription $object): bool => count(array_filter($methods, fn (string $method): bool => isset($object->reflectionClass) && $object->reflectionClass->hasMethod($method))) === count($methods),
|
fn (ObjectDescription $object): bool => count(array_filter($methods, fn (string $method): bool => isset($object->reflectionClass) && $object->reflectionClass->hasMethod($method))) === count($methods),
|
||||||
sprintf("to have method '%s'", implode("', '", $methods)),
|
sprintf('to have method [%s]', implode('], [', $methods)),
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target has a specific methods.
|
|
||||||
*
|
|
||||||
* @param array<int, string> $methods
|
* @param array<int, string> $methods
|
||||||
*/
|
*/
|
||||||
public function toHaveMethods(array $methods): ArchExpectation
|
public function toHaveMethods(array $methods): ArchExpectation
|
||||||
@@ -628,57 +547,36 @@ final class Expectation
|
|||||||
return $this->toHaveMethod($methods);
|
return $this->toHaveMethod($methods);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*/
|
|
||||||
public function toHavePublicMethodsBesides(): void
|
public function toHavePublicMethodsBesides(): void
|
||||||
{
|
{
|
||||||
throw InvalidExpectation::fromMethods(['toHavePublicMethodsBesides']);
|
throw InvalidExpectation::fromMethods(['toHavePublicMethodsBesides']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*/
|
|
||||||
public function toHavePublicMethods(): void
|
public function toHavePublicMethods(): void
|
||||||
{
|
{
|
||||||
throw InvalidExpectation::fromMethods(['toHavePublicMethods']);
|
throw InvalidExpectation::fromMethods(['toHavePublicMethods']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*/
|
|
||||||
public function toHaveProtectedMethodsBesides(): void
|
public function toHaveProtectedMethodsBesides(): void
|
||||||
{
|
{
|
||||||
throw InvalidExpectation::fromMethods(['toHaveProtectedMethodsBesides']);
|
throw InvalidExpectation::fromMethods(['toHaveProtectedMethodsBesides']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*/
|
|
||||||
public function toHaveProtectedMethods(): void
|
public function toHaveProtectedMethods(): void
|
||||||
{
|
{
|
||||||
throw InvalidExpectation::fromMethods(['toHaveProtectedMethods']);
|
throw InvalidExpectation::fromMethods(['toHaveProtectedMethods']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*/
|
|
||||||
public function toHavePrivateMethodsBesides(): void
|
public function toHavePrivateMethodsBesides(): void
|
||||||
{
|
{
|
||||||
throw InvalidExpectation::fromMethods(['toHavePrivateMethodsBesides']);
|
throw InvalidExpectation::fromMethods(['toHavePrivateMethodsBesides']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*/
|
|
||||||
public function toHavePrivateMethods(): void
|
public function toHavePrivateMethods(): void
|
||||||
{
|
{
|
||||||
throw InvalidExpectation::fromMethods(['toHavePrivateMethods']);
|
throw InvalidExpectation::fromMethods(['toHavePrivateMethods']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is cased correctly.
|
|
||||||
*/
|
|
||||||
public function toBeCasedCorrectly(): ArchExpectation
|
public function toBeCasedCorrectly(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -711,9 +609,6 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is enum.
|
|
||||||
*/
|
|
||||||
public function toBeEnum(): ArchExpectation
|
public function toBeEnum(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -724,17 +619,11 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation targets are enums.
|
|
||||||
*/
|
|
||||||
public function toBeEnums(): ArchExpectation
|
public function toBeEnums(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeEnum();
|
return $this->toBeEnum();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is a class.
|
|
||||||
*/
|
|
||||||
public function toBeClass(): ArchExpectation
|
public function toBeClass(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -745,17 +634,11 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation targets are classes.
|
|
||||||
*/
|
|
||||||
public function toBeClasses(): ArchExpectation
|
public function toBeClasses(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeClass();
|
return $this->toBeClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is interface.
|
|
||||||
*/
|
|
||||||
public function toBeInterface(): ArchExpectation
|
public function toBeInterface(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -766,30 +649,21 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation targets are interfaces.
|
|
||||||
*/
|
|
||||||
public function toBeInterfaces(): ArchExpectation
|
public function toBeInterfaces(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeInterface();
|
return $this->toBeInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target to be subclass of the given class.
|
|
||||||
*/
|
|
||||||
public function toExtend(string $class): ArchExpectation
|
public function toExtend(string $class): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
$this,
|
$this,
|
||||||
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && ($class === $object->reflectionClass->getName() || $object->reflectionClass->isSubclassOf($class)),
|
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && ($class === $object->reflectionClass->getName() || $object->reflectionClass->isSubclassOf($class)),
|
||||||
sprintf("to extend '%s'", $class),
|
sprintf('to extend [%s]', $class),
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target to be have a parent class.
|
|
||||||
*/
|
|
||||||
public function toExtendNothing(): ArchExpectation
|
public function toExtendNothing(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -800,17 +674,12 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target to use the given trait.
|
|
||||||
*/
|
|
||||||
public function toUseTrait(string $trait): ArchExpectation
|
public function toUseTrait(string $trait): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toUseTraits($trait);
|
return $this->toUseTraits($trait);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target to use the given traits.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $traits
|
* @param array<int, string>|string $traits
|
||||||
*/
|
*/
|
||||||
public function toUseTraits(array|string $traits): ArchExpectation
|
public function toUseTraits(array|string $traits): ArchExpectation
|
||||||
@@ -852,9 +721,6 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target to not implement any interfaces.
|
|
||||||
*/
|
|
||||||
public function toImplementNothing(): ArchExpectation
|
public function toImplementNothing(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -866,8 +732,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target to only implement the given interfaces.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $interfaces
|
* @param array<int, string>|string $interfaces
|
||||||
*/
|
*/
|
||||||
public function toOnlyImplement(array|string $interfaces): ArchExpectation
|
public function toOnlyImplement(array|string $interfaces): ArchExpectation
|
||||||
@@ -884,35 +748,27 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target to have the given prefix.
|
|
||||||
*/
|
|
||||||
public function toHavePrefix(string $prefix): ArchExpectation
|
public function toHavePrefix(string $prefix): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
$this,
|
$this,
|
||||||
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && str_starts_with($object->reflectionClass->getShortName(), $prefix),
|
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && str_starts_with($object->reflectionClass->getShortName(), $prefix),
|
||||||
"to have prefix '{$prefix}'",
|
"to have prefix [{$prefix}]",
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target to have the given suffix.
|
|
||||||
*/
|
|
||||||
public function toHaveSuffix(string $suffix): ArchExpectation
|
public function toHaveSuffix(string $suffix): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
$this,
|
$this,
|
||||||
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && str_ends_with($object->reflectionClass->getName(), $suffix),
|
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && str_ends_with($object->reflectionClass->getName(), $suffix),
|
||||||
"to have suffix '{$suffix}'",
|
"to have suffix [{$suffix}]",
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target to implement the given interfaces.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $interfaces
|
* @param array<int, string>|string $interfaces
|
||||||
*/
|
*/
|
||||||
public function toImplement(array|string $interfaces): ArchExpectation
|
public function toImplement(array|string $interfaces): ArchExpectation
|
||||||
@@ -928,8 +784,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target "only" use on the given dependencies.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $targets
|
* @param array<int, string>|string $targets
|
||||||
*/
|
*/
|
||||||
public function toOnlyUse(array|string $targets): ArchExpectation
|
public function toOnlyUse(array|string $targets): ArchExpectation
|
||||||
@@ -937,33 +791,22 @@ final class Expectation
|
|||||||
return ToOnlyUse::make($this, $targets);
|
return ToOnlyUse::make($this, $targets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target does not use any dependencies.
|
|
||||||
*/
|
|
||||||
public function toUseNothing(): ArchExpectation
|
public function toUseNothing(): ArchExpectation
|
||||||
{
|
{
|
||||||
return ToUseNothing::make($this);
|
return ToUseNothing::make($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the source code of the given expectation target does not include suspicious characters.
|
|
||||||
*/
|
|
||||||
public function toHaveSuspiciousCharacters(): ArchExpectation
|
public function toHaveSuspiciousCharacters(): ArchExpectation
|
||||||
{
|
{
|
||||||
throw InvalidExpectation::fromMethods(['toHaveSuspiciousCharacters']);
|
throw InvalidExpectation::fromMethods(['toHaveSuspiciousCharacters']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*/
|
|
||||||
public function toBeUsed(): void
|
public function toBeUsed(): void
|
||||||
{
|
{
|
||||||
throw InvalidExpectation::fromMethods(['toBeUsed']);
|
throw InvalidExpectation::fromMethods(['toBeUsed']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation dependency is used by the given targets.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $targets
|
* @param array<int, string>|string $targets
|
||||||
*/
|
*/
|
||||||
public function toBeUsedIn(array|string $targets): ArchExpectation
|
public function toBeUsedIn(array|string $targets): ArchExpectation
|
||||||
@@ -972,8 +815,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation dependency is "only" used by the given targets.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $targets
|
* @param array<int, string>|string $targets
|
||||||
*/
|
*/
|
||||||
public function toOnlyBeUsedIn(array|string $targets): ArchExpectation
|
public function toOnlyBeUsedIn(array|string $targets): ArchExpectation
|
||||||
@@ -981,17 +822,11 @@ final class Expectation
|
|||||||
return ToOnlyBeUsedIn::make($this, $targets);
|
return ToOnlyBeUsedIn::make($this, $targets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation dependency is not used.
|
|
||||||
*/
|
|
||||||
public function toBeUsedInNothing(): ArchExpectation
|
public function toBeUsedInNothing(): ArchExpectation
|
||||||
{
|
{
|
||||||
return ToBeUsedInNothing::make($this);
|
return ToBeUsedInNothing::make($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation dependency is an invokable class.
|
|
||||||
*/
|
|
||||||
public function toBeInvokable(): ArchExpectation
|
public function toBeInvokable(): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -1003,8 +838,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation is iterable and contains snake_case keys.
|
|
||||||
*
|
|
||||||
* @return self<TValue>
|
* @return self<TValue>
|
||||||
*/
|
*/
|
||||||
public function toHaveSnakeCaseKeys(string $message = ''): self
|
public function toHaveSnakeCaseKeys(string $message = ''): self
|
||||||
@@ -1027,8 +860,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation is iterable and contains kebab-case keys.
|
|
||||||
*
|
|
||||||
* @return self<TValue>
|
* @return self<TValue>
|
||||||
*/
|
*/
|
||||||
public function toHaveKebabCaseKeys(string $message = ''): self
|
public function toHaveKebabCaseKeys(string $message = ''): self
|
||||||
@@ -1051,8 +882,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation is iterable and contains camelCase keys.
|
|
||||||
*
|
|
||||||
* @return self<TValue>
|
* @return self<TValue>
|
||||||
*/
|
*/
|
||||||
public function toHaveCamelCaseKeys(string $message = ''): self
|
public function toHaveCamelCaseKeys(string $message = ''): self
|
||||||
@@ -1075,8 +904,6 @@ final class Expectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation is iterable and contains StudlyCase keys.
|
|
||||||
*
|
|
||||||
* @return self<TValue>
|
* @return self<TValue>
|
||||||
*/
|
*/
|
||||||
public function toHaveStudlyCaseKeys(string $message = ''): self
|
public function toHaveStudlyCaseKeys(string $message = ''): self
|
||||||
@@ -1098,38 +925,26 @@ final class Expectation
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target to have the given attribute.
|
|
||||||
*/
|
|
||||||
public function toHaveAttribute(string $attribute): ArchExpectation
|
public function toHaveAttribute(string $attribute): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
$this,
|
$this,
|
||||||
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->getAttributes($attribute) !== [],
|
fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->getAttributes($attribute) !== [],
|
||||||
"to have attribute '{$attribute}'",
|
"to have attribute [{$attribute}]",
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target has a constructor method.
|
|
||||||
*/
|
|
||||||
public function toHaveConstructor(): ArchExpectation
|
public function toHaveConstructor(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toHaveMethod('__construct');
|
return $this->toHaveMethod('__construct');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target has a destructor method.
|
|
||||||
*/
|
|
||||||
public function toHaveDestructor(): ArchExpectation
|
public function toHaveDestructor(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toHaveMethod('__destruct');
|
return $this->toHaveMethod('__destruct');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is a backed enum of given type.
|
|
||||||
*/
|
|
||||||
private function toBeBackedEnum(string $backingType): ArchExpectation
|
private function toBeBackedEnum(string $backingType): ArchExpectation
|
||||||
{
|
{
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
@@ -1143,33 +958,21 @@ final class Expectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation targets are string backed enums.
|
|
||||||
*/
|
|
||||||
public function toBeStringBackedEnums(): ArchExpectation
|
public function toBeStringBackedEnums(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeStringBackedEnum();
|
return $this->toBeStringBackedEnum();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation targets are int backed enums.
|
|
||||||
*/
|
|
||||||
public function toBeIntBackedEnums(): ArchExpectation
|
public function toBeIntBackedEnums(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeIntBackedEnum();
|
return $this->toBeIntBackedEnum();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is a string backed enum.
|
|
||||||
*/
|
|
||||||
public function toBeStringBackedEnum(): ArchExpectation
|
public function toBeStringBackedEnum(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeBackedEnum('string');
|
return $this->toBeBackedEnum('string');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is an int backed enum.
|
|
||||||
*/
|
|
||||||
public function toBeIntBackedEnum(): ArchExpectation
|
public function toBeIntBackedEnum(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeBackedEnum('int');
|
return $this->toBeBackedEnum('int');
|
||||||
|
|||||||
@@ -17,21 +17,14 @@ use function expect;
|
|||||||
*/
|
*/
|
||||||
final class EachExpectation
|
final class EachExpectation
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Indicates if the expectation is the opposite.
|
|
||||||
*/
|
|
||||||
private bool $opposite = false;
|
private bool $opposite = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an expectation on each item of the iterable "value".
|
|
||||||
*
|
|
||||||
* @param Expectation<TValue> $original
|
* @param Expectation<TValue> $original
|
||||||
*/
|
*/
|
||||||
public function __construct(private readonly Expectation $original) {}
|
public function __construct(private readonly Expectation $original) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new expectation.
|
|
||||||
*
|
|
||||||
* @template TAndValue
|
* @template TAndValue
|
||||||
*
|
*
|
||||||
* @param TAndValue $value
|
* @param TAndValue $value
|
||||||
@@ -43,8 +36,6 @@ final class EachExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the opposite expectation for the value.
|
|
||||||
*
|
|
||||||
* @return self<TValue>
|
* @return self<TValue>
|
||||||
*/
|
*/
|
||||||
public function not(): self
|
public function not(): self
|
||||||
@@ -55,8 +46,6 @@ final class EachExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically calls methods on the class with the given arguments on each item.
|
|
||||||
*
|
|
||||||
* @param array<int|string, mixed> $arguments
|
* @param array<int|string, mixed> $arguments
|
||||||
* @return self<TValue>
|
* @return self<TValue>
|
||||||
*/
|
*/
|
||||||
@@ -73,8 +62,6 @@ final class EachExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically calls methods on the class without any arguments on each item.
|
|
||||||
*
|
|
||||||
* @return self<TValue>
|
* @return self<TValue>
|
||||||
*/
|
*/
|
||||||
public function __get(string $name): self
|
public function __get(string $name): self
|
||||||
|
|||||||
@@ -25,19 +25,11 @@ final class HigherOrderExpectation
|
|||||||
*/
|
*/
|
||||||
private Expectation|EachExpectation $expectation;
|
private Expectation|EachExpectation $expectation;
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the expectation is the opposite.
|
|
||||||
*/
|
|
||||||
private bool $opposite = false;
|
private bool $opposite = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the expectation should reset the value.
|
|
||||||
*/
|
|
||||||
private bool $shouldReset = false;
|
private bool $shouldReset = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new higher order expectation.
|
|
||||||
*
|
|
||||||
* @param Expectation<TOriginalValue> $original
|
* @param Expectation<TOriginalValue> $original
|
||||||
* @param TValue $value
|
* @param TValue $value
|
||||||
*/
|
*/
|
||||||
@@ -47,8 +39,6 @@ final class HigherOrderExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the opposite expectation for the value.
|
|
||||||
*
|
|
||||||
* @return self<TOriginalValue, TValue>
|
* @return self<TOriginalValue, TValue>
|
||||||
*/
|
*/
|
||||||
public function not(): self
|
public function not(): self
|
||||||
@@ -59,8 +49,6 @@ final class HigherOrderExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Expectation.
|
|
||||||
*
|
|
||||||
* @template TExpectValue
|
* @template TExpectValue
|
||||||
*
|
*
|
||||||
* @param TExpectValue $value
|
* @param TExpectValue $value
|
||||||
@@ -72,8 +60,6 @@ final class HigherOrderExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new expectation.
|
|
||||||
*
|
|
||||||
* @template TExpectValue
|
* @template TExpectValue
|
||||||
*
|
*
|
||||||
* @param TExpectValue $value
|
* @param TExpectValue $value
|
||||||
@@ -85,9 +71,6 @@ final class HigherOrderExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scope an expectation callback to the current value in
|
|
||||||
* the HigherOrderExpectation chain.
|
|
||||||
*
|
|
||||||
* @param Closure(Expectation<TValue>): void $expectation
|
* @param Closure(Expectation<TValue>): void $expectation
|
||||||
* @return HigherOrderExpectation<TOriginalValue, TOriginalValue>
|
* @return HigherOrderExpectation<TOriginalValue, TOriginalValue>
|
||||||
*/
|
*/
|
||||||
@@ -99,8 +82,6 @@ final class HigherOrderExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new expectation with the decoded JSON value.
|
|
||||||
*
|
|
||||||
* @return self<TOriginalValue, array<string|int, mixed>|bool>
|
* @return self<TOriginalValue, array<string|int, mixed>|bool>
|
||||||
*/
|
*/
|
||||||
public function json(): self
|
public function json(): self
|
||||||
@@ -109,8 +90,6 @@ final class HigherOrderExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically calls methods on the class with the given arguments.
|
|
||||||
*
|
|
||||||
* @param array<int, mixed> $arguments
|
* @param array<int, mixed> $arguments
|
||||||
* @return self<TOriginalValue, mixed>
|
* @return self<TOriginalValue, mixed>
|
||||||
*/
|
*/
|
||||||
@@ -125,8 +104,6 @@ final class HigherOrderExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accesses properties in the value or in the expectation.
|
|
||||||
*
|
|
||||||
* @return self<TOriginalValue, mixed>
|
* @return self<TOriginalValue, mixed>
|
||||||
*/
|
*/
|
||||||
public function __get(string $name): self
|
public function __get(string $name): self
|
||||||
@@ -145,9 +122,6 @@ final class HigherOrderExpectation
|
|||||||
return $this->performAssertion($name, []);
|
return $this->performAssertion($name, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the original expectation has the given method name.
|
|
||||||
*/
|
|
||||||
private function expectationHasMethod(string $name): bool
|
private function expectationHasMethod(string $name): bool
|
||||||
{
|
{
|
||||||
if (method_exists($this->original, $name)) {
|
if (method_exists($this->original, $name)) {
|
||||||
@@ -161,8 +135,6 @@ final class HigherOrderExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the applicable value based on the current reset condition.
|
|
||||||
*
|
|
||||||
* @return TOriginalValue|TValue
|
* @return TOriginalValue|TValue
|
||||||
*/
|
*/
|
||||||
private function getValue(): mixed
|
private function getValue(): mixed
|
||||||
@@ -171,8 +143,6 @@ final class HigherOrderExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs the given assertion with the current expectation.
|
|
||||||
*
|
|
||||||
* @param array<int, mixed> $arguments
|
* @param array<int, mixed> $arguments
|
||||||
* @return self<TOriginalValue, TValue>
|
* @return self<TOriginalValue, TValue>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest\Expectations;
|
namespace Pest\Expectations;
|
||||||
|
|
||||||
use Attribute;
|
|
||||||
use Pest\Arch\Contracts\ArchExpectation;
|
use Pest\Arch\Contracts\ArchExpectation;
|
||||||
use Pest\Arch\Expectations\Targeted;
|
use Pest\Arch\Expectations\Targeted;
|
||||||
use Pest\Arch\Expectations\ToBeUsedIn;
|
use Pest\Arch\Expectations\ToBeUsedIn;
|
||||||
@@ -38,15 +37,11 @@ use stdClass;
|
|||||||
final readonly class OppositeExpectation
|
final readonly class OppositeExpectation
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Creates a new opposite expectation.
|
|
||||||
*
|
|
||||||
* @param Expectation<TValue> $original
|
* @param Expectation<TValue> $original
|
||||||
*/
|
*/
|
||||||
public function __construct(private Expectation $original) {}
|
public function __construct(private Expectation $original) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the value array not has the provided $keys.
|
|
||||||
*
|
|
||||||
* @param array<int, int|string|array<int-string, mixed>> $keys
|
* @param array<int, int|string|array<int-string, mixed>> $keys
|
||||||
* @return Expectation<TValue>
|
* @return Expectation<TValue>
|
||||||
*/
|
*/
|
||||||
@@ -70,8 +65,6 @@ final readonly class OppositeExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target does not use any of the given dependencies.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $targets
|
* @param array<int, string>|string $targets
|
||||||
*/
|
*/
|
||||||
public function toUse(array|string $targets): ArchExpectation
|
public function toUse(array|string $targets): ArchExpectation
|
||||||
@@ -84,9 +77,6 @@ final readonly class OppositeExpectation
|
|||||||
), is_string($targets) ? [$targets] : $targets));
|
), is_string($targets) ? [$targets] : $targets));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target does not have the given permissions
|
|
||||||
*/
|
|
||||||
public function toHaveFileSystemPermissions(string $permissions): ArchExpectation
|
public function toHaveFileSystemPermissions(string $permissions): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -100,17 +90,11 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*/
|
|
||||||
public function toHaveLineCountLessThan(): ArchExpectation
|
public function toHaveLineCountLessThan(): ArchExpectation
|
||||||
{
|
{
|
||||||
throw InvalidExpectation::fromMethods(['not', 'toHaveLineCountLessThan']);
|
throw InvalidExpectation::fromMethods(['not', 'toHaveLineCountLessThan']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*/
|
|
||||||
public function toHaveMethodsDocumented(): ArchExpectation
|
public function toHaveMethodsDocumented(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -130,9 +114,6 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*/
|
|
||||||
public function toHavePropertiesDocumented(): ArchExpectation
|
public function toHavePropertiesDocumented(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -153,9 +134,6 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target does not use the "declare(strict_types=1)" declaration.
|
|
||||||
*/
|
|
||||||
public function toUseStrictTypes(): ArchExpectation
|
public function toUseStrictTypes(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -169,9 +147,6 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target does not use the strict equality operator.
|
|
||||||
*/
|
|
||||||
public function toUseStrictEquality(): ArchExpectation
|
public function toUseStrictEquality(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -185,9 +160,6 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is not final.
|
|
||||||
*/
|
|
||||||
public function toBeFinal(): ArchExpectation
|
public function toBeFinal(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -201,9 +173,6 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is not readonly.
|
|
||||||
*/
|
|
||||||
public function toBeReadonly(): ArchExpectation
|
public function toBeReadonly(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -217,9 +186,6 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is not trait.
|
|
||||||
*/
|
|
||||||
public function toBeTrait(): ArchExpectation
|
public function toBeTrait(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -233,17 +199,11 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation targets are not traits.
|
|
||||||
*/
|
|
||||||
public function toBeTraits(): ArchExpectation
|
public function toBeTraits(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeTrait();
|
return $this->toBeTrait();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is not abstract.
|
|
||||||
*/
|
|
||||||
public function toBeAbstract(): ArchExpectation
|
public function toBeAbstract(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -258,8 +218,6 @@ final readonly class OppositeExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target does not have a specific method.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $method
|
* @param array<int, string>|string $method
|
||||||
*/
|
*/
|
||||||
public function toHaveMethod(array|string $method): ArchExpectation
|
public function toHaveMethod(array|string $method): ArchExpectation
|
||||||
@@ -280,9 +238,6 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target does not have suspicious characters.
|
|
||||||
*/
|
|
||||||
public function toHaveSuspiciousCharacters(): ArchExpectation
|
public function toHaveSuspiciousCharacters(): ArchExpectation
|
||||||
{
|
{
|
||||||
if (! class_exists(Spoofchecker::class)) {
|
if (! class_exists(Spoofchecker::class)) {
|
||||||
@@ -303,8 +258,6 @@ final readonly class OppositeExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target does not have the given methods.
|
|
||||||
*
|
|
||||||
* @param array<int, string> $methods
|
* @param array<int, string> $methods
|
||||||
*/
|
*/
|
||||||
public function toHaveMethods(array $methods): ArchExpectation
|
public function toHaveMethods(array $methods): ArchExpectation
|
||||||
@@ -313,8 +266,6 @@ final readonly class OppositeExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target not to have the public methods besides the given methods.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $methods
|
* @param array<int, string>|string $methods
|
||||||
*/
|
*/
|
||||||
public function toHavePublicMethodsBesides(array|string $methods): ArchExpectation
|
public function toHavePublicMethodsBesides(array|string $methods): ArchExpectation
|
||||||
@@ -345,22 +296,17 @@ final readonly class OppositeExpectation
|
|||||||
},
|
},
|
||||||
$methods === []
|
$methods === []
|
||||||
? 'not to have public methods'
|
? 'not to have public methods'
|
||||||
: sprintf("not to have public methods besides '%s'", implode("', '", $methods)),
|
: sprintf('not to have public methods besides [%s]', implode('], [', $methods)),
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)),
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target not to have the public methods.
|
|
||||||
*/
|
|
||||||
public function toHavePublicMethods(): ArchExpectation
|
public function toHavePublicMethods(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toHavePublicMethodsBesides([]);
|
return $this->toHavePublicMethodsBesides([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target not to have the protected methods besides the given methods.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $methods
|
* @param array<int, string>|string $methods
|
||||||
*/
|
*/
|
||||||
public function toHaveProtectedMethodsBesides(array|string $methods): ArchExpectation
|
public function toHaveProtectedMethodsBesides(array|string $methods): ArchExpectation
|
||||||
@@ -391,22 +337,17 @@ final readonly class OppositeExpectation
|
|||||||
},
|
},
|
||||||
$methods === []
|
$methods === []
|
||||||
? 'not to have protected methods'
|
? 'not to have protected methods'
|
||||||
: sprintf("not to have protected methods besides '%s'", implode("', '", $methods)),
|
: sprintf('not to have protected methods besides [%s]', implode('], [', $methods)),
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)),
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target not to have the protected methods.
|
|
||||||
*/
|
|
||||||
public function toHaveProtectedMethods(): ArchExpectation
|
public function toHaveProtectedMethods(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toHaveProtectedMethodsBesides([]);
|
return $this->toHaveProtectedMethodsBesides([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target not to have the private methods besides the given methods.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $methods
|
* @param array<int, string>|string $methods
|
||||||
*/
|
*/
|
||||||
public function toHavePrivateMethodsBesides(array|string $methods): ArchExpectation
|
public function toHavePrivateMethodsBesides(array|string $methods): ArchExpectation
|
||||||
@@ -437,22 +378,16 @@ final readonly class OppositeExpectation
|
|||||||
},
|
},
|
||||||
$methods === []
|
$methods === []
|
||||||
? 'not to have private methods'
|
? 'not to have private methods'
|
||||||
: sprintf("not to have private methods besides '%s'", implode("', '", $methods)),
|
: sprintf('not to have private methods besides [%s]', implode('], [', $methods)),
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)),
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target not to have the private methods.
|
|
||||||
*/
|
|
||||||
public function toHavePrivateMethods(): ArchExpectation
|
public function toHavePrivateMethods(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toHavePrivateMethodsBesides([]);
|
return $this->toHavePrivateMethodsBesides([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is not enum.
|
|
||||||
*/
|
|
||||||
public function toBeEnum(): ArchExpectation
|
public function toBeEnum(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -466,17 +401,11 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation targets are not enums.
|
|
||||||
*/
|
|
||||||
public function toBeEnums(): ArchExpectation
|
public function toBeEnums(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeEnum();
|
return $this->toBeEnum();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation targets is not class.
|
|
||||||
*/
|
|
||||||
public function toBeClass(): ArchExpectation
|
public function toBeClass(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -490,17 +419,11 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation targets are not classes.
|
|
||||||
*/
|
|
||||||
public function toBeClasses(): ArchExpectation
|
public function toBeClasses(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeClass();
|
return $this->toBeClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is not interface.
|
|
||||||
*/
|
|
||||||
public function toBeInterface(): ArchExpectation
|
public function toBeInterface(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -514,17 +437,11 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation targets are not interfaces.
|
|
||||||
*/
|
|
||||||
public function toBeInterfaces(): ArchExpectation
|
public function toBeInterfaces(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeInterface();
|
return $this->toBeInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target to be not subclass of the given class.
|
|
||||||
*/
|
|
||||||
public function toExtend(string $class): ArchExpectation
|
public function toExtend(string $class): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -533,14 +450,11 @@ final readonly class OppositeExpectation
|
|||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
$original,
|
$original,
|
||||||
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isSubclassOf($class),
|
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isSubclassOf($class),
|
||||||
sprintf("not to extend '%s'", $class),
|
sprintf('not to extend [%s]', $class),
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target to be not have any parent class.
|
|
||||||
*/
|
|
||||||
public function toExtendNothing(): ArchExpectation
|
public function toExtendNothing(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -554,17 +468,12 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target not to use the given trait.
|
|
||||||
*/
|
|
||||||
public function toUseTrait(string $trait): ArchExpectation
|
public function toUseTrait(string $trait): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toUseTraits($trait);
|
return $this->toUseTraits($trait);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target not to use the given traits.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $traits
|
* @param array<int, string>|string $traits
|
||||||
*/
|
*/
|
||||||
public function toUseTraits(array|string $traits): ArchExpectation
|
public function toUseTraits(array|string $traits): ArchExpectation
|
||||||
@@ -583,8 +492,6 @@ final readonly class OppositeExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation target not to implement the given interfaces.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $interfaces
|
* @param array<int, string>|string $interfaces
|
||||||
*/
|
*/
|
||||||
public function toImplement(array|string $interfaces): ArchExpectation
|
public function toImplement(array|string $interfaces): ArchExpectation
|
||||||
@@ -602,9 +509,6 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target to not implement any interfaces.
|
|
||||||
*/
|
|
||||||
public function toImplementNothing(): ArchExpectation
|
public function toImplementNothing(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -618,17 +522,11 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*/
|
|
||||||
public function toOnlyImplement(): void
|
public function toOnlyImplement(): void
|
||||||
{
|
{
|
||||||
throw InvalidExpectation::fromMethods(['not', 'toOnlyImplement']);
|
throw InvalidExpectation::fromMethods(['not', 'toOnlyImplement']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target to not have the given prefix.
|
|
||||||
*/
|
|
||||||
public function toHavePrefix(string $prefix): ArchExpectation
|
public function toHavePrefix(string $prefix): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -637,14 +535,11 @@ final readonly class OppositeExpectation
|
|||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
$original,
|
$original,
|
||||||
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! str_starts_with($object->reflectionClass->getShortName(), $prefix),
|
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! str_starts_with($object->reflectionClass->getShortName(), $prefix),
|
||||||
"not to have prefix '{$prefix}'",
|
"not to have prefix [{$prefix}]",
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target to not have the given suffix.
|
|
||||||
*/
|
|
||||||
public function toHaveSuffix(string $suffix): ArchExpectation
|
public function toHaveSuffix(string $suffix): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -653,30 +548,21 @@ final readonly class OppositeExpectation
|
|||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
$original,
|
$original,
|
||||||
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! str_ends_with($object->reflectionClass->getName(), $suffix),
|
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! str_ends_with($object->reflectionClass->getName(), $suffix),
|
||||||
"not to have suffix '{$suffix}'",
|
"not to have suffix [{$suffix}]",
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*/
|
|
||||||
public function toOnlyUse(): void
|
public function toOnlyUse(): void
|
||||||
{
|
{
|
||||||
throw InvalidExpectation::fromMethods(['not', 'toOnlyUse']);
|
throw InvalidExpectation::fromMethods(['not', 'toOnlyUse']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Not supported.
|
|
||||||
*/
|
|
||||||
public function toUseNothing(): void
|
public function toUseNothing(): void
|
||||||
{
|
{
|
||||||
throw InvalidExpectation::fromMethods(['not', 'toUseNothing']);
|
throw InvalidExpectation::fromMethods(['not', 'toUseNothing']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation dependency is not used.
|
|
||||||
*/
|
|
||||||
public function toBeUsed(): ArchExpectation
|
public function toBeUsed(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -686,8 +572,6 @@ final readonly class OppositeExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given expectation dependency is not used by any of the given targets.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $targets
|
* @param array<int, string>|string $targets
|
||||||
*/
|
*/
|
||||||
public function toBeUsedIn(array|string $targets): ArchExpectation
|
public function toBeUsedIn(array|string $targets): ArchExpectation
|
||||||
@@ -705,17 +589,11 @@ final readonly class OppositeExpectation
|
|||||||
throw InvalidExpectation::fromMethods(['not', 'toOnlyBeUsedIn']);
|
throw InvalidExpectation::fromMethods(['not', 'toOnlyBeUsedIn']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation dependency is not used.
|
|
||||||
*/
|
|
||||||
public function toBeUsedInNothing(): void
|
public function toBeUsedInNothing(): void
|
||||||
{
|
{
|
||||||
throw InvalidExpectation::fromMethods(['not', 'toBeUsedInNothing']);
|
throw InvalidExpectation::fromMethods(['not', 'toBeUsedInNothing']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation dependency is not an invokable class.
|
|
||||||
*/
|
|
||||||
public function toBeInvokable(): ArchExpectation
|
public function toBeInvokable(): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -729,9 +607,6 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target not to have the given attribute.
|
|
||||||
*/
|
|
||||||
public function toHaveAttribute(string $attribute): ArchExpectation
|
public function toHaveAttribute(string $attribute): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -740,14 +615,12 @@ final readonly class OppositeExpectation
|
|||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
$original,
|
$original,
|
||||||
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || $object->reflectionClass->getAttributes($attribute) === [],
|
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || $object->reflectionClass->getAttributes($attribute) === [],
|
||||||
"to not have attribute '{$attribute}'",
|
"to not have attribute [{$attribute}]",
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle dynamic method calls into the original expectation.
|
|
||||||
*
|
|
||||||
* @param array<int, mixed> $arguments
|
* @param array<int, mixed> $arguments
|
||||||
* @return Expectation<TValue>|Expectation<mixed>|never
|
* @return Expectation<TValue>|Expectation<mixed>|never
|
||||||
*/
|
*/
|
||||||
@@ -768,8 +641,6 @@ final readonly class OppositeExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle dynamic properties gets into the original expectation.
|
|
||||||
*
|
|
||||||
* @return Expectation<TValue>|Expectation<mixed>|never
|
* @return Expectation<TValue>|Expectation<mixed>|never
|
||||||
*/
|
*/
|
||||||
public function __get(string $name): Expectation
|
public function __get(string $name): Expectation
|
||||||
@@ -788,8 +659,6 @@ final readonly class OppositeExpectation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new expectation failed exception with a nice readable message.
|
|
||||||
*
|
|
||||||
* @param array<int, mixed>|string $arguments
|
* @param array<int, mixed>|string $arguments
|
||||||
*/
|
*/
|
||||||
public function throwExpectationFailedException(string $name, array|string $arguments = []): never
|
public function throwExpectationFailedException(string $name, array|string $arguments = []): never
|
||||||
@@ -806,25 +675,16 @@ final readonly class OppositeExpectation
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target does not have a constructor method.
|
|
||||||
*/
|
|
||||||
public function toHaveConstructor(): ArchExpectation
|
public function toHaveConstructor(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toHaveMethod('__construct');
|
return $this->toHaveMethod('__construct');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target does not have a destructor method.
|
|
||||||
*/
|
|
||||||
public function toHaveDestructor(): ArchExpectation
|
public function toHaveDestructor(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toHaveMethod('__destruct');
|
return $this->toHaveMethod('__destruct');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is not a backed enum of given type.
|
|
||||||
*/
|
|
||||||
private function toBeBackedEnum(string $backingType): ArchExpectation
|
private function toBeBackedEnum(string $backingType): ArchExpectation
|
||||||
{
|
{
|
||||||
/** @var Expectation<array<int, string>|string> $original */
|
/** @var Expectation<array<int, string>|string> $original */
|
||||||
@@ -841,33 +701,21 @@ final readonly class OppositeExpectation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation targets are not string backed enums.
|
|
||||||
*/
|
|
||||||
public function toBeStringBackedEnums(): ArchExpectation
|
public function toBeStringBackedEnums(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeStringBackedEnum();
|
return $this->toBeStringBackedEnum();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation targets are not int backed enums.
|
|
||||||
*/
|
|
||||||
public function toBeIntBackedEnums(): ArchExpectation
|
public function toBeIntBackedEnums(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeIntBackedEnum();
|
return $this->toBeIntBackedEnum();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is not a string backed enum.
|
|
||||||
*/
|
|
||||||
public function toBeStringBackedEnum(): ArchExpectation
|
public function toBeStringBackedEnum(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeBackedEnum('string');
|
return $this->toBeBackedEnum('string');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the given expectation target is not an int backed enum.
|
|
||||||
*/
|
|
||||||
public function toBeIntBackedEnum(): ArchExpectation
|
public function toBeIntBackedEnum(): ArchExpectation
|
||||||
{
|
{
|
||||||
return $this->toBeBackedEnum('int');
|
return $this->toBeBackedEnum('int');
|
||||||
|
|||||||
@@ -8,24 +8,12 @@ use Pest\Support\HigherOrderMessageCollection;
|
|||||||
|
|
||||||
trait HigherOrderable
|
trait HigherOrderable
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The higher order messages that are chainable.
|
|
||||||
*/
|
|
||||||
public HigherOrderMessageCollection $chains;
|
public HigherOrderMessageCollection $chains;
|
||||||
|
|
||||||
/**
|
|
||||||
* The higher order messages that are "factory" proxyable.
|
|
||||||
*/
|
|
||||||
public HigherOrderMessageCollection $factoryProxies;
|
public HigherOrderMessageCollection $factoryProxies;
|
||||||
|
|
||||||
/**
|
|
||||||
* The higher order messages that are proxyable.
|
|
||||||
*/
|
|
||||||
public HigherOrderMessageCollection $proxies;
|
public HigherOrderMessageCollection $proxies;
|
||||||
|
|
||||||
/**
|
|
||||||
* Boot the higher order properties.
|
|
||||||
*/
|
|
||||||
private function bootHigherOrderable(): void
|
private function bootHigherOrderable(): void
|
||||||
{
|
{
|
||||||
$this->chains = new HigherOrderMessageCollection;
|
$this->chains = new HigherOrderMessageCollection;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use Pest\Concerns;
|
|||||||
use Pest\Contracts\HasPrintableTestCaseName;
|
use Pest\Contracts\HasPrintableTestCaseName;
|
||||||
use Pest\Evaluators\Attributes;
|
use Pest\Evaluators\Attributes;
|
||||||
use Pest\Exceptions\DatasetMissing;
|
use Pest\Exceptions\DatasetMissing;
|
||||||
|
use Pest\Exceptions\InvalidTestClassName;
|
||||||
use Pest\Exceptions\ShouldNotHappen;
|
use Pest\Exceptions\ShouldNotHappen;
|
||||||
use Pest\Exceptions\TestAlreadyExist;
|
use Pest\Exceptions\TestAlreadyExist;
|
||||||
use Pest\Exceptions\TestClosureMustNotBeStatic;
|
use Pest\Exceptions\TestClosureMustNotBeStatic;
|
||||||
@@ -29,29 +30,21 @@ final class TestCaseFactory
|
|||||||
use HigherOrderable;
|
use HigherOrderable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of attributes.
|
|
||||||
*
|
|
||||||
* @var array<int, Attribute>
|
* @var array<int, Attribute>
|
||||||
*/
|
*/
|
||||||
public array $attributes = [];
|
public array $attributes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The FQN of the Test Case class.
|
|
||||||
*
|
|
||||||
* @var class-string
|
* @var class-string
|
||||||
*/
|
*/
|
||||||
public string $class = TestCase::class;
|
public string $class = TestCase::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of class methods.
|
|
||||||
*
|
|
||||||
* @var array<string, TestCaseMethodFactory>
|
* @var array<string, TestCaseMethodFactory>
|
||||||
*/
|
*/
|
||||||
public array $methods = [];
|
public array $methods = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of class traits.
|
|
||||||
*
|
|
||||||
* @var array <int, class-string>
|
* @var array <int, class-string>
|
||||||
*/
|
*/
|
||||||
public array $traits = [
|
public array $traits = [
|
||||||
@@ -59,14 +52,8 @@ final class TestCaseFactory
|
|||||||
Concerns\Expectable::class,
|
Concerns\Expectable::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* The namespace for the test case, overrides the path-based namespace when set.
|
|
||||||
*/
|
|
||||||
public ?string $namespace = null;
|
public ?string $namespace = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Factory instance.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $filename
|
public string $filename
|
||||||
) {
|
) {
|
||||||
@@ -83,14 +70,11 @@ final class TestCaseFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a Test Case class using a runtime evaluate.
|
|
||||||
*
|
|
||||||
* @param array<string, TestCaseMethodFactory> $methods
|
* @param array<string, TestCaseMethodFactory> $methods
|
||||||
*/
|
*/
|
||||||
public function evaluate(string $filename, array $methods): void
|
public function evaluate(string $filename, array $methods): void
|
||||||
{
|
{
|
||||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||||
// In case Windows, strtolower drive name, like in UsesCall.
|
|
||||||
$filename = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', static fn (array $match): string => strtolower($match['drive']), $filename);
|
$filename = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', static fn (array $match): string => strtolower($match['drive']), $filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,11 +97,8 @@ final class TestCaseFactory
|
|||||||
|
|
||||||
$relativePath = str_replace(DIRECTORY_SEPARATOR, '\\', $relativePath);
|
$relativePath = str_replace(DIRECTORY_SEPARATOR, '\\', $relativePath);
|
||||||
|
|
||||||
// Strip out any %-encoded octets.
|
|
||||||
$relativePath = (string) preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $relativePath);
|
$relativePath = (string) preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $relativePath);
|
||||||
// Remove escaped quote sequences (maintain namespace)
|
|
||||||
$relativePath = str_replace(array_map(fn (string $quote): string => sprintf('\\%s', $quote), ['\'', '"']), '', $relativePath);
|
$relativePath = str_replace(array_map(fn (string $quote): string => sprintf('\\%s', $quote), ['\'', '"']), '', $relativePath);
|
||||||
// Limit to Unicode letters and numbers.
|
|
||||||
$relativePath = (string) preg_replace('/[^\p{L}\p{N}\\\\]/u', '', $relativePath);
|
$relativePath = (string) preg_replace('/[^\p{L}\p{N}\\\\]/u', '', $relativePath);
|
||||||
|
|
||||||
$classFQN = 'P\\'.$relativePath;
|
$classFQN = 'P\\'.$relativePath;
|
||||||
@@ -138,6 +119,16 @@ final class TestCaseFactory
|
|||||||
|
|
||||||
if (trim($className) === '') {
|
if (trim($className) === '') {
|
||||||
$className = 'InvalidTestName'.Str::random();
|
$className = 'InvalidTestName'.Str::random();
|
||||||
|
} elseif (! Str::isValidClassName($className)) {
|
||||||
|
throw InvalidTestClassName::fromClassName($this->filename, $className);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->namespace === null) {
|
||||||
|
foreach ($partsFQN as $partFQN) {
|
||||||
|
if (! Str::isValidIdentifier($partFQN)) {
|
||||||
|
throw InvalidTestClassName::fromNamespace($this->filename, $namespace, $partFQN);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->attributes = [
|
$this->attributes = [
|
||||||
@@ -179,16 +170,13 @@ final class TestCaseFactory
|
|||||||
eval($classCode);
|
eval($classCode);
|
||||||
} catch (ParseError $caught) {
|
} catch (ParseError $caught) {
|
||||||
throw new RuntimeException(sprintf(
|
throw new RuntimeException(sprintf(
|
||||||
"Unable to create test case for test file at %s. \n %s",
|
"Unable to create test case for test file at [%s]. \n %s",
|
||||||
$filename,
|
$filename,
|
||||||
$classCode
|
$classCode
|
||||||
), 1, $caught);
|
), 1, $caught);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the given Method to the Test Case.
|
|
||||||
*/
|
|
||||||
public function addMethod(TestCaseMethodFactory $method): void
|
public function addMethod(TestCaseMethodFactory $method): void
|
||||||
{
|
{
|
||||||
if ($method->description === null) {
|
if ($method->description === null) {
|
||||||
@@ -222,9 +210,6 @@ final class TestCaseFactory
|
|||||||
$this->methods[$method->description] = $method;
|
$this->methods[$method->description] = $method;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a test case has a method.
|
|
||||||
*/
|
|
||||||
public function hasMethod(string $methodName): bool
|
public function hasMethod(string $methodName): bool
|
||||||
{
|
{
|
||||||
foreach ($this->methods as $method) {
|
foreach ($this->methods as $method) {
|
||||||
@@ -240,9 +225,6 @@ final class TestCaseFactory
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a Method by the given name.
|
|
||||||
*/
|
|
||||||
public function getMethod(string $methodName): TestCaseMethodFactory
|
public function getMethod(string $methodName): TestCaseMethodFactory
|
||||||
{
|
{
|
||||||
foreach ($this->methods as $method) {
|
foreach ($this->methods as $method) {
|
||||||
@@ -255,6 +237,6 @@ final class TestCaseFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw ShouldNotHappen::fromMessage(sprintf('Method %s not found.', $methodName));
|
throw ShouldNotHappen::fromMessage(sprintf('Method [%s] not found.', $methodName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,84 +27,54 @@ final class TestCaseMethodFactory
|
|||||||
use HigherOrderable;
|
use HigherOrderable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of attributes.
|
|
||||||
*
|
|
||||||
* @var array<int, Attribute>
|
* @var array<int, Attribute>
|
||||||
*/
|
*/
|
||||||
public array $attributes = [];
|
public array $attributes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The test's describing, if any.
|
|
||||||
*
|
|
||||||
* @var array<int, Description>
|
* @var array<int, Description>
|
||||||
*/
|
*/
|
||||||
public array $describing = [];
|
public array $describing = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's description, if any.
|
|
||||||
*/
|
|
||||||
public ?string $description = null;
|
public ?string $description = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's number of repetitions.
|
|
||||||
*/
|
|
||||||
public int $repetitions = 1;
|
public int $repetitions = 1;
|
||||||
|
|
||||||
/**
|
|
||||||
* The test's number of flaky retry tries.
|
|
||||||
*/
|
|
||||||
public ?int $flakyTries = null;
|
public ?int $flakyTries = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the test is a "todo".
|
|
||||||
*/
|
|
||||||
public bool $todo = false;
|
public bool $todo = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The associated issue numbers.
|
|
||||||
*
|
|
||||||
* @var array<int, int>
|
* @var array<int, int>
|
||||||
*/
|
*/
|
||||||
public array $issues = [];
|
public array $issues = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The test assignees.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
public array $assignees = [];
|
public array $assignees = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The associated PRs numbers.
|
|
||||||
*
|
|
||||||
* @var array<int, int>
|
* @var array<int, int>
|
||||||
*/
|
*/
|
||||||
public array $prs = [];
|
public array $prs = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The test's notes.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
public array $notes = [];
|
public array $notes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The test's datasets.
|
|
||||||
*
|
|
||||||
* @var array<Closure|iterable<int|string, mixed>|string>
|
* @var array<Closure|iterable<int|string, mixed>|string>
|
||||||
*/
|
*/
|
||||||
public array $datasets = [];
|
public array $datasets = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The test's dependencies.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
public array $depends = [];
|
public array $depends = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The test's groups.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
public array $groups = [];
|
public array $groups = [];
|
||||||
@@ -114,9 +84,6 @@ final class TestCaseMethodFactory
|
|||||||
*/
|
*/
|
||||||
public bool $__ran = false;
|
public bool $__ran = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new test case method factory instance.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $filename,
|
public string $filename,
|
||||||
public ?Closure $closure,
|
public ?Closure $closure,
|
||||||
@@ -128,9 +95,6 @@ final class TestCaseMethodFactory
|
|||||||
$this->bootHigherOrderable();
|
$this->bootHigherOrderable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the test's hooks, and runs any proxy to the test case.
|
|
||||||
*/
|
|
||||||
public function setUp(TestCase $concrete): void
|
public function setUp(TestCase $concrete): void
|
||||||
{
|
{
|
||||||
$concrete::flush(); // @phpstan-ignore-line
|
$concrete::flush(); // @phpstan-ignore-line
|
||||||
@@ -146,17 +110,11 @@ final class TestCaseMethodFactory
|
|||||||
$this->factoryProxies->proxy($concrete);
|
$this->factoryProxies->proxy($concrete);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Flushes the test case.
|
|
||||||
*/
|
|
||||||
public function tearDown(TestCase $concrete): void
|
public function tearDown(TestCase $concrete): void
|
||||||
{
|
{
|
||||||
$concrete::flush(); // @phpstan-ignore-line
|
$concrete::flush(); // @phpstan-ignore-line
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the test's closure.
|
|
||||||
*/
|
|
||||||
public function getClosure(): Closure
|
public function getClosure(): Closure
|
||||||
{
|
{
|
||||||
$closure = $this->closure;
|
$closure = $this->closure;
|
||||||
@@ -178,17 +136,11 @@ final class TestCaseMethodFactory
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if the test case will receive argument input from Pest, or not.
|
|
||||||
*/
|
|
||||||
public function receivesArguments(): bool
|
public function receivesArguments(): bool
|
||||||
{
|
{
|
||||||
return $this->datasets !== [] || $this->depends !== [] || $this->repetitions > 1;
|
return $this->datasets !== [] || $this->depends !== [] || $this->repetitions > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a PHPUnit method as a string ready for evaluation.
|
|
||||||
*/
|
|
||||||
public function buildForEvaluation(): string
|
public function buildForEvaluation(): string
|
||||||
{
|
{
|
||||||
if ($this->description === null) {
|
if ($this->description === null) {
|
||||||
@@ -248,9 +200,6 @@ final class TestCaseMethodFactory
|
|||||||
PHP;
|
PHP;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a PHPUnit Data Provider as a string ready for evaluation.
|
|
||||||
*/
|
|
||||||
private function buildDatasetForEvaluation(string $methodName, string $dataProviderName): string
|
private function buildDatasetForEvaluation(string $methodName, string $dataProviderName): string
|
||||||
{
|
{
|
||||||
$datasets = $this->datasets;
|
$datasets = $this->datasets;
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ use PHPUnit\Framework\TestCase;
|
|||||||
|
|
||||||
if (! function_exists('expect')) {
|
if (! function_exists('expect')) {
|
||||||
/**
|
/**
|
||||||
* Creates a new expectation.
|
|
||||||
*
|
|
||||||
* @template TValue
|
* @template TValue
|
||||||
*
|
*
|
||||||
* @param TValue|null $value
|
* @param TValue|null $value
|
||||||
@@ -41,9 +39,6 @@ if (! function_exists('expect')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('beforeAll')) {
|
if (! function_exists('beforeAll')) {
|
||||||
/**
|
|
||||||
* Runs the given closure before all tests in the current file.
|
|
||||||
*/
|
|
||||||
function beforeAll(Closure $closure): void
|
function beforeAll(Closure $closure): void
|
||||||
{
|
{
|
||||||
if (DescribeCall::describing() !== []) {
|
if (DescribeCall::describing() !== []) {
|
||||||
@@ -58,8 +53,6 @@ if (! function_exists('beforeAll')) {
|
|||||||
|
|
||||||
if (! function_exists('beforeEach')) {
|
if (! function_exists('beforeEach')) {
|
||||||
/**
|
/**
|
||||||
* Runs the given closure before each test in the current file.
|
|
||||||
*
|
|
||||||
* @param-closure-this TestCall $closure
|
* @param-closure-this TestCall $closure
|
||||||
*/
|
*/
|
||||||
function beforeEach(?Closure $closure = null): BeforeEachCall
|
function beforeEach(?Closure $closure = null): BeforeEachCall
|
||||||
@@ -72,8 +65,6 @@ if (! function_exists('beforeEach')) {
|
|||||||
|
|
||||||
if (! function_exists('dataset')) {
|
if (! function_exists('dataset')) {
|
||||||
/**
|
/**
|
||||||
* Registers the given dataset.
|
|
||||||
*
|
|
||||||
* @param Closure|iterable<int|string, mixed> $dataset
|
* @param Closure|iterable<int|string, mixed> $dataset
|
||||||
*/
|
*/
|
||||||
function dataset(string $name, Closure|iterable $dataset): void
|
function dataset(string $name, Closure|iterable $dataset): void
|
||||||
@@ -85,11 +76,6 @@ if (! function_exists('dataset')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('describe')) {
|
if (! function_exists('describe')) {
|
||||||
/**
|
|
||||||
* Adds the given closure as a group of tests. The first argument
|
|
||||||
* is the group description; the second argument is a closure
|
|
||||||
* that contains the group tests.
|
|
||||||
*/
|
|
||||||
function describe(string $description, Closure $tests): DescribeCall
|
function describe(string $description, Closure $tests): DescribeCall
|
||||||
{
|
{
|
||||||
$filename = Backtrace::testFile();
|
$filename = Backtrace::testFile();
|
||||||
@@ -100,9 +86,6 @@ if (! function_exists('describe')) {
|
|||||||
|
|
||||||
if (! function_exists('uses')) {
|
if (! function_exists('uses')) {
|
||||||
/**
|
/**
|
||||||
* The uses function binds the given
|
|
||||||
* arguments to test closures.
|
|
||||||
*
|
|
||||||
* @param class-string ...$classAndTraits
|
* @param class-string ...$classAndTraits
|
||||||
*/
|
*/
|
||||||
function uses(string ...$classAndTraits): UsesCall
|
function uses(string ...$classAndTraits): UsesCall
|
||||||
@@ -114,9 +97,6 @@ if (! function_exists('uses')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('pest')) {
|
if (! function_exists('pest')) {
|
||||||
/**
|
|
||||||
* Creates a new Pest configuration instance.
|
|
||||||
*/
|
|
||||||
function pest(): Configuration
|
function pest(): Configuration
|
||||||
{
|
{
|
||||||
return new Configuration(Backtrace::testFile());
|
return new Configuration(Backtrace::testFile());
|
||||||
@@ -125,10 +105,6 @@ if (! function_exists('pest')) {
|
|||||||
|
|
||||||
if (! function_exists('test')) {
|
if (! function_exists('test')) {
|
||||||
/**
|
/**
|
||||||
* Adds the given closure as a test. The first argument
|
|
||||||
* is the test description; the second argument is
|
|
||||||
* a closure that contains the test expectations.
|
|
||||||
*
|
|
||||||
* @param-closure-this TestCall $closure
|
* @param-closure-this TestCall $closure
|
||||||
*
|
*
|
||||||
* @return ($description is string ? TestCall : HigherOrderTapProxy|TestCall)
|
* @return ($description is string ? TestCall : HigherOrderTapProxy|TestCall)
|
||||||
@@ -147,10 +123,6 @@ if (! function_exists('test')) {
|
|||||||
|
|
||||||
if (! function_exists('it')) {
|
if (! function_exists('it')) {
|
||||||
/**
|
/**
|
||||||
* Adds the given closure as a test. The first argument
|
|
||||||
* is the test description; the second argument is
|
|
||||||
* a closure that contains the test expectations.
|
|
||||||
*
|
|
||||||
* @param-closure-this TestCall $closure
|
* @param-closure-this TestCall $closure
|
||||||
*/
|
*/
|
||||||
function it(string $description, ?Closure $closure = null): TestCall
|
function it(string $description, ?Closure $closure = null): TestCall
|
||||||
@@ -162,9 +134,6 @@ if (! function_exists('it')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('todo')) {
|
if (! function_exists('todo')) {
|
||||||
/**
|
|
||||||
* Creates a new test that is marked as "todo".
|
|
||||||
*/
|
|
||||||
function todo(string $description): TestCall
|
function todo(string $description): TestCall
|
||||||
{
|
{
|
||||||
return test($description)->todo();
|
return test($description)->todo();
|
||||||
@@ -173,8 +142,6 @@ if (! function_exists('todo')) {
|
|||||||
|
|
||||||
if (! function_exists('afterEach')) {
|
if (! function_exists('afterEach')) {
|
||||||
/**
|
/**
|
||||||
* Runs the given closure after each test in the current file.
|
|
||||||
*
|
|
||||||
* @param-closure-this TestCall $closure
|
* @param-closure-this TestCall $closure
|
||||||
*/
|
*/
|
||||||
function afterEach(?Closure $closure = null): AfterEachCall
|
function afterEach(?Closure $closure = null): AfterEachCall
|
||||||
@@ -186,9 +153,6 @@ if (! function_exists('afterEach')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('afterAll')) {
|
if (! function_exists('afterAll')) {
|
||||||
/**
|
|
||||||
* Runs the given closure after all tests in the current file.
|
|
||||||
*/
|
|
||||||
function afterAll(Closure $closure): void
|
function afterAll(Closure $closure): void
|
||||||
{
|
{
|
||||||
if (DescribeCall::describing() !== []) {
|
if (DescribeCall::describing() !== []) {
|
||||||
@@ -203,8 +167,6 @@ if (! function_exists('afterAll')) {
|
|||||||
|
|
||||||
if (! function_exists('covers')) {
|
if (! function_exists('covers')) {
|
||||||
/**
|
/**
|
||||||
* Specifies which classes, or functions, a test case covers.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $classesOrFunctions
|
* @param array<int, string>|string $classesOrFunctions
|
||||||
*/
|
*/
|
||||||
function covers(array|string ...$classesOrFunctions): void
|
function covers(array|string ...$classesOrFunctions): void
|
||||||
@@ -232,8 +194,6 @@ if (! function_exists('covers')) {
|
|||||||
|
|
||||||
if (! function_exists('mutates')) {
|
if (! function_exists('mutates')) {
|
||||||
/**
|
/**
|
||||||
* Specifies which classes, enums, or traits a test case mutates.
|
|
||||||
*
|
|
||||||
* @param array<int, string>|string $targets
|
* @param array<int, string>|string $targets
|
||||||
*/
|
*/
|
||||||
function mutates(array|string ...$targets): void
|
function mutates(array|string ...$targets): void
|
||||||
@@ -266,9 +226,6 @@ if (! function_exists('mutates')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! function_exists('fixture')) {
|
if (! function_exists('fixture')) {
|
||||||
/**
|
|
||||||
* Returns the absolute path to a fixture file.
|
|
||||||
*/
|
|
||||||
function fixture(string $file): string
|
function fixture(string $file): string
|
||||||
{
|
{
|
||||||
$file = implode(DIRECTORY_SEPARATOR, [
|
$file = implode(DIRECTORY_SEPARATOR, [
|
||||||
@@ -292,8 +249,6 @@ if (! function_exists('fixture')) {
|
|||||||
|
|
||||||
if (! function_exists('visit')) {
|
if (! function_exists('visit')) {
|
||||||
/**
|
/**
|
||||||
* Browse to the given URL.
|
|
||||||
*
|
|
||||||
* @template TUrl of array<int, string>|string
|
* @template TUrl of array<int, string>|string
|
||||||
*
|
*
|
||||||
* @param TUrl $url
|
* @param TUrl $url
|
||||||
|
|||||||
+1
-24
@@ -29,32 +29,23 @@ use Whoops\Exception\Inspector;
|
|||||||
*/
|
*/
|
||||||
final class Kernel
|
final class Kernel
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Either the kernel is terminated or not.
|
|
||||||
*/
|
|
||||||
private bool $terminated = false;
|
private bool $terminated = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Kernel bootstrappers.
|
|
||||||
*
|
|
||||||
* @var array<int, class-string>
|
* @var array<int, class-string>
|
||||||
*/
|
*/
|
||||||
private const array BOOTSTRAPPERS = [
|
private const array BOOTSTRAPPERS = [
|
||||||
Bootstrappers\BootOverrides::class,
|
Bootstrappers\BootOverrides::class,
|
||||||
Bootstrappers\BootPhpUnitConfiguration::class,
|
Bootstrappers\BootPhpUnitConfiguration::class,
|
||||||
Plugins\Tia\Bootstrapper::class,
|
|
||||||
Bootstrappers\BootSubscribers::class,
|
Bootstrappers\BootSubscribers::class,
|
||||||
Bootstrappers\BootFiles::class,
|
Bootstrappers\BootFiles::class,
|
||||||
|
Plugins\Tia\Bootstrapper::class,
|
||||||
Bootstrappers\BootView::class,
|
Bootstrappers\BootView::class,
|
||||||
Bootstrappers\BootKernelDump::class,
|
Bootstrappers\BootKernelDump::class,
|
||||||
Bootstrappers\BootExcludeList::class,
|
Bootstrappers\BootExcludeList::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Kernel restarters — resolved and invoked from `bin/pest`
|
|
||||||
* before any other Pest class is touched, so the list is exposed
|
|
||||||
* on the Kernel rather than driven from `bin/pest` directly.
|
|
||||||
*
|
|
||||||
* @var array<int, class-string<Contracts\Restarter>>
|
* @var array<int, class-string<Contracts\Restarter>>
|
||||||
*/
|
*/
|
||||||
public const array RESTARTERS = [
|
public const array RESTARTERS = [
|
||||||
@@ -62,14 +53,8 @@ final class Kernel
|
|||||||
Restarters\PcovRestarter::class,
|
Restarters\PcovRestarter::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Kernel instance.
|
|
||||||
*/
|
|
||||||
public function __construct(private readonly Application $application, private readonly OutputInterface $output) {}
|
public function __construct(private readonly Application $application, private readonly OutputInterface $output) {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Boots the Kernel.
|
|
||||||
*/
|
|
||||||
public static function boot(TestSuite $testSuite, InputInterface $input, OutputInterface $output): self
|
public static function boot(TestSuite $testSuite, InputInterface $input, OutputInterface $output): self
|
||||||
{
|
{
|
||||||
$container = Container::getInstance();
|
$container = Container::getInstance();
|
||||||
@@ -102,8 +87,6 @@ final class Kernel
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the application, and returns the exit code.
|
|
||||||
*
|
|
||||||
* @param array<int, string> $originalArguments
|
* @param array<int, string> $originalArguments
|
||||||
* @param array<int, string> $arguments
|
* @param array<int, string> $arguments
|
||||||
*/
|
*/
|
||||||
@@ -135,9 +118,6 @@ final class Kernel
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Terminate the Kernel.
|
|
||||||
*/
|
|
||||||
public function terminate(): void
|
public function terminate(): void
|
||||||
{
|
{
|
||||||
if ($this->terminated) {
|
if ($this->terminated) {
|
||||||
@@ -155,9 +135,6 @@ final class Kernel
|
|||||||
CallsTerminable::execute();
|
CallsTerminable::execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shutdowns unexpectedly the Kernel.
|
|
||||||
*/
|
|
||||||
public function shutdown(): void
|
public function shutdown(): void
|
||||||
{
|
{
|
||||||
$this->terminate();
|
$this->terminate();
|
||||||
|
|||||||
+1
-25
@@ -10,23 +10,14 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
|
|
||||||
final class KernelDump
|
final class KernelDump
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The output buffer, if any.
|
|
||||||
*/
|
|
||||||
private string $buffer = '';
|
private string $buffer = '';
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Kernel Dump instance.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly OutputInterface $output,
|
private readonly OutputInterface $output,
|
||||||
) {
|
) {
|
||||||
// ...
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable the output buffering.
|
|
||||||
*/
|
|
||||||
public function enable(): void
|
public function enable(): void
|
||||||
{
|
{
|
||||||
if (class_exists(Execution::class) && Execution::running()) {
|
if (class_exists(Execution::class) && Execution::running()) {
|
||||||
@@ -40,9 +31,6 @@ final class KernelDump
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable the output buffering.
|
|
||||||
*/
|
|
||||||
public function disable(): void
|
public function disable(): void
|
||||||
{
|
{
|
||||||
@ob_clean();
|
@ob_clean();
|
||||||
@@ -52,17 +40,11 @@ final class KernelDump
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Terminate the output buffering.
|
|
||||||
*/
|
|
||||||
public function terminate(): void
|
public function terminate(): void
|
||||||
{
|
{
|
||||||
$this->disable();
|
$this->disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Flushes the buffer.
|
|
||||||
*/
|
|
||||||
private function flush(): void
|
private function flush(): void
|
||||||
{
|
{
|
||||||
View::renderUsing($this->output);
|
View::renderUsing($this->output);
|
||||||
@@ -103,17 +85,11 @@ final class KernelDump
|
|||||||
$this->buffer = '';
|
$this->buffer = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the given output contains an opening headline.
|
|
||||||
*/
|
|
||||||
private function isOpeningHeadline(string $output): bool
|
private function isOpeningHeadline(string $output): bool
|
||||||
{
|
{
|
||||||
return str_contains($output, 'by Sebastian Bergmann and contributors.');
|
return str_contains($output, 'by Sebastian Bergmann and contributors.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the given output contains an opening headline.
|
|
||||||
*/
|
|
||||||
private function isInternalError(string $output): bool
|
private function isInternalError(string $output): bool
|
||||||
{
|
{
|
||||||
return str_contains($output, 'An error occurred inside PHPUnit.');
|
return str_contains($output, 'An error occurred inside PHPUnit.');
|
||||||
|
|||||||
@@ -25,28 +25,16 @@ use PHPUnit\TestRunner\TestResult\TestResult as PhpUnitTestResult;
|
|||||||
*/
|
*/
|
||||||
final readonly class Converter
|
final readonly class Converter
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The prefix for the test suite name.
|
|
||||||
*/
|
|
||||||
private const string PREFIX = 'P\\';
|
private const string PREFIX = 'P\\';
|
||||||
|
|
||||||
/**
|
|
||||||
* The state generator.
|
|
||||||
*/
|
|
||||||
private StateGenerator $stateGenerator;
|
private StateGenerator $stateGenerator;
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance of the Converter.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private string $rootPath,
|
private string $rootPath,
|
||||||
) {
|
) {
|
||||||
$this->stateGenerator = new StateGenerator;
|
$this->stateGenerator = new StateGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the test case method name.
|
|
||||||
*/
|
|
||||||
public function getTestCaseMethodName(Test $test): string
|
public function getTestCaseMethodName(Test $test): string
|
||||||
{
|
{
|
||||||
if (! $test instanceof TestMethod) {
|
if (! $test instanceof TestMethod) {
|
||||||
@@ -56,9 +44,6 @@ final readonly class Converter
|
|||||||
return $test->testDox()->prettifiedMethodName();
|
return $test->testDox()->prettifiedMethodName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the test case location.
|
|
||||||
*/
|
|
||||||
public function getTestCaseLocation(Test $test): string
|
public function getTestCaseLocation(Test $test): string
|
||||||
{
|
{
|
||||||
if (! $test instanceof TestMethod) {
|
if (! $test instanceof TestMethod) {
|
||||||
@@ -68,15 +53,11 @@ final readonly class Converter
|
|||||||
$path = $test->testDox()->prettifiedClassName();
|
$path = $test->testDox()->prettifiedClassName();
|
||||||
$relativePath = $this->toRelativePath($path);
|
$relativePath = $this->toRelativePath($path);
|
||||||
|
|
||||||
// TODO: Get the description without the dataset.
|
|
||||||
$description = $test->testDox()->prettifiedMethodName();
|
$description = $test->testDox()->prettifiedMethodName();
|
||||||
|
|
||||||
return "$relativePath::$description";
|
return "$relativePath::$description";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the exception message.
|
|
||||||
*/
|
|
||||||
public function getExceptionMessage(Throwable $throwable): string
|
public function getExceptionMessage(Throwable $throwable): string
|
||||||
{
|
{
|
||||||
if (is_a($throwable->className(), FrameworkException::class, true)) {
|
if (is_a($throwable->className(), FrameworkException::class, true)) {
|
||||||
@@ -93,9 +74,6 @@ final readonly class Converter
|
|||||||
return $buffer;
|
return $buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the exception details.
|
|
||||||
*/
|
|
||||||
public function getExceptionDetails(Throwable $throwable): string
|
public function getExceptionDetails(Throwable $throwable): string
|
||||||
{
|
{
|
||||||
$buffer = $this->getStackTrace($throwable);
|
$buffer = $this->getStackTrace($throwable);
|
||||||
@@ -113,26 +91,19 @@ final readonly class Converter
|
|||||||
return $buffer;
|
return $buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the stack trace.
|
|
||||||
*/
|
|
||||||
public function getStackTrace(Throwable $throwable): string
|
public function getStackTrace(Throwable $throwable): string
|
||||||
{
|
{
|
||||||
$stackTrace = $throwable->stackTrace();
|
$stackTrace = $throwable->stackTrace();
|
||||||
|
|
||||||
// Split stacktrace per frame.
|
|
||||||
$frames = explode("\n", $stackTrace);
|
$frames = explode("\n", $stackTrace);
|
||||||
|
|
||||||
// Remove empty lines
|
|
||||||
$frames = array_filter($frames);
|
$frames = array_filter($frames);
|
||||||
|
|
||||||
// clean the paths of each frame.
|
|
||||||
$frames = array_map(
|
$frames = array_map(
|
||||||
$this->toRelativePath(...),
|
$this->toRelativePath(...),
|
||||||
$frames
|
$frames
|
||||||
);
|
);
|
||||||
|
|
||||||
// Format stacktrace as `at <path>`
|
|
||||||
$frames = array_map(
|
$frames = array_map(
|
||||||
fn (string $frame): string => "at $frame",
|
fn (string $frame): string => "at $frame",
|
||||||
$frames
|
$frames
|
||||||
@@ -141,9 +112,6 @@ final readonly class Converter
|
|||||||
return implode("\n", $frames);
|
return implode("\n", $frames);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the test suite name.
|
|
||||||
*/
|
|
||||||
public function getTestSuiteName(TestSuite $testSuite): string
|
public function getTestSuiteName(TestSuite $testSuite): string
|
||||||
{
|
{
|
||||||
if ($testSuite instanceof TestSuiteForTestMethodWithDataProvider) {
|
if ($testSuite instanceof TestSuiteForTestMethodWithDataProvider) {
|
||||||
@@ -162,17 +130,11 @@ final readonly class Converter
|
|||||||
return Str::after($name, self::PREFIX);
|
return Str::after($name, self::PREFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the trimmed test class name.
|
|
||||||
*/
|
|
||||||
public function getTrimmedTestClassName(TestMethod $test): string
|
public function getTrimmedTestClassName(TestMethod $test): string
|
||||||
{
|
{
|
||||||
return Str::after($test->className(), self::PREFIX);
|
return Str::after($test->className(), self::PREFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the test suite location.
|
|
||||||
*/
|
|
||||||
public function getTestSuiteLocation(TestSuite $testSuite): ?string
|
public function getTestSuiteLocation(TestSuite $testSuite): ?string
|
||||||
{
|
{
|
||||||
$firstTest = $this->getFirstTest($testSuite);
|
$firstTest = $this->getFirstTest($testSuite);
|
||||||
@@ -191,22 +153,15 @@ final readonly class Converter
|
|||||||
return $classRelativePath;
|
return $classRelativePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the prettified test method name without dataset-related suffix.
|
|
||||||
*/
|
|
||||||
private function getTestMethodNameWithoutDatasetSuffix(TestMethod $testMethod): string
|
private function getTestMethodNameWithoutDatasetSuffix(TestMethod $testMethod): string
|
||||||
{
|
{
|
||||||
return Str::beforeLast($testMethod->testDox()->prettifiedMethodName(), ' with data set ');
|
return Str::beforeLast($testMethod->testDox()->prettifiedMethodName(), ' with data set ');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the first test from the test suite.
|
|
||||||
*/
|
|
||||||
private function getFirstTest(TestSuite $testSuite): ?TestMethod
|
private function getFirstTest(TestSuite $testSuite): ?TestMethod
|
||||||
{
|
{
|
||||||
$tests = $testSuite->tests()->asArray();
|
$tests = $testSuite->tests()->asArray();
|
||||||
|
|
||||||
// TODO: figure out how to get the file path without a test being there.
|
|
||||||
if ($tests === []) {
|
if ($tests === []) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -219,26 +174,16 @@ final readonly class Converter
|
|||||||
return $firstTest;
|
return $firstTest;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the test suite size.
|
|
||||||
*/
|
|
||||||
public function getTestSuiteSize(TestSuite $testSuite): int
|
public function getTestSuiteSize(TestSuite $testSuite): int
|
||||||
{
|
{
|
||||||
return $testSuite->count();
|
return $testSuite->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms the given path in relative path.
|
|
||||||
*/
|
|
||||||
private function toRelativePath(string $path): string
|
private function toRelativePath(string $path): string
|
||||||
{
|
{
|
||||||
// Remove cwd from the path.
|
|
||||||
return str_replace("$this->rootPath".DIRECTORY_SEPARATOR, '', $path);
|
return str_replace("$this->rootPath".DIRECTORY_SEPARATOR, '', $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the test result.
|
|
||||||
*/
|
|
||||||
public function getStateFromResult(PhpUnitTestResult $result): State
|
public function getStateFromResult(PhpUnitTestResult $result): State
|
||||||
{
|
{
|
||||||
$events = [
|
$events = [
|
||||||
@@ -253,11 +198,9 @@ final readonly class Converter
|
|||||||
|
|
||||||
foreach ($events as $event) {
|
foreach ($events as $event) {
|
||||||
if ($event instanceof AfterLastTestMethodErrored) {
|
if ($event instanceof AfterLastTestMethodErrored) {
|
||||||
// PHPUnit's collector does not count these towards `numberOfTestsRun`...
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($event instanceof AfterLastTestMethodFailed) {
|
if ($event instanceof AfterLastTestMethodFailed) {
|
||||||
// PHPUnit's collector does not count these towards `numberOfTestsRun`...
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($event instanceof BeforeFirstTestMethodErrored || $event instanceof BeforeFirstTestMethodFailed) {
|
if ($event instanceof BeforeFirstTestMethodErrored || $event instanceof BeforeFirstTestMethodFailed) {
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ namespace Pest\Logging\TeamCity;
|
|||||||
*/
|
*/
|
||||||
final class ServiceMessage
|
final class ServiceMessage
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The flow ID.
|
|
||||||
*/
|
|
||||||
private static ?int $flowId = null;
|
private static ?int $flowId = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,14 +11,8 @@ use Pest\Logging\TeamCity\TeamCityLogger;
|
|||||||
*/
|
*/
|
||||||
abstract class Subscriber // @pest-arch-ignore-line
|
abstract class Subscriber // @pest-arch-ignore-line
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Creates a new Subscriber instance.
|
|
||||||
*/
|
|
||||||
public function __construct(private readonly TeamCityLogger $logger) {}
|
public function __construct(private readonly TeamCityLogger $logger) {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new TeamCityLogger instance.
|
|
||||||
*/
|
|
||||||
final protected function logger(): TeamCityLogger // @pest-arch-ignore-line
|
final protected function logger(): TeamCityLogger // @pest-arch-ignore-line
|
||||||
{
|
{
|
||||||
return $this->logger;
|
return $this->logger;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user