Compare commits

...

15 Commits

Author SHA1 Message Date
09eff785c4 release: v3.0.7 2024-09-19 12:29:38 +01:00
22cc7805d7 chore: bumps dependencies 2024-09-19 12:29:38 +01:00
689da4ed4e Merge pull request #1254 from pestphp/bugfix/jira-url
fix: update assignee URL for Jira
2024-09-18 21:48:09 +01:00
2f15861b0d fix: update assignee URL for Jira 2024-09-16 12:18:21 +01:00
0d50d35b5e release: v3.0.6 2024-09-11 18:59:43 +01:00
ce61ced8e1 Merge pull request #1237 from smirok/teamcity-fix-for-tests-with-dataset
fix: unify the `locationHint` prefix and prettify both `locationHint` and `name` parameters for testing with datasets
2024-09-11 18:51:04 +01:00
7227d24611 fix: unify the locationHint prefix and prettify both locationHint and name parameters for testing with datasets 2024-09-11 16:42:06 +02:00
45f16484d5 Merge pull request #1235 from pestphp/3.x_herd_fix
Fixes parallel mutation testing when using Laravel Herd
2024-09-11 15:13:49 +01:00
b16e8650da Fixes parallel mutation testing when using Laravel Herd. 2024-09-11 15:11:47 +01:00
c2f30e0148 Fixes parallel mutation testing when using Laravel Herd. 2024-09-11 15:04:44 +01:00
47ce45de56 release: v3.0.4 2024-09-11 00:48:29 +01:00
32881774d2 fix: global afterEach being called twice 2024-09-11 00:40:41 +01:00
ea72461f1b release: v3.0.3 2024-09-10 22:29:09 +01:00
49f15521e0 fix: printer method name 2024-09-10 22:29:01 +01:00
95c5394b66 Bumps dependencies 2024-09-10 16:59:38 +01:00
23 changed files with 192 additions and 50 deletions

View File

@ -23,11 +23,11 @@
"nunomaduro/termwind": "^2.1.0", "nunomaduro/termwind": "^2.1.0",
"pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin": "^3.0.0",
"pestphp/pest-plugin-arch": "^3.0.0", "pestphp/pest-plugin-arch": "^3.0.0",
"pestphp/pest-plugin-mutate": "^3.0.2", "pestphp/pest-plugin-mutate": "^3.0.3",
"phpunit/phpunit": "^11.3.4" "phpunit/phpunit": "^11.3.6"
}, },
"conflict": { "conflict": {
"phpunit/phpunit": ">11.3.4", "phpunit/phpunit": ">11.3.6",
"sebastian/exporter": "<6.0.0", "sebastian/exporter": "<6.0.0",
"webmozart/assert": "<1.11.0" "webmozart/assert": "<1.11.0"
}, },

View File

