mirror of
https://github.com/pestphp/pest.git
synced 2026-04-20 22:20:17 +02:00
Merge branch '4.x' into fix/unicode-filename-filter
This commit is contained in:
24
.github/workflows/static.yml
vendored
24
.github/workflows/static.yml
vendored
@ -2,9 +2,14 @@ name: Static Analysis
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [4.x]
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
- cron: '0 9 * * *'
|
||||
|
||||
concurrency:
|
||||
group: static-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
static:
|
||||
@ -12,6 +17,7 @@ jobs:
|
||||
name: Static Tests
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
@ -29,8 +35,22 @@ jobs:
|
||||
coverage: none
|
||||
extensions: sockets
|
||||
|
||||
- name: Get Composer cache directory
|
||||
id: composer-cache
|
||||
shell: bash
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Composer dependencies
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: static-php-8.3-${{ matrix.dependency-version }}-composer-${{ hashFiles('**/composer.json') }}
|
||||
restore-keys: |
|
||||
static-php-8.3-${{ matrix.dependency-version }}-composer-
|
||||
static-php-8.3-composer-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: composer update --prefer-stable --no-interaction --no-progress --ansi
|
||||
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress --ansi
|
||||
|
||||
- name: Profanity Check
|
||||
run: composer test:profanity
|
||||
|
||||
27
.github/workflows/tests.yml
vendored
27
.github/workflows/tests.yml
vendored
@ -2,20 +2,31 @@ name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [4.x]
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 9 * * *'
|
||||
|
||||
concurrency:
|
||||
group: tests-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
if: github.event_name != 'schedule' || github.repository == 'pestphp/pest'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest] # windows-latest
|
||||
symfony: ['7.3']
|
||||
symfony: ['7.4', '8.0']
|
||||
php: ['8.3', '8.4', '8.5']
|
||||
dependency_version: [prefer-stable]
|
||||
exclude:
|
||||
- php: '8.3'
|
||||
symfony: '8.0'
|
||||
|
||||
name: PHP ${{ matrix.php }} - Symfony ^${{ matrix.symfony }} - ${{ matrix.os }} - ${{ matrix.dependency_version }}
|
||||
|
||||
@ -31,6 +42,20 @@ jobs:
|
||||
coverage: none
|
||||
extensions: sockets
|
||||
|
||||
- name: Get Composer cache directory
|
||||
id: composer-cache
|
||||
shell: bash
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Composer dependencies
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ matrix.os }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-composer-${{ hashFiles('**/composer.json') }}
|
||||
restore-keys: |
|
||||
${{ matrix.os }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-composer-
|
||||
${{ matrix.os }}-php-${{ matrix.php }}-composer-
|
||||
|
||||
- name: Setup Problem Matches
|
||||
run: |
|
||||
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
|
||||
|
||||
14
Makefile
14
Makefile
@ -1,14 +0,0 @@
|
||||
# Well documented Makefiles
|
||||
DEFAULT_GOAL := help
|
||||
help:
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-40s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
|
||||
build: ## Build all docker images. Specify the command e.g. via make build ARGS="--build-arg PHP=8.2"
|
||||
docker compose build $(ARGS)
|
||||
|
||||
##@ [Application]
|
||||
install: ## Install the composer dependencies
|
||||
docker compose run --rm composer install
|
||||
|
||||
test: ## Run the tests
|
||||
docker compose run --rm composer test
|
||||
@ -32,8 +32,8 @@ We cannot thank our sponsors enough for their incredible support in funding Pest
|
||||
### Platinum Sponsors
|
||||
|
||||
- **[CodeRabbit](https://coderabbit.ai/?ref=pestphp)**
|
||||
- **[Devin](https://devin.ai/?ref=nunomaduro)**
|
||||
- **[Mailtrap](https://l.rw.rw/pestphp)**
|
||||
- **[SerpApi](https://serpapi.com/?ref=nunomaduro)**
|
||||
- **[Tighten](https://tighten.com/?ref=nunomaduro)**
|
||||
- **[Redberry](https://redberry.international/laravel-development/?utm_source=pest&utm_medium=banner&utm_campaign=pest_sponsorship)**
|
||||
|
||||
@ -48,6 +48,6 @@ We cannot thank our sponsors enough for their incredible support in funding Pest
|
||||
- [Route4Me](https://route4me.com/pt?ref=pestphp)
|
||||
- [Nerdify](https://getnerdify.com/?ref=pestphp)
|
||||
- [Akaunting](https://akaunting.com/?ref=pestphp)
|
||||
- [LambdaTest](https://lambdatest.com/?ref=pestphp)
|
||||
- [TestMu AI](https://www.testmuai.com/?utm_medium=sponsor&utm_source=pest)
|
||||
|
||||
Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
||||
|
||||
@ -18,19 +18,19 @@
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.3.0",
|
||||
"brianium/paratest": "^7.16.1",
|
||||
"nunomaduro/collision": "^8.8.3",
|
||||
"nunomaduro/termwind": "^2.3.3",
|
||||
"brianium/paratest": "^7.20.0",
|
||||
"nunomaduro/collision": "^8.9.3",
|
||||
"nunomaduro/termwind": "^2.4.0",
|
||||
"pestphp/pest-plugin": "^4.0.0",
|
||||
"pestphp/pest-plugin-arch": "^4.0.0",
|
||||
"pestphp/pest-plugin-mutate": "^4.0.1",
|
||||
"pestphp/pest-plugin-profanity": "^4.2.1",
|
||||
"phpunit/phpunit": "^12.5.8",
|
||||
"symfony/process": "^7.4.4|^8.0.0"
|
||||
"phpunit/phpunit": "^12.5.16",
|
||||
"symfony/process": "^7.4.8|^8.0.8"
|
||||
},
|
||||
"conflict": {
|
||||
"filp/whoops": "<2.18.3",
|
||||
"phpunit/phpunit": ">12.5.8",
|
||||
"phpunit/phpunit": ">12.5.16",
|
||||
"sebastian/exporter": "<7.0.0",
|
||||
"webmozart/assert": "<1.11.0"
|
||||
},
|
||||
@ -55,10 +55,10 @@
|
||||
]
|
||||
},
|
||||
"require-dev": {
|
||||
"pestphp/pest-dev-tools": "^4.0.0",
|
||||
"pestphp/pest-plugin-browser": "^4.2.1",
|
||||
"pestphp/pest-plugin-type-coverage": "^4.0.3",
|
||||
"psy/psysh": "^0.12.18"
|
||||
"pestphp/pest-dev-tools": "^4.1.0",
|
||||
"pestphp/pest-plugin-browser": "^4.3.1",
|
||||
"pestphp/pest-plugin-type-coverage": "^4.0.4",
|
||||
"psy/psysh": "^0.12.22"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
php:
|
||||
build:
|
||||
context: ./docker
|
||||
volumes:
|
||||
- .:/var/www/html
|
||||
composer:
|
||||
build:
|
||||
context: ./docker
|
||||
volumes:
|
||||
- .:/var/www/html
|
||||
entrypoint: ["composer"]
|
||||
@ -14,6 +14,9 @@ namespace PHPUnit\Logging\JUnit;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use Pest\Logging\Converter;
|
||||
use Pest\Support\Container;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Event\Code\Test;
|
||||
use PHPUnit\Event\Code\TestMethod;
|
||||
use PHPUnit\Event\EventFacadeIsSealedException;
|
||||
@ -50,7 +53,7 @@ final class JunitXmlLogger
|
||||
{
|
||||
private readonly Printer $printer;
|
||||
|
||||
private readonly \Pest\Logging\Converter $converter; // pest-added
|
||||
private readonly Converter $converter; // pest-added
|
||||
|
||||
private DOMDocument $document;
|
||||
|
||||
@ -108,7 +111,7 @@ final class JunitXmlLogger
|
||||
public function __construct(Printer $printer, Facade $facade)
|
||||
{
|
||||
$this->printer = $printer;
|
||||
$this->converter = new \Pest\Logging\Converter(\Pest\Support\Container::getInstance()->get(\Pest\TestSuite::class)->rootPath); // pest-added
|
||||
$this->converter = new Converter(Container::getInstance()->get(TestSuite::class)->rootPath); // pest-added
|
||||
|
||||
$this->registerSubscribers($facade);
|
||||
$this->createDocument();
|
||||
|
||||
@ -69,6 +69,7 @@ final class Laravel extends AbstractPreset
|
||||
->toHaveSuffix('Request');
|
||||
|
||||
$this->expectations[] = expect('App\Http\Requests')
|
||||
->classes()
|
||||
->toExtend('Illuminate\Foundation\Http\FormRequest');
|
||||
|
||||
$this->expectations[] = expect('App\Http\Requests')
|
||||
@ -118,6 +119,7 @@ final class Laravel extends AbstractPreset
|
||||
->toHaveMethod('handle');
|
||||
|
||||
$this->expectations[] = expect('App\Notifications')
|
||||
->classes()
|
||||
->toExtend('Illuminate\Notifications\Notification');
|
||||
|
||||
$this->expectations[] = expect('App')
|
||||
@ -128,6 +130,7 @@ final class Laravel extends AbstractPreset
|
||||
->toHaveSuffix('ServiceProvider');
|
||||
|
||||
$this->expectations[] = expect('App\Providers')
|
||||
->classes()
|
||||
->toExtend('Illuminate\Support\ServiceProvider');
|
||||
|
||||
$this->expectations[] = expect('App\Providers')
|
||||
@ -150,7 +153,7 @@ final class Laravel extends AbstractPreset
|
||||
->toHaveSuffix('Controller');
|
||||
|
||||
$this->expectations[] = expect('App\Http')
|
||||
->toOnlyBeUsedIn('App\Http');
|
||||
->toOnlyBeUsedIn(['App\Http', 'App\Providers']);
|
||||
|
||||
$this->expectations[] = expect('App\Http\Controllers')
|
||||
->not->toHavePublicMethodsBesides(['__construct', '__invoke', 'index', 'show', 'create', 'store', 'edit', 'update', 'destroy', 'middleware']);
|
||||
@ -173,5 +176,9 @@ final class Laravel extends AbstractPreset
|
||||
->toImplement('Illuminate\Contracts\Container\ContextualAttribute')
|
||||
->toHaveAttribute('Attribute')
|
||||
->toHaveMethod('resolve');
|
||||
|
||||
$this->expectations[] = expect('App\Rules')
|
||||
->classes()
|
||||
->toImplement('Illuminate\Contracts\Validation\ValidationRule');
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,6 +66,6 @@ trait Pipeable
|
||||
*/
|
||||
private function pipes(string $name, object $context, string $scope): array
|
||||
{
|
||||
return array_map(fn (Closure $pipe): \Closure => $pipe->bindTo($context, $scope), self::$pipes[$name] ?? []);
|
||||
return array_map(fn (Closure $pipe): Closure => $pipe->bindTo($context, $scope), self::$pipes[$name] ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@ trait Testable
|
||||
*/
|
||||
public function __addBeforeAll(?Closure $hook): void
|
||||
{
|
||||
if (! $hook instanceof \Closure) {
|
||||
if (! $hook instanceof Closure) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -143,7 +143,7 @@ trait Testable
|
||||
*/
|
||||
public function __addAfterAll(?Closure $hook): void
|
||||
{
|
||||
if (! $hook instanceof \Closure) {
|
||||
if (! $hook instanceof Closure) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -173,7 +173,7 @@ trait Testable
|
||||
*/
|
||||
private function __addHook(string $property, ?Closure $hook): void
|
||||
{
|
||||
if (! $hook instanceof \Closure) {
|
||||
if (! $hook instanceof Closure) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ use Pest\Arch\Expectations\ToOnlyUse;
|
||||
use Pest\Arch\Expectations\ToUse;
|
||||
use Pest\Arch\Expectations\ToUseNothing;
|
||||
use Pest\Arch\PendingArchExpectation;
|
||||
use Pest\Arch\Support\Composer;
|
||||
use Pest\Arch\Support\FileLineFinder;
|
||||
use Pest\Concerns\Extendable;
|
||||
use Pest\Concerns\Pipeable;
|
||||
@ -136,7 +137,7 @@ 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>
|
||||
*/
|
||||
public function ddWhen(Closure|bool $condition, mixed ...$arguments): Expectation
|
||||
@ -153,7 +154,7 @@ 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>
|
||||
*/
|
||||
public function ddUnless(Closure|bool $condition, mixed ...$arguments): Expectation
|
||||
@ -669,6 +670,37 @@ final class Expectation
|
||||
throw InvalidExpectation::fromMethods(['toHavePrivateMethods']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given expectation target is cased correctly.
|
||||
*/
|
||||
public function toBeCasedCorrectly(): ArchExpectation
|
||||
{
|
||||
return Targeted::make(
|
||||
$this,
|
||||
function (ObjectDescription $object): bool {
|
||||
if (! isset($object->reflectionClass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$realPath = realpath($object->path);
|
||||
|
||||
foreach (Composer::userNamespacesWithDirectories() as $directory => $namespace) {
|
||||
if (str_starts_with($realPath, $directory)) {
|
||||
$relativePath = substr($realPath, strlen($directory) + 1);
|
||||
$relativePath = explode('.', $relativePath)[0];
|
||||
$classFromPath = $namespace.'\\'.str_replace(DIRECTORY_SEPARATOR, '\\', $relativePath);
|
||||
|
||||
return $classFromPath === $object->reflectionClass->getName();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
'to be cased correctly',
|
||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given expectation target is enum.
|
||||
*/
|
||||
@ -783,7 +815,22 @@ final class Expectation
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! in_array($trait, $object->reflectionClass->getTraitNames(), true)) {
|
||||
$currentClass = $object->reflectionClass;
|
||||
$usedTraits = [];
|
||||
|
||||
do {
|
||||
$classTraits = $currentClass->getTraits();
|
||||
foreach ($classTraits as $traitReflection) {
|
||||
$usedTraits[$traitReflection->getName()] = $traitReflection->getName();
|
||||
|
||||
$nestedTraits = $traitReflection->getTraits();
|
||||
foreach ($nestedTraits as $nestedTrait) {
|
||||
$usedTraits[$nestedTrait->getName()] = $nestedTrait->getName();
|
||||
}
|
||||
}
|
||||
} while ($currentClass = $currentClass->getParentClass());
|
||||
|
||||
if (! array_key_exists($trait, $usedTraits)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ use Pest\Factories\Concerns\HigherOrderable;
|
||||
use Pest\Support\Reflection;
|
||||
use Pest\Support\Str;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RuntimeException;
|
||||
|
||||
@ -58,6 +59,11 @@ final class TestCaseFactory
|
||||
Concerns\Expectable::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The namespace for the test case, overrides the path-based namespace when set.
|
||||
*/
|
||||
public ?string $namespace = null;
|
||||
|
||||
/**
|
||||
* Creates a new Factory instance.
|
||||
*/
|
||||
@ -126,7 +132,7 @@ final class TestCaseFactory
|
||||
|
||||
$partsFQN = explode('\\', $classFQN);
|
||||
$className = array_pop($partsFQN);
|
||||
$namespace = implode('\\', $partsFQN);
|
||||
$namespace = $this->namespace ?? implode('\\', $partsFQN);
|
||||
$baseClass = sprintf('\%s', $this->class);
|
||||
|
||||
if (trim($className) === '') {
|
||||
@ -135,7 +141,7 @@ final class TestCaseFactory
|
||||
|
||||
$this->attributes = [
|
||||
new Attribute(
|
||||
\PHPUnit\Framework\Attributes\TestDox::class,
|
||||
TestDox::class,
|
||||
[$this->filename],
|
||||
),
|
||||
...$this->attributes,
|
||||
|
||||
@ -9,10 +9,14 @@ use Pest\Evaluators\Attributes;
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
use Pest\Factories\Concerns\HigherOrderable;
|
||||
use Pest\Repositories\DatasetsRepository;
|
||||
use Pest\Support\Description;
|
||||
use Pest\Support\Str;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Depends;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
@ -32,7 +36,7 @@ final class TestCaseMethodFactory
|
||||
/**
|
||||
* The test's describing, if any.
|
||||
*
|
||||
* @var array<int, \Pest\Support\Description>
|
||||
* @var array<int, Description>
|
||||
*/
|
||||
public array $describing = [];
|
||||
|
||||
@ -192,11 +196,11 @@ final class TestCaseMethodFactory
|
||||
|
||||
$this->attributes = [
|
||||
new Attribute(
|
||||
\PHPUnit\Framework\Attributes\Test::class,
|
||||
Test::class,
|
||||
[],
|
||||
),
|
||||
new Attribute(
|
||||
\PHPUnit\Framework\Attributes\TestDox::class,
|
||||
TestDox::class,
|
||||
[str_replace('*/', '{@*}', $this->description)],
|
||||
),
|
||||
...$this->attributes,
|
||||
@ -206,7 +210,7 @@ final class TestCaseMethodFactory
|
||||
$depend = Str::evaluable($this->describing === [] ? $depend : Str::describe($this->describing, $depend));
|
||||
|
||||
$this->attributes[] = new Attribute(
|
||||
\PHPUnit\Framework\Attributes\Depends::class,
|
||||
Depends::class,
|
||||
[$depend],
|
||||
);
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ if (! function_exists('test')) {
|
||||
*/
|
||||
function test(?string $description = null, ?Closure $closure = null): HigherOrderTapProxy|TestCall
|
||||
{
|
||||
if ($description === null && TestSuite::getInstance()->test instanceof \PHPUnit\Framework\TestCase) {
|
||||
if ($description === null && TestSuite::getInstance()->test instanceof TestCase) {
|
||||
return new HigherOrderTapProxy(TestSuite::getInstance()->test);
|
||||
}
|
||||
|
||||
@ -236,7 +236,7 @@ if (! function_exists('covers')) {
|
||||
|
||||
/** @var MutationTestRunner $runner */
|
||||
$runner = Container::getInstance()->get(MutationTestRunner::class);
|
||||
/** @var \Pest\Mutate\Repositories\ConfigurationRepository $configurationRepository */
|
||||
/** @var ConfigurationRepository $configurationRepository */
|
||||
$configurationRepository = Container::getInstance()->get(ConfigurationRepository::class);
|
||||
$everything = $configurationRepository->cliConfiguration->toArray()['everything'] ?? false;
|
||||
$classes = $configurationRepository->cliConfiguration->toArray()['classes'] ?? false;
|
||||
@ -263,7 +263,7 @@ if (! function_exists('mutates')) {
|
||||
|
||||
/** @var MutationTestRunner $runner */
|
||||
$runner = Container::getInstance()->get(MutationTestRunner::class);
|
||||
/** @var \Pest\Mutate\Repositories\ConfigurationRepository $configurationRepository */
|
||||
/** @var ConfigurationRepository $configurationRepository */
|
||||
$configurationRepository = Container::getInstance()->get(ConfigurationRepository::class);
|
||||
$everything = $configurationRepository->cliConfiguration->toArray()['everything'] ?? false;
|
||||
$classes = $configurationRepository->cliConfiguration->toArray()['classes'] ?? false;
|
||||
@ -320,7 +320,7 @@ if (! function_exists('visit')) {
|
||||
*/
|
||||
function visit(array|string $url, array $options = []): ArrayablePendingAwaitablePage|PendingAwaitablePage
|
||||
{
|
||||
if (! class_exists(\Pest\Browser\Configuration::class)) {
|
||||
if (! class_exists(Pest\Browser\Configuration::class)) {
|
||||
PluginBrowser::install();
|
||||
|
||||
exit(0);
|
||||
|
||||
@ -151,7 +151,7 @@ final readonly class Converter
|
||||
{
|
||||
if ($testSuite instanceof TestSuiteForTestMethodWithDataProvider) {
|
||||
$firstTest = $this->getFirstTest($testSuite);
|
||||
if ($firstTest instanceof \PHPUnit\Event\Code\TestMethod) {
|
||||
if ($firstTest instanceof TestMethod) {
|
||||
return $this->getTestMethodNameWithoutDatasetSuffix($firstTest);
|
||||
}
|
||||
}
|
||||
@ -179,7 +179,7 @@ final readonly class Converter
|
||||
public function getTestSuiteLocation(TestSuite $testSuite): ?string
|
||||
{
|
||||
$firstTest = $this->getFirstTest($testSuite);
|
||||
if (! $firstTest instanceof \PHPUnit\Event\Code\TestMethod) {
|
||||
if (! $firstTest instanceof TestMethod) {
|
||||
return null;
|
||||
}
|
||||
$path = $firstTest->testDox()->prettifiedClassName();
|
||||
|
||||
@ -200,7 +200,7 @@ final class TeamCityLogger
|
||||
|
||||
public function testFinished(Finished $event): void
|
||||
{
|
||||
if (! $this->time instanceof \PHPUnit\Event\Telemetry\HRTime) {
|
||||
if (! $this->time instanceof HRTime) {
|
||||
throw ShouldNotHappen::fromMessage('Start time has not been set.');
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ use Closure;
|
||||
use Countable;
|
||||
use DateTimeInterface;
|
||||
use Error;
|
||||
use Illuminate\Testing\TestResponse;
|
||||
use InvalidArgumentException;
|
||||
use JsonSerializable;
|
||||
use Pest\Exceptions\InvalidExpectationValue;
|
||||
@ -842,7 +843,7 @@ final class Expectation
|
||||
is_object($this->value) && method_exists($this->value, 'toSnapshot') => $this->value->toSnapshot(),
|
||||
is_object($this->value) && method_exists($this->value, '__toString') => $this->value->__toString(),
|
||||
is_object($this->value) && method_exists($this->value, 'toString') => $this->value->toString(),
|
||||
$this->value instanceof \Illuminate\Testing\TestResponse => $this->value->getContent(), // @phpstan-ignore-line
|
||||
$this->value instanceof TestResponse => $this->value->getContent(), // @phpstan-ignore-line
|
||||
is_array($this->value) => json_encode($this->value, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT),
|
||||
$this->value instanceof Traversable => json_encode(iterator_to_array($this->value), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT),
|
||||
$this->value instanceof JsonSerializable => json_encode($this->value->jsonSerialize(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT),
|
||||
@ -983,7 +984,7 @@ final class Expectation
|
||||
*/
|
||||
private function export(mixed $value): string
|
||||
{
|
||||
if (! $this->exporter instanceof \Pest\Support\Exporter) {
|
||||
if (! $this->exporter instanceof Exporter) {
|
||||
$this->exporter = Exporter::default();
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\PendingCalls\Concerns;
|
||||
|
||||
use Pest\Support\Description;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -12,14 +14,14 @@ trait Describable
|
||||
/**
|
||||
* Note: this is property is not used; however, it gets added automatically by rector php.
|
||||
*
|
||||
* @var array<int, \Pest\Support\Description>
|
||||
* @var array<int, Description>
|
||||
*/
|
||||
public array $__describing;
|
||||
|
||||
/**
|
||||
* The describing of the test case.
|
||||
*
|
||||
* @var array<int, \Pest\Support\Description>
|
||||
* @var array<int, Description>
|
||||
*/
|
||||
public array $describing = [];
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ final class DescribeCall
|
||||
{
|
||||
$filename = Backtrace::file();
|
||||
|
||||
if (! $this->currentBeforeEachCall instanceof \Pest\PendingCalls\BeforeEachCall) {
|
||||
if (! $this->currentBeforeEachCall instanceof BeforeEachCall) {
|
||||
$this->currentBeforeEachCall = new BeforeEachCall(TestSuite::getInstance(), $filename);
|
||||
|
||||
$this->currentBeforeEachCall->describing[] = $this->description;
|
||||
|
||||
@ -22,6 +22,10 @@ use Pest\Support\NullClosure;
|
||||
use Pest\Support\Str;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\CoversFunction;
|
||||
use PHPUnit\Framework\Attributes\CoversTrait;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
@ -211,7 +215,7 @@ final class TestCall // @phpstan-ignore-line
|
||||
{
|
||||
foreach ($groups as $group) {
|
||||
$this->testCaseMethod->attributes[] = new Attribute(
|
||||
\PHPUnit\Framework\Attributes\Group::class,
|
||||
Group::class,
|
||||
[$group],
|
||||
);
|
||||
}
|
||||
@ -604,7 +608,7 @@ final class TestCall // @phpstan-ignore-line
|
||||
{
|
||||
foreach ($classes as $class) {
|
||||
$this->testCaseFactoryAttributes[] = new Attribute(
|
||||
\PHPUnit\Framework\Attributes\CoversClass::class,
|
||||
CoversClass::class,
|
||||
[$class],
|
||||
);
|
||||
}
|
||||
@ -627,7 +631,7 @@ final class TestCall // @phpstan-ignore-line
|
||||
{
|
||||
foreach ($traits as $trait) {
|
||||
$this->testCaseFactoryAttributes[] = new Attribute(
|
||||
\PHPUnit\Framework\Attributes\CoversTrait::class,
|
||||
CoversTrait::class,
|
||||
[$trait],
|
||||
);
|
||||
}
|
||||
@ -650,7 +654,7 @@ final class TestCall // @phpstan-ignore-line
|
||||
{
|
||||
foreach ($functions as $function) {
|
||||
$this->testCaseFactoryAttributes[] = new Attribute(
|
||||
\PHPUnit\Framework\Attributes\CoversFunction::class,
|
||||
CoversFunction::class,
|
||||
[$function],
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ namespace Pest;
|
||||
|
||||
function version(): string
|
||||
{
|
||||
return '4.3.2';
|
||||
return '4.4.4';
|
||||
}
|
||||
|
||||
function testDirectory(string $file = ''): string
|
||||
|
||||
@ -56,4 +56,31 @@ trait HandleArguments
|
||||
|
||||
return array_values(array_flip($arguments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops the given argument and its value from the arguments, returning the value.
|
||||
*
|
||||
* @param array<int, string> $arguments
|
||||
*/
|
||||
public function popArgumentValue(string $argument, array &$arguments): ?string
|
||||
{
|
||||
foreach ($arguments as $key => $value) {
|
||||
if (str_contains($value, "$argument=")) {
|
||||
unset($arguments[$key]);
|
||||
$arguments = array_values($arguments);
|
||||
|
||||
return substr($value, strlen($argument) + 1);
|
||||
}
|
||||
|
||||
if ($value === $argument && isset($arguments[$key + 1])) {
|
||||
$result = $arguments[$key + 1];
|
||||
unset($arguments[$key], $arguments[$key + 1]);
|
||||
$arguments = array_values($arguments);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,6 +107,13 @@ final readonly class Help implements HandlesArguments
|
||||
'desc' => 'Initialise a standard Pest configuration',
|
||||
]], ...$content['Configuration']];
|
||||
|
||||
$content['AI'] = [
|
||||
[
|
||||
'arg' => '--ai',
|
||||
'desc' => 'Run a code snippet as a fully scaffolded test for AI verification',
|
||||
],
|
||||
];
|
||||
|
||||
$content['Execution'] = [...[
|
||||
[
|
||||
'arg' => '--parallel',
|
||||
|
||||
@ -127,7 +127,9 @@ final class Parallel implements HandlesArguments
|
||||
$arguments
|
||||
);
|
||||
|
||||
$exitCode = $this->paratestCommand()->run(new ArgvInput($filteredArguments), new CleanConsoleOutput);
|
||||
$filteredArguments = $this->processTeamcityArguments($filteredArguments);
|
||||
|
||||
$exitCode = $this->paratestCommand()->run(new ArgvInput(array_values($filteredArguments)), new CleanConsoleOutput);
|
||||
|
||||
return CallsAddsOutput::execute($exitCode);
|
||||
}
|
||||
@ -197,4 +199,18 @@ final class Parallel implements HandlesArguments
|
||||
|
||||
return $this->popArgument('-p', $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $arguments
|
||||
* @return string[]
|
||||
*/
|
||||
public function processTeamcityArguments(array $arguments): array
|
||||
{
|
||||
$argv = new ArgvInput;
|
||||
if ($argv->hasParameterOption('--teamcity')) {
|
||||
$arguments[] = '--teamcity';
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ namespace Pest\Plugins\Parallel\Handlers;
|
||||
use Closure;
|
||||
use Composer\InstalledVersions;
|
||||
use Illuminate\Testing\ParallelRunner;
|
||||
use Orchestra\Testbench\TestCase;
|
||||
use ParaTest\Options;
|
||||
use ParaTest\RunnerInterface;
|
||||
use Pest\Contracts\Plugins\HandlesArguments;
|
||||
@ -39,13 +40,13 @@ final class Laravel implements HandlesArguments
|
||||
* Executes the given closure when running Laravel.
|
||||
*
|
||||
* @param array<int, string> $arguments
|
||||
* @param CLosure(array<int, string>): array<int, string> $closure
|
||||
* @param Closure(array<int, string>): array<int, string> $closure
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function whenUsingLaravel(array $arguments, Closure $closure): array
|
||||
{
|
||||
$isLaravelApplication = InstalledVersions::isInstalled('laravel/framework', false);
|
||||
$isLaravelPackage = class_exists(\Orchestra\Testbench\TestCase::class);
|
||||
$isLaravelPackage = class_exists(TestCase::class);
|
||||
|
||||
if ($isLaravelApplication && ! $isLaravelPackage) {
|
||||
return $closure($arguments);
|
||||
|
||||
@ -92,14 +92,13 @@ final class ResultPrinter
|
||||
$this->teamcityLogFileHandle = $teamcityLogFileHandle;
|
||||
}
|
||||
|
||||
/** @param list<SplFileInfo> $teamcityFiles */
|
||||
public function printFeedback(
|
||||
SplFileInfo $progressFile,
|
||||
SplFileInfo $outputFile,
|
||||
array $teamcityFiles
|
||||
?SplFileInfo $teamcityFile,
|
||||
): void {
|
||||
if ($this->options->needsTeamcity) {
|
||||
$teamcityProgress = $this->tailMultiple($teamcityFiles);
|
||||
if ($this->options->needsTeamcity && $teamcityFile instanceof SplFileInfo) {
|
||||
$teamcityProgress = $this->tailMultiple([$teamcityFile]);
|
||||
|
||||
if ($this->teamcityLogFileHandle !== null) {
|
||||
fwrite($this->teamcityLogFileHandle, $teamcityProgress);
|
||||
@ -171,6 +170,14 @@ final class ResultPrinter
|
||||
|
||||
$state = (new StateGenerator)->fromPhpUnitTestResult($this->passedTests, $testResult);
|
||||
|
||||
if ($testResult->numberOfTestsRun() === 0 && $state->testSuiteTestsCount() === 0) {
|
||||
$this->output->writeln([
|
||||
'',
|
||||
' <fg=white;options=bold;bg=blue> INFO </> No tests found.',
|
||||
'',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->compactPrinter->errors($state);
|
||||
$this->compactPrinter->recap($state, $testResult, $duration, $this->options);
|
||||
}
|
||||
|
||||
@ -39,6 +39,7 @@ use function dirname;
|
||||
use function file_get_contents;
|
||||
use function max;
|
||||
use function realpath;
|
||||
use function str_starts_with;
|
||||
use function unlink;
|
||||
use function unserialize;
|
||||
use function usleep;
|
||||
@ -51,6 +52,11 @@ final class WrapperRunner implements RunnerInterface
|
||||
/**
|
||||
* The time to sleep between cycles.
|
||||
*/
|
||||
/**
|
||||
* The merged test result from the parallel run.
|
||||
*/
|
||||
public static ?TestResult $result = null;
|
||||
|
||||
private const int CYCLE_SLEEP = 10000;
|
||||
|
||||
/**
|
||||
@ -131,6 +137,7 @@ final class WrapperRunner implements RunnerInterface
|
||||
$parameters = $this->handleLaravelHerd($parameters);
|
||||
|
||||
$parameters[] = $wrapper;
|
||||
$parameters[] = '--test-directory='.TestSuite::getInstance()->testPath;
|
||||
|
||||
$this->parameters = $parameters;
|
||||
$this->codeCoverageFilterRegistry = new CodeCoverageFilterRegistry;
|
||||
@ -225,7 +232,7 @@ final class WrapperRunner implements RunnerInterface
|
||||
$this->printer->printFeedback(
|
||||
$worker->progressFile,
|
||||
$worker->unexpectedOutputFile,
|
||||
$this->teamcityFiles,
|
||||
$worker->teamcityFile ?? null,
|
||||
);
|
||||
$worker->reset();
|
||||
}
|
||||
@ -385,6 +392,8 @@ final class WrapperRunner implements RunnerInterface
|
||||
$testResultSum->numberOfIssuesIgnoredByBaseline(),
|
||||
);
|
||||
|
||||
self::$result = $testResultSum;
|
||||
|
||||
if ($this->options->configuration->cacheResult()) {
|
||||
$resultCacheSum = new DefaultResultCache($this->options->configuration->testResultCacheFile());
|
||||
foreach ($this->resultCacheFiles as $resultCacheFile) {
|
||||
@ -483,15 +492,61 @@ final class WrapperRunner implements RunnerInterface
|
||||
*/
|
||||
private function getTestFiles(SuiteLoader $suiteLoader): array
|
||||
{
|
||||
/** @var array<string, non-empty-string> $files */
|
||||
$files = [
|
||||
...array_values(array_filter(
|
||||
/** @var array<string, null> $files */
|
||||
$files = [];
|
||||
|
||||
foreach (array_filter(
|
||||
$suiteLoader->tests,
|
||||
fn (string $filename): bool => ! str_ends_with($filename, "eval()'d code")
|
||||
)),
|
||||
...TestSuite::getInstance()->tests->getFilenames(),
|
||||
];
|
||||
) as $filename) {
|
||||
$resolved = realpath($filename) ?: $filename;
|
||||
$files[$resolved] = null;
|
||||
}
|
||||
|
||||
return $files; // @phpstan-ignore-line
|
||||
foreach (TestSuite::getInstance()->tests->getFilenames() as $filename) {
|
||||
if ($this->shouldIncludeBootstrappedTestFile($filename)) {
|
||||
$resolved = realpath($filename)
|
||||
?: realpath($this->options->cwd.DIRECTORY_SEPARATOR.$filename)
|
||||
?: $filename;
|
||||
$files[$resolved] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return array_keys($files); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
private function shouldIncludeBootstrappedTestFile(string $filename): bool
|
||||
{
|
||||
if (! $this->options->configuration->hasCliArguments()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$resolvedFilename = realpath($filename);
|
||||
|
||||
if ($resolvedFilename === false) {
|
||||
$resolvedFilename = realpath($this->options->cwd.DIRECTORY_SEPARATOR.$filename);
|
||||
}
|
||||
|
||||
if ($resolvedFilename === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->options->configuration->cliArguments() as $path) {
|
||||
$resolvedPath = realpath($path);
|
||||
|
||||
if ($resolvedPath === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($resolvedFilename === $resolvedPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_dir($resolvedPath) && str_starts_with($resolvedFilename, $resolvedPath.DIRECTORY_SEPARATOR)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,6 +113,16 @@ final class TestRepository
|
||||
$this->testCaseMethodFilters[] = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the class and traits configured for the given directory path.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function getUsesForPath(string $path): array
|
||||
{
|
||||
return $this->uses[$path][0] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the test case factory from the given filename.
|
||||
*/
|
||||
|
||||
@ -19,14 +19,14 @@ final class Closure
|
||||
*/
|
||||
public static function bind(?BaseClosure $closure, ?object $newThis, object|string|null $newScope = 'static'): BaseClosure
|
||||
{
|
||||
if (! $closure instanceof \Closure) {
|
||||
if (! $closure instanceof BaseClosure) {
|
||||
throw ShouldNotHappen::fromMessage('Could not bind null closure.');
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$closure = BaseClosure::bind($closure, $newThis, $newScope);
|
||||
|
||||
if (! $closure instanceof \Closure) {
|
||||
if (! $closure instanceof BaseClosure) {
|
||||
throw ShouldNotHappen::fromMessage('Could not bind closure.');
|
||||
}
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ final class Container
|
||||
*/
|
||||
public static function getInstance(): self
|
||||
{
|
||||
if (! self::$instance instanceof \Pest\Support\Container) {
|
||||
if (! self::$instance instanceof Container) {
|
||||
self::$instance = new self;
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ final class DatasetInfo
|
||||
|
||||
public static function isInsideADatasetsDirectory(string $file): bool
|
||||
{
|
||||
return basename(dirname($file)) === self::DATASETS_DIR_NAME;
|
||||
return in_array(self::DATASETS_DIR_NAME, self::directorySegmentsInsideTestsDirectory($file), true);
|
||||
}
|
||||
|
||||
public static function isADatasetsFile(string $file): bool
|
||||
@ -32,7 +32,23 @@ final class DatasetInfo
|
||||
}
|
||||
|
||||
if (self::isInsideADatasetsDirectory($file)) {
|
||||
return dirname($file, 2);
|
||||
$scope = [];
|
||||
|
||||
foreach (self::directorySegmentsInsideTestsDirectory($file) as $segment) {
|
||||
if ($segment === self::DATASETS_DIR_NAME) {
|
||||
break;
|
||||
}
|
||||
|
||||
$scope[] = $segment;
|
||||
}
|
||||
|
||||
$testsDirectoryPath = self::testsDirectoryPath($file);
|
||||
|
||||
if ($scope === []) {
|
||||
return $testsDirectoryPath;
|
||||
}
|
||||
|
||||
return $testsDirectoryPath.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $scope);
|
||||
}
|
||||
|
||||
if (self::isADatasetsFile($file)) {
|
||||
@ -41,4 +57,45 @@ final class DatasetInfo
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private static function directorySegmentsInsideTestsDirectory(string $file): array
|
||||
{
|
||||
$directory = dirname(self::pathInsideTestsDirectory($file));
|
||||
|
||||
if ($directory === '.' || $directory === DIRECTORY_SEPARATOR) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_filter(
|
||||
explode(DIRECTORY_SEPARATOR, trim($directory, DIRECTORY_SEPARATOR)),
|
||||
static fn (string $segment): bool => $segment !== '',
|
||||
));
|
||||
}
|
||||
|
||||
private static function pathInsideTestsDirectory(string $file): string
|
||||
{
|
||||
$testsDirectory = DIRECTORY_SEPARATOR.trim(testDirectory(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
|
||||
$position = strrpos($file, $testsDirectory);
|
||||
|
||||
if ($position === false) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return substr($file, $position + strlen($testsDirectory));
|
||||
}
|
||||
|
||||
private static function testsDirectoryPath(string $file): string
|
||||
{
|
||||
$testsDirectory = DIRECTORY_SEPARATOR.trim(testDirectory(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
|
||||
$position = strrpos($file, $testsDirectory);
|
||||
|
||||
if ($position === false) {
|
||||
return dirname($file);
|
||||
}
|
||||
|
||||
return substr($file, 0, $position + strlen($testsDirectory) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ final class ExceptionTrace
|
||||
return $closure();
|
||||
} catch (Throwable $throwable) {
|
||||
if (Str::startsWith($message = $throwable->getMessage(), self::UNDEFINED_METHOD)) {
|
||||
// @phpstan-ignore-next-line
|
||||
$class = preg_match('/^Call to undefined method ([^:]+)::/', $message, $matches) === false ? null : $matches[1];
|
||||
|
||||
$message = str_replace(self::UNDEFINED_METHOD, 'Call to undefined method ', $message);
|
||||
|
||||
@ -8,6 +8,7 @@ use Closure;
|
||||
use InvalidArgumentException;
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionFunction;
|
||||
@ -66,7 +67,7 @@ final class Reflection
|
||||
{
|
||||
$test = TestSuite::getInstance()->test;
|
||||
|
||||
if (! $test instanceof \PHPUnit\Framework\TestCase) {
|
||||
if (! $test instanceof TestCase) {
|
||||
return self::bindCallable($callable);
|
||||
}
|
||||
|
||||
@ -221,7 +222,7 @@ final class Reflection
|
||||
{
|
||||
$getProperties = fn (ReflectionClass $reflectionClass): array => array_filter(
|
||||
array_map(
|
||||
fn (ReflectionProperty $property): \ReflectionProperty => $property,
|
||||
fn (ReflectionProperty $property): ReflectionProperty => $property,
|
||||
$reflectionClass->getProperties(),
|
||||
), fn (ReflectionProperty $property): bool => $property->getDeclaringClass()->getName() === $reflectionClass->getName(),
|
||||
);
|
||||
@ -256,7 +257,7 @@ final class Reflection
|
||||
{
|
||||
$getMethods = fn (ReflectionClass $reflectionClass): array => array_filter(
|
||||
array_map(
|
||||
fn (ReflectionMethod $method): \ReflectionMethod => $method,
|
||||
fn (ReflectionMethod $method): ReflectionMethod => $method,
|
||||
$reflectionClass->getMethods($filter),
|
||||
), fn (ReflectionMethod $method): bool => $method->getDeclaringClass()->getName() === $reflectionClass->getName(),
|
||||
);
|
||||
|
||||
@ -11,6 +11,10 @@ use PHPUnit\Event\Code\TestDoxBuilder;
|
||||
use PHPUnit\Event\Code\TestMethod;
|
||||
use PHPUnit\Event\Code\ThrowableBuilder;
|
||||
use PHPUnit\Event\Test\Errored;
|
||||
use PHPUnit\Event\Test\PhpunitDeprecationTriggered;
|
||||
use PHPUnit\Event\Test\PhpunitErrorTriggered;
|
||||
use PHPUnit\Event\Test\PhpunitNoticeTriggered;
|
||||
use PHPUnit\Event\Test\PhpunitWarningTriggered;
|
||||
use PHPUnit\Event\TestData\TestDataCollection;
|
||||
use PHPUnit\Framework\SkippedWithMessageException;
|
||||
use PHPUnit\Metadata\MetadataCollection;
|
||||
@ -43,6 +47,8 @@ final class StateGenerator
|
||||
));
|
||||
}
|
||||
|
||||
$this->addTriggeredPhpunitEvents($state, $testResult->testTriggeredPhpunitErrorEvents(), TestResult::FAIL);
|
||||
|
||||
foreach ($testResult->testMarkedIncompleteEvents() as $testResultEvent) {
|
||||
$state->add(TestResult::fromPestParallelTestCase(
|
||||
$testResultEvent->test(),
|
||||
@ -99,6 +105,8 @@ final class StateGenerator
|
||||
}
|
||||
}
|
||||
|
||||
$this->addTriggeredPhpunitEvents($state, $testResult->testTriggeredPhpunitDeprecationEvents(), TestResult::DEPRECATED);
|
||||
|
||||
foreach ($testResult->notices() as $testResultEvent) {
|
||||
foreach ($testResultEvent->triggeringTests() as $triggeringTest) {
|
||||
['test' => $test] = $triggeringTest;
|
||||
@ -123,6 +131,8 @@ final class StateGenerator
|
||||
}
|
||||
}
|
||||
|
||||
$this->addTriggeredPhpunitEvents($state, $testResult->testTriggeredPhpunitNoticeEvents(), TestResult::NOTICE);
|
||||
|
||||
foreach ($testResult->warnings() as $testResultEvent) {
|
||||
foreach ($testResultEvent->triggeringTests() as $triggeringTest) {
|
||||
['test' => $test] = $triggeringTest;
|
||||
@ -135,6 +145,8 @@ final class StateGenerator
|
||||
}
|
||||
}
|
||||
|
||||
$this->addTriggeredPhpunitEvents($state, $testResult->testTriggeredPhpunitWarningEvents(), TestResult::WARN);
|
||||
|
||||
foreach ($testResult->phpWarnings() as $testResultEvent) {
|
||||
foreach ($testResultEvent->triggeringTests() as $triggeringTest) {
|
||||
['test' => $test] = $triggeringTest;
|
||||
@ -165,4 +177,24 @@ final class StateGenerator
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, list<PhpunitDeprecationTriggered|PhpunitErrorTriggered|PhpunitNoticeTriggered|PhpunitWarningTriggered>> $testResultEvents
|
||||
*/
|
||||
private function addTriggeredPhpunitEvents(State $state, array $testResultEvents, string $type): void
|
||||
{
|
||||
foreach ($testResultEvents as $events) {
|
||||
foreach ($events as $event) {
|
||||
if (! $event->test()->isTestMethod()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$state->add(TestResult::fromPestParallelTestCase(
|
||||
$event->test(),
|
||||
$type,
|
||||
ThrowableBuilder::from(new TestOutcome($event->message()))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Test Case
|
||||
@ -7,12 +10,12 @@
|
||||
|
|
||||
| The closure you provide to your test functions is always bound to a specific PHPUnit test
|
||||
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
|
||||
| need to change it using the "pest()" function to bind a different classes or traits.
|
||||
| need to change it using the "pest()" function to bind different classes or traits.
|
||||
|
|
||||
*/
|
||||
|
||||
pest()->extend(Tests\TestCase::class)
|
||||
// ->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
|
||||
pest()->extend(TestCase::class)
|
||||
// ->use(RefreshDatabase::class)
|
||||
->in('Feature');
|
||||
|
||||
/*
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Tests\TestCase;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Test Case
|
||||
@ -7,11 +9,11 @@
|
||||
|
|
||||
| The closure you provide to your test functions is always bound to a specific PHPUnit test
|
||||
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
|
||||
| need to change it using the "pest()" function to bind a different classes or traits.
|
||||
| need to change it using the "pest()" function to bind different classes or traits.
|
||||
|
|
||||
*/
|
||||
|
||||
pest()->extend(Tests\TestCase::class)->in('Feature');
|
||||
pest()->extend(TestCase::class)->in('Feature');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
Pest Testing Framework 4.3.2.
|
||||
Pest Testing Framework 4.4.4.
|
||||
|
||||
USAGE: pest <file> [options]
|
||||
|
||||
@ -92,7 +92,7 @@
|
||||
--random-order-seed [N] Use the specified random seed when running tests in random order
|
||||
|
||||
REPORTING OPTIONS:
|
||||
--colors [flag] ......... Use colors in output ("never", "auto" or "always")
|
||||
--colors=[flag] ......... Use colors in output ("never", "auto" or "always")
|
||||
--columns [n] ................. Number of columns to use for progress output
|
||||
--columns max ............ Use maximum number of columns for progress output
|
||||
--stderr ................................. Write to STDERR instead of STDOUT
|
||||
@ -147,6 +147,9 @@
|
||||
--disable-coverage-ignore ...... Disable metadata for ignoring code coverage
|
||||
--no-coverage Ignore code coverage reporting configured in the XML configuration file
|
||||
|
||||
AI OPTIONS:
|
||||
--ai ..... Run a code snippet as a fully scaffolded test for AI verification
|
||||
|
||||
MUTATION TESTING OPTIONS:
|
||||
--mutate .... Runs mutation testing, to understand the quality of your tests
|
||||
--mutate --parallel ...................... Runs mutation testing in parallel
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
|
||||
Pest Testing Framework 4.3.2.
|
||||
Pest Testing Framework 4.4.4.
|
||||
|
||||
|
||||
@ -448,6 +448,10 @@
|
||||
✓ failures with custom message
|
||||
✓ not failures
|
||||
|
||||
PASS Tests\Features\Expect\toBeCasedCorrectly
|
||||
✓ pass
|
||||
✓ failure
|
||||
|
||||
PASS Tests\Features\Expect\toBeDigits
|
||||
✓ pass
|
||||
✓ failures
|
||||
@ -1034,6 +1038,10 @@
|
||||
✓ pass
|
||||
✓ failures
|
||||
✓ not failures
|
||||
✓ trait inheritance - direct usage
|
||||
✓ trait inheritance - inherited usage
|
||||
✓ trait inheritance - negative case
|
||||
✓ nested trait inheritance
|
||||
|
||||
PASS Tests\Features\Expect\unless
|
||||
✓ it pass
|
||||
@ -1490,6 +1498,10 @@
|
||||
PASS Tests\Fixtures\ExampleTest
|
||||
✓ it example 2
|
||||
|
||||
PASS Tests\Fixtures\ParallelNestedDatasets\TestFileWithNestedDataset
|
||||
✓ loads nested dataset with ('alice')
|
||||
✓ loads nested dataset with ('bob')
|
||||
|
||||
WARN Tests\Fixtures\UnexpectedOutput
|
||||
- output
|
||||
|
||||
@ -1640,9 +1652,14 @@
|
||||
✓ it cannot resolve a parameter without type
|
||||
|
||||
PASS Tests\Unit\Support\DatasetInfo
|
||||
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Datase…rs.php', true)
|
||||
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/Datasets/project/tes…rs.php', true) #1
|
||||
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/Datasets/project/tes…rs.php', true) #2
|
||||
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Datase…rs.php', true) #1
|
||||
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Datase…rs.php', true) #2
|
||||
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Datasets.php', false)
|
||||
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur…rs.php', true)
|
||||
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur…rs.php', true) #1
|
||||
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur…rs.php', true) #2
|
||||
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur…rs.php', true) #3
|
||||
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur…rs.php', false)
|
||||
✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur…ts.php', false)
|
||||
✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Datase…rs.php', false)
|
||||
@ -1650,12 +1667,18 @@
|
||||
✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Featur…rs.php', false) #1
|
||||
✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Featur…rs.php', false) #2
|
||||
✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Featur…ts.php', true)
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Datase…rs.php', '/var/www/project/tests')
|
||||
✓ it computes the dataset scope with ('/var/www/Datasets/project/tes…rs.php', '/var/www/Datasets/project/tests')
|
||||
✓ it computes the dataset scope with ('/var/www/Datasets/project/tes…rs.php', '/var/www/Datasets/project/tes…atures')
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Datase…rs.php', '/var/www/project/tests') #1
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Datase…rs.php', '/var/www/project/tests') #2
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Datasets.php', '/var/www/project/tests')
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Features')
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Features') #1
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Features') #2
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Featur…rs.php') #1
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Featur…ts.php', '/var/www/project/tests/Features')
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Featur…ollers')
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Featur…ollers') #1
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Featur…ollers') #2
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Features') #3
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Featur…rs.php', '/var/www/project/tests/Featur…rs.php') #2
|
||||
✓ it computes the dataset scope with ('/var/www/project/tests/Featur…ts.php', '/var/www/project/tests/Featur…ollers')
|
||||
|
||||
@ -1756,6 +1779,10 @@
|
||||
PASS Tests\Visual\Parallel
|
||||
✓ parallel
|
||||
✓ a parallel test can extend another test with same name
|
||||
✓ parallel reports invalid datasets as failures
|
||||
|
||||
PASS Tests\Visual\ParallelNestedDatasets
|
||||
✓ parallel loads nested datasets from nested directories
|
||||
|
||||
PASS Tests\Visual\SingleTestOrDirectory
|
||||
✓ allows to run a single test
|
||||
@ -1786,4 +1813,5 @@
|
||||
✓ pass with dataset with ('my-datas-set-value')
|
||||
✓ within describe → pass with dataset with ('my-datas-set-value')
|
||||
|
||||
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 39 todos, 35 skipped, 1190 passed (2818 assertions)
|
||||
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 39 todos, 35 skipped, 1209 passed (2842 assertions)
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
test('missing dataset', function (string $value) {
|
||||
expect($value)->toBe('x');
|
||||
})->with('missing.dataset');
|
||||
3
tests/.tests/ParallelInvalidDataset/PassingTest.php
Normal file
3
tests/.tests/ParallelInvalidDataset/PassingTest.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
it('passes')->assertTrue(true);
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Pest\Plugin;
|
||||
|
||||
trait PluginTrait
|
||||
{
|
||||
public function assertPluginTraitGotRegistered(): void
|
||||
@ -16,8 +18,8 @@ trait SecondPluginTrait
|
||||
}
|
||||
}
|
||||
|
||||
Pest\Plugin::uses(PluginTrait::class);
|
||||
Pest\Plugin::uses(SecondPluginTrait::class);
|
||||
Plugin::uses(PluginTrait::class);
|
||||
Plugin::uses(SecondPluginTrait::class);
|
||||
|
||||
function _assertThat()
|
||||
{
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
$foo = new \stdClass;
|
||||
$foo = new stdClass;
|
||||
$foo->bar = 0;
|
||||
|
||||
beforeAll(function () use ($foo) {
|
||||
|
||||
@ -17,7 +17,7 @@ it('adds coverage if --coverage exist', function () {
|
||||
$arguments = $plugin->handleArguments(['--coverage']);
|
||||
expect($arguments)->toEqual(['--coverage-php', Coverage::getPath()])
|
||||
->and($plugin->coverage)->toBeTrue();
|
||||
})->skip(! \Pest\Support\Coverage::isAvailable() || ! function_exists('xdebug_info') || ! in_array('coverage', xdebug_info('mode'), true), 'Coverage is not available');
|
||||
})->skip(! Coverage::isAvailable() || ! function_exists('xdebug_info') || ! in_array('coverage', xdebug_info('mode'), true), 'Coverage is not available');
|
||||
|
||||
it('adds coverage if --min exist', function () {
|
||||
$plugin = new CoveragePlugin(new ConsoleOutput);
|
||||
|
||||
12
tests/Features/Expect/toBeCasedCorrectly.php
Normal file
12
tests/Features/Expect/toBeCasedCorrectly.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Pest\Arch\Exceptions\ArchExpectationFailedException;
|
||||
|
||||
test('pass')
|
||||
->expect('Tests\Fixtures\Arch\ToBeCasedCorrectly\CorrectCasing')
|
||||
->toBeCasedCorrectly();
|
||||
|
||||
test('failure')
|
||||
->expect('Tests\Fixtures\Arch\ToBeCasedCorrectly\IncorrectCasing')
|
||||
->toBeCasedCorrectly()
|
||||
->throws(ArchExpectationFailedException::class);
|
||||
@ -14,3 +14,19 @@ test('failures', function () {
|
||||
test('not failures', function () {
|
||||
expect('Pest\Expectations\HigherOrderExpectation')->not->toUseTrait('Pest\Concerns\Retrievable');
|
||||
})->throws(ArchExpectationFailedException::class);
|
||||
|
||||
test('trait inheritance - direct usage', function () {
|
||||
expect('Tests\Fixtures\Arch\ToUseTrait\HasTrait\ParentClassWithTrait')->toUseTrait('Tests\Fixtures\Arch\ToUseTrait\HasTrait\TestTraitForInheritance');
|
||||
});
|
||||
|
||||
test('trait inheritance - inherited usage', function () {
|
||||
expect('Tests\Fixtures\Arch\ToUseTrait\HasInheritedTrait\ChildClassExtendingParent')->toUseTrait('Tests\Fixtures\Arch\ToUseTrait\HasTrait\TestTraitForInheritance');
|
||||
});
|
||||
|
||||
test('trait inheritance - negative case', function () {
|
||||
expect('Tests\Fixtures\Arch\ToUseTrait\HasInheritedTrait\ChildClassExtendingParent')->not->toUseTrait('NonExistentTrait');
|
||||
});
|
||||
|
||||
test('nested trait inheritance', function () {
|
||||
expect('Tests\Fixtures\Arch\ToUseTrait\HasInheritedTrait\ChildClassExtendingParent')->toUseTrait('Tests\Fixtures\Arch\ToUseTrait\HasNestedTrait\NestedTrait');
|
||||
});
|
||||
|
||||
@ -39,7 +39,7 @@ it('allows to call underlying protected/private methods', function () {
|
||||
|
||||
it('throws error if method do not exist', function () {
|
||||
test()->foo();
|
||||
})->throws(\ReflectionException::class, 'Call to undefined method PHPUnit\Framework\TestCase::foo()');
|
||||
})->throws(ReflectionException::class, 'Call to undefined method PHPUnit\Framework\TestCase::foo()');
|
||||
|
||||
it('can forward unexpected calls to any global function')->_assertThat();
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Fixtures\Arch\ToBeCasedCorrectly\CorrectCasing;
|
||||
|
||||
class CorrectCasing {}
|
||||
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Fixtures\Arch\ToBeCasedCorrectly\IncorrectCasing;
|
||||
|
||||
class IncorrectCasing {}
|
||||
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Fixtures\Arch\ToBeCasedCorrectly\IncorrectDirectoryCasing;
|
||||
|
||||
class CorrectCasing {}
|
||||
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Fixtures\Arch\ToUseTrait\HasInheritedTrait;
|
||||
|
||||
use Tests\Fixtures\Arch\ToUseTrait\HasTrait\ParentClassWithTrait;
|
||||
|
||||
class ChildClassExtendingParent extends ParentClassWithTrait {}
|
||||
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Fixtures\Arch\ToUseTrait\HasNestedTrait;
|
||||
|
||||
trait NestedTrait
|
||||
{
|
||||
public function nestedMethod()
|
||||
{
|
||||
return 'nested';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Fixtures\Arch\ToUseTrait\HasTrait;
|
||||
|
||||
class ParentClassWithTrait
|
||||
{
|
||||
use TestTraitForInheritance;
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Fixtures\Arch\ToUseTrait\HasTrait;
|
||||
|
||||
use Tests\Fixtures\Arch\ToUseTrait\HasNestedTrait\NestedTrait;
|
||||
|
||||
trait TestTraitForInheritance
|
||||
{
|
||||
use NestedTrait;
|
||||
|
||||
public function testMethod()
|
||||
{
|
||||
return 'test';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
dataset('nested.users', [
|
||||
['alice'],
|
||||
['bob'],
|
||||
]);
|
||||
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
test('loads nested dataset', function (string $name) {
|
||||
expect($name)->not->toBeEmpty();
|
||||
})->with('nested.users');
|
||||
@ -1,7 +1,9 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @return \PHPUnit\Framework\TestCase
|
||||
* @return TestCase
|
||||
*/
|
||||
function myAssertTrue($value)
|
||||
{
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class MyCustomClassTest extends PHPUnit\Framework\TestCase
|
||||
class MyCustomClassTest extends TestCase
|
||||
{
|
||||
public function assertTrueIsTrue()
|
||||
{
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
pest()->use(Tests\CustomTestCase\CustomTestCase::class)->in(__DIR__);
|
||||
use Tests\CustomTestCase\CustomTestCase;
|
||||
|
||||
pest()->use(CustomTestCase::class)->in(__DIR__);
|
||||
|
||||
test('closure was bound to CustomTestCase', function () {
|
||||
$this->assertCustomTrue();
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
trait MyCustomTrait
|
||||
{
|
||||
public function assertFalseIsFalse()
|
||||
@ -8,7 +10,7 @@ trait MyCustomTrait
|
||||
}
|
||||
}
|
||||
|
||||
abstract class MyCustomClass extends PHPUnit\Framework\TestCase
|
||||
abstract class MyCustomClass extends TestCase
|
||||
{
|
||||
public function assertTrueIsTrue()
|
||||
{
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
<?php
|
||||
|
||||
use Pest\Configuration\Printer;
|
||||
|
||||
it('creates a printer instance', function () {
|
||||
$theme = pest()->printer();
|
||||
|
||||
expect($theme)->toBeInstanceOf(Pest\Configuration\Printer::class);
|
||||
expect($theme)->toBeInstanceOf(Printer::class);
|
||||
});
|
||||
|
||||
@ -5,9 +5,14 @@ use Pest\Support\DatasetInfo;
|
||||
it('can check if dataset is defined inside a Datasets directory', function (string $file, bool $inside) {
|
||||
expect(DatasetInfo::isInsideADatasetsDirectory($file))->toBe($inside);
|
||||
})->with([
|
||||
['file' => '/var/www/Datasets/project/tests/Datasets/Nested/Numbers.php', 'inside' => true],
|
||||
['file' => '/var/www/Datasets/project/tests/Features/Datasets/Nested/Numbers.php', 'inside' => true],
|
||||
['file' => '/var/www/project/tests/Datasets/Numbers.php', 'inside' => true],
|
||||
['file' => '/var/www/project/tests/Datasets/Nested/Numbers.php', 'inside' => true],
|
||||
['file' => '/var/www/project/tests/Datasets.php', 'inside' => false],
|
||||
['file' => '/var/www/project/tests/Features/Datasets/Numbers.php', 'inside' => true],
|
||||
['file' => '/var/www/project/tests/Features/Datasets/Nested/Numbers.php', 'inside' => true],
|
||||
['file' => '/var/www/project/tests/Features/Datasets/Nested/Datasets/Numbers.php', 'inside' => true],
|
||||
['file' => '/var/www/project/tests/Features/Numbers.php', 'inside' => false],
|
||||
['file' => '/var/www/project/tests/Features/Datasets.php', 'inside' => false],
|
||||
]);
|
||||
@ -25,12 +30,18 @@ it('can check if dataset is defined inside a Datasets.php file', function (strin
|
||||
it('computes the dataset scope', function (string $file, string $scope) {
|
||||
expect(DatasetInfo::scope($file))->toBe($scope);
|
||||
})->with([
|
||||
['file' => '/var/www/Datasets/project/tests/Datasets/Nested/Numbers.php', 'scope' => '/var/www/Datasets/project/tests'],
|
||||
['file' => '/var/www/Datasets/project/tests/Features/Datasets/Nested/Numbers.php', 'scope' => '/var/www/Datasets/project/tests/Features'],
|
||||
['file' => '/var/www/project/tests/Datasets/Numbers.php', 'scope' => '/var/www/project/tests'],
|
||||
['file' => '/var/www/project/tests/Datasets/Nested/Numbers.php', 'scope' => '/var/www/project/tests'],
|
||||
['file' => '/var/www/project/tests/Datasets.php', 'scope' => '/var/www/project/tests'],
|
||||
['file' => '/var/www/project/tests/Features/Datasets/Numbers.php', 'scope' => '/var/www/project/tests/Features'],
|
||||
['file' => '/var/www/project/tests/Features/Datasets/Nested/Numbers.php', 'scope' => '/var/www/project/tests/Features'],
|
||||
['file' => '/var/www/project/tests/Features/Numbers.php', 'scope' => '/var/www/project/tests/Features/Numbers.php'],
|
||||
['file' => '/var/www/project/tests/Features/Datasets.php', 'scope' => '/var/www/project/tests/Features'],
|
||||
['file' => '/var/www/project/tests/Features/Controllers/Datasets/Numbers.php', 'scope' => '/var/www/project/tests/Features/Controllers'],
|
||||
['file' => '/var/www/project/tests/Features/Controllers/Datasets/Nested/Numbers.php', 'scope' => '/var/www/project/tests/Features/Controllers'],
|
||||
['file' => '/var/www/project/tests/Features/Datasets/Nested/Datasets/Numbers.php', 'scope' => '/var/www/project/tests/Features'],
|
||||
['file' => '/var/www/project/tests/Features/Controllers/Numbers.php', 'scope' => '/var/www/project/tests/Features/Controllers/Numbers.php'],
|
||||
['file' => '/var/www/project/tests/Features/Controllers/Datasets.php', 'scope' => '/var/www/project/tests/Features/Controllers'],
|
||||
]);
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
test('collision', function (array $arguments) {
|
||||
$output = function () use ($arguments) {
|
||||
$process = (new Symfony\Component\Process\Process(
|
||||
$process = (new Process(
|
||||
array_merge(['php', 'bin/pest', 'tests/Fixtures/CollisionTest.php'], $arguments),
|
||||
null,
|
||||
['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true', 'COLLISION_TEST' => true]
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
test('visual snapshot of help command output', function () {
|
||||
$output = function () {
|
||||
$process = (new Symfony\Component\Process\Process(['php', 'bin/pest', '--help'], null, ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true']));
|
||||
$process = (new Process(['php', 'bin/pest', '--help'], null, ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true']));
|
||||
|
||||
$process->run();
|
||||
|
||||
|
||||
@ -16,10 +16,17 @@ $run = function () {
|
||||
|
||||
test('parallel', function () use ($run) {
|
||||
expect($run('--exclude-group=integration'))
|
||||
->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 39 todos, 26 skipped, 1177 passed (2789 assertions)')
|
||||
->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 3 notices, 39 todos, 26 skipped, 1196 passed (2809 assertions)')
|
||||
->toContain('Parallel: 3 processes');
|
||||
})->skipOnWindows();
|
||||
|
||||
test('a parallel test can extend another test with same name', function () use ($run) {
|
||||
expect($run('tests/Fixtures/Inheritance'))->toContain('Tests: 1 skipped, 2 passed (2 assertions)');
|
||||
});
|
||||
expect($run('tests/Fixtures/Inheritance'))->toContain('Tests: 1 skipped, 1 passed (1 assertions)');
|
||||
})->skipOnWindows();
|
||||
|
||||
test('parallel reports invalid datasets as failures', function () use ($run) {
|
||||
expect($run('tests/.tests/ParallelInvalidDataset'))
|
||||
->toContain("A dataset with the name `missing.dataset` does not exist. You can create it using `dataset('missing.dataset', ['a', 'b']);`.")
|
||||
->toContain('Tests: 1 failed, 1 passed (1 assertions)')
|
||||
->toContain('Parallel: 3 processes');
|
||||
})->skipOnWindows();
|
||||
|
||||
40
tests/Visual/ParallelNestedDatasets.php
Normal file
40
tests/Visual/ParallelNestedDatasets.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
$run = function (bool $parallel = false): array {
|
||||
$command = [
|
||||
'php',
|
||||
'bin/pest',
|
||||
'tests/Fixtures/ParallelNestedDatasets/TestFileWithNestedDataset.php',
|
||||
'--colors=never',
|
||||
];
|
||||
|
||||
if ($parallel) {
|
||||
$command[] = '--parallel';
|
||||
$command[] = '--processes=2';
|
||||
}
|
||||
|
||||
$process = new Process($command, dirname(__DIR__, 2),
|
||||
['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'],
|
||||
);
|
||||
|
||||
$process->run();
|
||||
|
||||
return [
|
||||
'exitCode' => $process->getExitCode(),
|
||||
'output' => removeAnsiEscapeSequences($process->getOutput().$process->getErrorOutput()),
|
||||
];
|
||||
};
|
||||
|
||||
test('parallel loads nested datasets from nested directories', function () use ($run) {
|
||||
$serial = $run();
|
||||
$parallel = $run(true);
|
||||
|
||||
expect($serial['exitCode'])->toBe(0)
|
||||
->and($parallel['exitCode'])->toBe(0)
|
||||
->and($serial['output'])->toContain('Tests: 2 passed (2 assertions)')
|
||||
->and($parallel['output'])->toContain('Tests: 2 passed (2 assertions)')
|
||||
->and($parallel['output'])->toContain('Parallel: 2 processes')
|
||||
->and($parallel['output'])->not->toContain('No tests found.');
|
||||
})->skipOnWindows();
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
test('visual snapshot of test suite on success', function () {
|
||||
$testsPath = dirname(__DIR__);
|
||||
$snapshot = implode(DIRECTORY_SEPARATOR, [
|
||||
@ -9,8 +11,8 @@ test('visual snapshot of test suite on success', function () {
|
||||
]);
|
||||
|
||||
$output = function () use ($testsPath) {
|
||||
$process = (new Symfony\Component\Process\Process(
|
||||
['php', 'bin/pest'],
|
||||
$process = (new Process(
|
||||
['php', '-d', 'memory_limit=256M', 'bin/pest'],
|
||||
dirname($testsPath),
|
||||
['EXCLUDE' => 'integration', '--exclude-group' => 'integration', 'REBUILD_SNAPSHOTS' => false, 'PARATEST' => 0, 'COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'],
|
||||
));
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
function normalize_windows_os_output(string $text): string
|
||||
{
|
||||
$text = str_replace('\r', '', $text);
|
||||
@ -17,7 +19,7 @@ test('visual snapshot of team city', function (string $testFile) {
|
||||
]);
|
||||
|
||||
$output = function () use ($testsPath) {
|
||||
$process = (new Symfony\Component\Process\Process(
|
||||
$process = (new Process(
|
||||
['php', 'bin/pest', '--teamcity', $testsPath],
|
||||
dirname(__DIR__, levels: 2),
|
||||
[
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
test('visual snapshot of help command output', function () {
|
||||
$output = function () {
|
||||
$process = (new Symfony\Component\Process\Process(['php', 'bin/pest', '--version'], null, ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true']));
|
||||
$process = (new Process(['php', 'bin/pest', '--version'], null, ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true']));
|
||||
|
||||
$process->run();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user