@ -25,7 +25,7 @@ final class BootOverrides implements Bootstrapper
'8abdad6413329c6fe0d7d44a8b9926e390af32c0b3123f3720bb9c5bbc6fbb7e' => 'TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php', '8abdad6413329c6fe0d7d44a8b9926e390af32c0b3123f3720bb9c5bbc6fbb7e' => 'TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php',
'43883b7e5811886cf3731c8ed6304d5a77078d9731e1e505abc2da36bde19f3e' => 'TextUI/TestSuiteFilterProcessor.php', '43883b7e5811886cf3731c8ed6304d5a77078d9731e1e505abc2da36bde19f3e' => 'TextUI/TestSuiteFilterProcessor.php',
'357d5cd7007f8559b26e1b8cdf43bb6fb15b51b79db981779da6f31b7ec39dad' => 'Event/Value/ThrowableBuilder.php', '357d5cd7007f8559b26e1b8cdf43bb6fb15b51b79db981779da6f31b7ec39dad' => 'Event/Value/ThrowableBuilder.php',
'01974a686eba69b5fbb87a904d936eae2176e39567616898c5b758db71d87a22' => 'Logging/JUnit/JunitXmlLogger.php', '676273f1fe483877cf2d95c5aedbf9ae5d6a8e2f4c12d6ce716df6591e6db023' => 'Logging/JUnit/JunitXmlLogger.php',
]; ];
/** /**

View File

@ -116,7 +116,7 @@ trait Testable
self::$__latestIssues = $method->issues; self::$__latestIssues = $method->issues;
self::$__latestPrs = $method->prs; self::$__latestPrs = $method->prs;
$this->__describing = $method->describing; $this->__describing = $method->describing;
$this->__test = $method->getClosure($this); $this->__test = $method->getClosure();
} }
} }
@ -240,6 +240,8 @@ trait Testable
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name()); $method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
$method->setUp($this);
$description = $method->description; $description = $method->description;
if ($this->dataName()) { if ($this->dataName()) {
$description = str_contains((string) $description, ':dataset') $description = str_contains((string) $description, ':dataset')
@ -298,6 +300,9 @@ trait Testable
parent::tearDown(); parent::tearDown();
TestSuite::getInstance()->test = null; TestSuite::getInstance()->test = null;
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
$method->tearDown($this);
} }
} }
@ -392,11 +397,12 @@ trait Testable
fn (ReflectionParameter $reflectionParameter): string => $reflectionParameter->getName(), fn (ReflectionParameter $reflectionParameter): string => $reflectionParameter->getName(),
array_filter($testReflection->getParameters(), fn (ReflectionParameter $reflectionParameter): bool => ! $reflectionParameter->isOptional()), array_filter($testReflection->getParameters(), fn (ReflectionParameter $reflectionParameter): bool => ! $reflectionParameter->isOptional()),
); );
if (array_diff($testParameterNames, $datasetParameterNames) === []) { if (array_diff($testParameterNames, $datasetParameterNames) === []) {
return; return;
} }
if (isset($testParameterNames[0])
&& $suppliedParametersCount >= $requiredParametersCount) { if (isset($testParameterNames[0]) && $suppliedParametersCount >= $requiredParametersCount) {
return; return;
} }

View File

@ -79,11 +79,11 @@ final readonly class Configuration
} }
/** /**
* Gets the theme configuration. * Gets the printer configuration.
*/ */
public function theme(): Configuration\Theme public function printer(): Configuration\Printer
{ {
return new Configuration\Theme; return new Configuration\Printer;
} }
/** /**

View File

@ -9,7 +9,7 @@ use NunoMaduro\Collision\Adapters\Phpunit\Printers\DefaultPrinter;
/** /**
* @internal * @internal
*/ */
final readonly class Theme final readonly class Printer
{ {
/** /**
* Sets the theme to compact. * Sets the theme to compact.

View File

@ -89,7 +89,7 @@ final class Project
{ {
$this->issues = "https://{$namespace}.atlassian.net/browse/{$project}-%s"; $this->issues = "https://{$namespace}.atlassian.net/browse/{$project}-%s";
$this->assignees = "https://{$namespace}.atlassian.net/secure/ViewProfile?name=%s"; $this->assignees = "https://{$namespace}.atlassian.net/secure/ViewProfile.jspa?name=%s";
return $this; return $this;
} }

View File

@ -118,9 +118,9 @@ final class TestCaseMethodFactory
} }
/** /**
* Creates the test's closure. * Sets the test's hooks, and runs any proxy to the test case.
*/ */
public function getClosure(TestCase $concrete): Closure public function setUp(TestCase $concrete): void
{ {
$concrete::flush(); // @phpstan-ignore-line $concrete::flush(); // @phpstan-ignore-line
@ -128,14 +128,29 @@ final class TestCaseMethodFactory
throw ShouldNotHappen::fromMessage('Description can not be empty.'); throw ShouldNotHappen::fromMessage('Description can not be empty.');
} }
$closure = $this->closure;
$testCase = TestSuite::getInstance()->tests->get($this->filename); $testCase = TestSuite::getInstance()->tests->get($this->filename);
assert($testCase instanceof TestCaseFactory); assert($testCase instanceof TestCaseFactory);
$testCase->factoryProxies->proxy($concrete); $testCase->factoryProxies->proxy($concrete);
$this->factoryProxies->proxy($concrete); $this->factoryProxies->proxy($concrete);
}
/**
* Flushes the test case.
*/
public function tearDown(TestCase $concrete): void
{
$concrete::flush(); // @phpstan-ignore-line
}
/**
* Creates the test's closure.
*/
public function getClosure(): Closure
{
$closure = $this->closure;
$testCase = TestSuite::getInstance()->tests->get($this->filename);
assert($testCase instanceof TestCaseFactory);
$method = $this; $method = $this;
return function (...$arguments) use ($testCase, $method, $closure): mixed { // @phpstan-ignore-line return function (...$arguments) use ($testCase, $method, $closure): mixed { // @phpstan-ignore-line
@ -209,10 +224,8 @@ final class TestCaseMethodFactory
$attributesCode $attributesCode
public function $methodName(...\$arguments) public function $methodName(...\$arguments)
{ {
\$test = \Pest\TestSuite::getInstance()->tests->get(self::\$__filename)->getMethod(\$this->name())->getClosure(\$this);
return \$this->__runTest( return \$this->__runTest(
\$test, \$this->__test,
...\$arguments, ...\$arguments,
); );
} }

View File

@ -18,6 +18,7 @@ use PHPUnit\Event\Test\Failed;
use PHPUnit\Event\Test\MarkedIncomplete; use PHPUnit\Event\Test\MarkedIncomplete;
use PHPUnit\Event\Test\Skipped; use PHPUnit\Event\Test\Skipped;
use PHPUnit\Event\TestSuite\TestSuite; use PHPUnit\Event\TestSuite\TestSuite;
use PHPUnit\Event\TestSuite\TestSuiteForTestMethodWithDataProvider;
use PHPUnit\Framework\Exception as FrameworkException; use PHPUnit\Framework\Exception as FrameworkException;
use PHPUnit\TestRunner\TestResult\TestResult as PhpUnitTestResult; use PHPUnit\TestRunner\TestResult\TestResult as PhpUnitTestResult;
@ -147,6 +148,13 @@ final readonly class Converter
*/ */
public function getTestSuiteName(TestSuite $testSuite): string public function getTestSuiteName(TestSuite $testSuite): string
{ {
if ($testSuite instanceof TestSuiteForTestMethodWithDataProvider) {
$firstTest = $this->getFirstTest($testSuite);
if ($firstTest != null) {
return $this->getTestMethodNameWithoutDatasetSuffix($firstTest);
}
}
$name = $testSuite->name(); $name = $testSuite->name();
if (! str_starts_with($name, self::PREFIX)) { if (! str_starts_with($name, self::PREFIX)) {
@ -168,6 +176,35 @@ final readonly class Converter
* Gets the test suite location. * Gets the test suite location.
*/ */
public function getTestSuiteLocation(TestSuite $testSuite): ?string public function getTestSuiteLocation(TestSuite $testSuite): ?string
{
$firstTest = $this->getFirstTest($testSuite);
if ($firstTest == null) {
return null;
}
$path = $firstTest->testDox()->prettifiedClassName();
$classRelativePath = $this->toRelativePath($path);
if ($testSuite instanceof TestSuiteForTestMethodWithDataProvider) {
$methodName = $this->getTestMethodNameWithoutDatasetSuffix($firstTest);
return "$classRelativePath::$methodName";
}
return $classRelativePath;
}
/**
* Gets the prettified test method name without dataset-related suffix.
*/
private function getTestMethodNameWithoutDatasetSuffix(TestMethod $testMethod): string
{
return Str::beforeLast($testMethod->testDox()->prettifiedMethodName(), ' with data set ');
}
/**
* Gets the first test from the test suite.
*/
private function getFirstTest(TestSuite $testSuite): ?TestMethod
{ {
$tests = $testSuite->tests()->asArray(); $tests = $testSuite->tests()->asArray();
@ -181,9 +218,7 @@ final readonly class Converter
throw ShouldNotHappen::fromMessage('Not an instance of TestMethod'); throw ShouldNotHappen::fromMessage('Not an instance of TestMethod');
} }
$path = $firstTest->testDox()->prettifiedClassName(); return $firstTest;
return $this->toRelativePath($path);
} }
/** /**

View File

@ -38,7 +38,7 @@ final class ServiceMessage
{ {
return new self('testSuiteStarted', [ return new self('testSuiteStarted', [
'name' => $name, 'name' => $name,
'locationHint' => $location === null ? null : "file://$location", 'locationHint' => $location === null ? null : "pest_qn://$location",
]); ]);
} }

View File

@ -54,7 +54,7 @@ final class UsesCall
} }
/** /**
* @deprecated Use `pest()->theme()->compact()` instead. * @deprecated Use `pest()->printer()->compact()` instead.
*/ */
public function compact(): self public function compact(): self
{ {

View File

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

View File

@ -122,6 +122,9 @@ final class WrapperRunner implements RunnerInterface
$parameters = array_merge($parameters, $options->passthruPhp); $parameters = array_merge($parameters, $options->passthruPhp);
} }
/** @var array<int, non-empty-string> $parameters */
$parameters = $this->handleLaravelHerd($parameters);
$parameters[] = $wrapper; $parameters[] = $wrapper;
$this->parameters = $parameters; $this->parameters = $parameters;
@ -153,6 +156,21 @@ final class WrapperRunner implements RunnerInterface
return $this->complete($result); return $this->complete($result);
} }
/**
* Handles Laravel Herd's debug and coverage modes.
*
* @param array<string> $parameters
* @return array<string>
*/
private function handleLaravelHerd(array $parameters): array
{
if (isset($_ENV['HERD_DEBUG_INI'])) {
return array_merge($parameters, ['-c', $_ENV['HERD_DEBUG_INI']]);
}
return $parameters;
}
private function startWorkers(): void private function startWorkers(): void
{ {
for ($token = 1; $token <= $this->options->processes; $token++) { for ($token = 1; $token <= $this->options->processes; $token++) {

View File

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

View File

@ -1,3 +1,3 @@
Pest Testing Framework 3.0.1. Pest Testing Framework 3.0.7.

View File

@ -1,4 +1,4 @@
##teamcity[testSuiteStarted name='Tests/tests/Failure' locationHint='file://tests/.tests/Failure.php' flowId='1234'] ##teamcity[testSuiteStarted name='Tests/tests/Failure' locationHint='pest_qn://tests/.tests/Failure.php' flowId='1234']
##teamcity[testCount count='8' flowId='1234'] ##teamcity[testCount count='8' flowId='1234']
##teamcity[testStarted name='it can fail with comparison' locationHint='pest_qn://tests/.tests/Failure.php::it can fail with comparison' flowId='1234'] ##teamcity[testStarted name='it can fail with comparison' locationHint='pest_qn://tests/.tests/Failure.php::it can fail with comparison' flowId='1234']
##teamcity[testFailed name='it can fail with comparison' message='Failed asserting that true matches expected false.' details='at tests/.tests/Failure.php:6' type='comparisonFailure' actual='true' expected='false' flowId='1234'] ##teamcity[testFailed name='it can fail with comparison' message='Failed asserting that true matches expected false.' details='at tests/.tests/Failure.php:6' type='comparisonFailure' actual='true' expected='false' flowId='1234']

View File

@ -1,11 +1,15 @@
##teamcity[testSuiteStarted name='Tests/tests/SuccessOnly' locationHint='file://tests/.tests/SuccessOnly.php' flowId='1234'] ##teamcity[testSuiteStarted name='Tests/tests/SuccessOnly' locationHint='pest_qn://tests/.tests/SuccessOnly.php' flowId='1234']
##teamcity[testCount count='2' flowId='1234'] ##teamcity[testCount count='3' flowId='1234']
##teamcity[testStarted name='it can pass with comparison' locationHint='pest_qn://tests/.tests/SuccessOnly.php::it can pass with comparison' flowId='1234'] ##teamcity[testStarted name='it can pass with comparison' locationHint='pest_qn://tests/.tests/SuccessOnly.php::it can pass with comparison' flowId='1234']
##teamcity[testFinished name='it can pass with comparison' duration='100000' flowId='1234'] ##teamcity[testFinished name='it can pass with comparison' duration='100000' flowId='1234']
##teamcity[testStarted name='can also pass' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can also pass' flowId='1234'] ##teamcity[testStarted name='can also pass' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can also pass' flowId='1234']
##teamcity[testFinished name='can also pass' duration='100000' flowId='1234'] ##teamcity[testFinished name='can also pass' duration='100000' flowId='1234']
##teamcity[testSuiteStarted name='can pass with dataset' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can pass with dataset' flowId='1234']
##teamcity[testStarted name='can pass with dataset with data set "(true)"' locationHint='pest_qn://tests/.tests/SuccessOnly.php::can pass with dataset with data set "(true)"' flowId='1234']
##teamcity[testFinished name='can pass with dataset with data set "(true)"' duration='100000' flowId='1234']
##teamcity[testSuiteFinished name='can pass with dataset' flowId='1234']
##teamcity[testSuiteFinished name='Tests/tests/SuccessOnly' flowId='1234'] ##teamcity[testSuiteFinished name='Tests/tests/SuccessOnly' flowId='1234']
Tests: 2 passed (2 assertions) Tests: 3 passed (3 assertions)
Duration: 1.00s Duration: 1.00s

View File

@ -1305,6 +1305,7 @@
✓ it executes tests in the Helpers directory ✓ it executes tests in the Helpers directory
PASS Tests\Hooks\AfterEachTest PASS Tests\Hooks\AfterEachTest
✓ nested → nested afterEach execution order
✓ global afterEach execution order ✓ global afterEach execution order
PASS Tests\Hooks\BeforeEachTest PASS Tests\Hooks\BeforeEachTest
@ -1381,7 +1382,7 @@
✓ it proxies to uses call ✓ it proxies to uses call
PASS Tests\Unit\Configuration\Theme PASS Tests\Unit\Configuration\Theme
✓ it creates a theme instance ✓ it creates a printer instance
PASS Tests\Unit\Console\Help PASS Tests\Unit\Console\Help
✓ it outputs the help information when --help is used ✓ it outputs the help information when --help is used
@ -1573,4 +1574,4 @@
WARN Tests\Visual\Version WARN Tests\Visual\Version
- visual snapshot of help command output - visual snapshot of help command output
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 28 skipped, 1088 passed (2615 assertions) Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 28 skipped, 1089 passed (2637 assertions)

View File

@ -9,3 +9,7 @@ it('can pass with comparison', function () {
test('can also pass', function () { test('can also pass', function () {
expect("string")->toBeString(); expect("string")->toBeString();
}); });
test('can pass with dataset', function ($value) {
expect($value)->toEqual(true);
})->with([true]);

View File

@ -1,23 +1,79 @@
<?php <?php
beforeEach(function () {
$this->ith = 0;
});
pest()->afterEach(function () { pest()->afterEach(function () {
expect($this) expect($this)
->toHaveProperty('ith') ->toHaveProperty('ith')
->and($this->ith) ->and($this->ith)
->toBe(1); ->toBe(3);
$this->ith = 2; $this->ith++;
});
pest()->afterEach(function () {
expect($this)
->toHaveProperty('ith')
->and($this->ith)
->toBe(4);
$this->ith++;
}); });
afterEach(function () { afterEach(function () {
expect($this) expect($this)
->toHaveProperty('ith') ->toHaveProperty('ith')
->and($this->ith) ->and($this->ith)
->toBe(2); ->toBe(5);
$this->ith++;
});
describe('nested', function () {
afterEach(function () {
expect($this)
->toHaveProperty('ith')
->and($this->ith)
->toBe(6);
$this->ith++;
});
test('nested afterEach execution order', function () {
expect($this)
->toHaveProperty('ith')
->and($this->ith)
->toBe(0);
$this->ith++;
});
afterEach(function () {
expect($this)
->toHaveProperty('ith')
->and($this->ith)
->toBe(7);
$this->ith++;
});
});
afterEach(function () {
expect($this)
->toHaveProperty('ith')
->and($this->ith)
->toBeBetween(6, 8);
$this->ith++;
}); });
test('global afterEach execution order', function () { test('global afterEach execution order', function () {
expect($this) expect($this)
->not() ->toHaveProperty('ith')
->toHaveProperty('ith'); ->and($this->ith)
->toBe(0);
$this->ith++;
}); });

View File

@ -32,7 +32,12 @@ pest()
$_SERVER['globalHook']->calls->beforeAll++; $_SERVER['globalHook']->calls->beforeAll++;
}) })
->afterEach(function () { ->afterEach(function () {
$this->ith = 0; if (! isset($this->ith)) {
return;
}
assert($this->ith === 1, 'Expected $this->ith to be 1, but got '.$this->ith);
$this->ith++;
}) })
->afterAll(function () { ->afterAll(function () {
$_SERVER['globalHook']->afterAll = 0; $_SERVER['globalHook']->afterAll = 0;
@ -57,12 +62,12 @@ pest()->in('Hooks')
$_SERVER['globalHook']->beforeAll = 1; $_SERVER['globalHook']->beforeAll = 1;
}) })
->afterEach(function () { ->afterEach(function () {
expect($this) if (! isset($this->ith)) {
->toHaveProperty('ith') return;
->and($this->ith) }
->toBe(0);
$this->ith = 1; assert($this->ith === 2, 'Expected $this->ith to be 1, but got '.$this->ith);
$this->ith++;
}) })
->afterAll(function () { ->afterAll(function () {
expect($_SERVER['globalHook']) expect($_SERVER['globalHook'])

View File

@ -1,7 +1,7 @@
<?php <?php
it('creates a theme instance', function () { it('creates a printer instance', function () {
$theme = pest()->theme(); $theme = pest()->printer();
expect($theme)->toBeInstanceOf(Pest\Configuration\Theme::class); expect($theme)->toBeInstanceOf(Pest\Configuration\Printer::class);
}); });

View File

@ -36,8 +36,8 @@ test('junit output', function () use ($normalizedPath, $run) {
expect($result['testsuite']['@attributes']) expect($result['testsuite']['@attributes'])
->name->toBe('Tests\tests\SuccessOnly') ->name->toBe('Tests\tests\SuccessOnly')
->file->toBe($normalizedPath('tests/.tests/SuccessOnly.php')) ->file->toBe($normalizedPath('tests/.tests/SuccessOnly.php'))
->tests->toBe('2') ->tests->toBe('3')
->assertions->toBe('2') ->assertions->toBe('3')
->errors->toBe('0') ->errors->toBe('0')
->failures->toBe('0') ->failures->toBe('0')
->skipped->toBe('0'); ->skipped->toBe('0');

View File

@ -16,7 +16,7 @@ $run = function () {
test('parallel', function () use ($run) { test('parallel', function () use ($run) {
expect($run('--exclude-group=integration')) expect($run('--exclude-group=integration'))
->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 19 skipped, 1078 passed (2591 assertions)') ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 19 skipped, 1079 passed (2613 assertions)')
->toContain('Parallel: 3 processes'); ->toContain('Parallel: 3 processes');
})->skipOnWindows(); })->skipOnWindows();