Skip to content

Commit 23b6adc

Browse files
authored
[11.x] Adds PHPUnit 11 support (#49622)
1 parent 9ccf003 commit 23b6adc

23 files changed

+812
-619
lines changed

.github/workflows/tests.yml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ jobs:
4040
fail-fast: true
4141
matrix:
4242
php: [8.2, 8.3]
43+
phpunit: ['10.5', '11.0']
4344
stability: [prefer-lowest, prefer-stable]
4445

45-
name: PHP ${{ matrix.php }} - ${{ matrix.stability }}
46+
name: PHP ${{ matrix.php }} - PHPUnit ${{ matrix.phpunit }} - ${{ matrix.stability }}
4647

4748
steps:
4849
- name: Checkout code
@@ -69,6 +70,13 @@ jobs:
6970
max_attempts: 5
7071
command: composer require guzzlehttp/psr7:^2.4 --no-interaction --no-update
7172

73+
- name: Set PHPUnit
74+
uses: nick-fields/retry@v2
75+
with:
76+
timeout_minutes: 5
77+
max_attempts: 5
78+
command: composer require phpunit/phpunit:^${{ matrix.phpunit }} --dev --no-interaction --no-update
79+
7280
- name: Install dependencies
7381
uses: nick-fields/retry@v2
7482
with:
@@ -101,9 +109,10 @@ jobs:
101109
fail-fast: true
102110
matrix:
103111
php: [8.2, 8.3]
112+
phpunit: ['10.5', '11.0']
104113
stability: [prefer-lowest, prefer-stable]
105114

106-
name: PHP ${{ matrix.php }} - ${{ matrix.stability }} - Windows
115+
name: PHP ${{ matrix.php }} - PHPUnit ${{ matrix.phpunit }} - ${{ matrix.stability }} - Windows
107116

108117
steps:
109118
- name: Set git to use LF
@@ -131,6 +140,13 @@ jobs:
131140
max_attempts: 5
132141
command: composer require guzzlehttp/psr7:~2.4 --no-interaction --no-update
133142

143+
- name: Set PHPUnit
144+
uses: nick-fields/retry@v2
145+
with:
146+
timeout_minutes: 5
147+
max_attempts: 5
148+
command: composer require phpunit/phpunit:~${{ matrix.phpunit }} --dev --no-interaction --no-update
149+
134150
- name: Install dependencies
135151
uses: nick-fields/retry@v2
136152
with:

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
"orchestra/testbench-core": "^9.0",
109109
"pda/pheanstalk": "^5.0",
110110
"phpstan/phpstan": "^1.4.7",
111-
"phpunit/phpunit": "^10.5",
111+
"phpunit/phpunit": "^10.5|^11.0",
112112
"predis/predis": "^2.0.2",
113113
"symfony/cache": "^7.0",
114114
"symfony/http-client": "^7.0",
@@ -163,7 +163,7 @@
163163
"ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).",
164164
"ably/ably-php": "Required to use the Ably broadcast driver (^1.0).",
165165
"aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).",
166-
"brianium/paratest": "Required to run tests in parallel (^6.0).",
166+
"brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).",
167167
"fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).",
168168
"filp/whoops": "Required for friendly error pages in development (^2.14.3).",
169169
"guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.8).",
@@ -176,7 +176,7 @@
176176
"mockery/mockery": "Required to use mocking (^1.6).",
177177
"nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).",
178178
"pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).",
179-
"phpunit/phpunit": "Required to use assertions and run tests (^10.5).",
179+
"phpunit/phpunit": "Required to use assertions and run tests (^10.5|^11.0).",
180180
"predis/predis": "Required to use the predis connector (^2.0.2).",
181181
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
182182
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).",

phpstan.src.neon.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ parameters:
1313
- "#should always throw an exception or terminate script execution#"
1414
- "#Instantiated class [a-zA-Z0-9\\\\_]+ not found.#"
1515
- "#Unsafe usage of new static#"
16+
- "#has an uninitialized readonly property#"
1617
excludePaths:
1718
- "src/Illuminate/Testing/ParallelRunner.php"

src/Illuminate/Foundation/Bootstrap/HandleExceptions.php

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Illuminate\Log\LogManager;
1010
use Illuminate\Support\Env;
1111
use Monolog\Handler\NullHandler;
12+
use PHPUnit\Runner\ErrorHandler;
1213
use Symfony\Component\Console\Output\ConsoleOutput;
1314
use Symfony\Component\ErrorHandler\Error\FatalError;
1415
use Throwable;
@@ -37,7 +38,7 @@ class HandleExceptions
3738
*/
3839
public function bootstrap(Application $app)
3940
{
40-
self::$reservedMemory = str_repeat('x', 32768);
41+
static::$reservedMemory = str_repeat('x', 32768);
4142

4243
static::$app = $app;
4344

@@ -177,7 +178,7 @@ protected function ensureNullLogDriverIsConfigured()
177178
*/
178179
public function handleException(Throwable $e)
179180
{
180-
self::$reservedMemory = null;
181+
static::$reservedMemory = null;
181182

182183
try {
183184
$this->getExceptionHandler()->report($e);
@@ -225,7 +226,7 @@ protected function renderHttpResponse(Throwable $e)
225226
*/
226227
public function handleShutdown()
227228
{
228-
self::$reservedMemory = null;
229+
static::$reservedMemory = null;
229230

230231
if (! is_null($error = error_get_last()) && $this->isFatal($error['type'])) {
231232
$this->handleException($this->fatalErrorFromPhpError($error, 0));
@@ -292,9 +293,70 @@ protected function getExceptionHandler()
292293
* Clear the local application instance from memory.
293294
*
294295
* @return void
296+
*
297+
* @deprecated This method will be removed in a future Laravel version.
295298
*/
296299
public static function forgetApp()
297300
{
298301
static::$app = null;
299302
}
303+
304+
/**
305+
* Flush the bootstrapper's global state.
306+
*
307+
* @return void
308+
*/
309+
public static function flushState()
310+
{
311+
if (is_null(static::$app)) {
312+
return;
313+
}
314+
315+
static::flushHandlersState();
316+
317+
static::$app = null;
318+
319+
static::$reservedMemory = null;
320+
}
321+
322+
/**
323+
* Flush the bootstrapper's global handlers state.
324+
*
325+
* @return void
326+
*/
327+
public static function flushHandlersState()
328+
{
329+
while (true) {
330+
$previousHandler = set_exception_handler(static fn () => null);
331+
332+
restore_exception_handler();
333+
334+
if ($previousHandler === null) {
335+
break;
336+
}
337+
338+
restore_exception_handler();
339+
}
340+
341+
while (true) {
342+
$previousHandler = set_error_handler(static fn () => null);
343+
344+
restore_error_handler();
345+
346+
if ($previousHandler === null) {
347+
break;
348+
}
349+
350+
restore_error_handler();
351+
}
352+
353+
if (class_exists(ErrorHandler::class)) {
354+
$instance = ErrorHandler::instance();
355+
356+
if ((fn () => $this->enabled ?? false)->call($instance)) {
357+
$instance->disable();
358+
$instance->enable();
359+
}
360+
}
361+
}
300362
}

src/Illuminate/Foundation/Testing/TestCase.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ protected function tearDown(): void
246246
Component::forgetComponentsResolver();
247247
Component::forgetFactory();
248248
ConvertEmptyStringsToNull::flushState();
249-
HandleExceptions::forgetApp();
249+
HandleExceptions::flushState();
250250
Queue::createPayloadUsing(null);
251251
Sleep::fake(false);
252252
TrimStrings::flushState();

src/Illuminate/Testing/Constraints/ArraySubset.php

Lines changed: 7 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -2,142 +2,17 @@
22

33
namespace Illuminate\Testing\Constraints;
44

5-
use ArrayObject;
65
use PHPUnit\Framework\Constraint\Constraint;
7-
use SebastianBergmann\Comparator\ComparisonFailure;
8-
use Traversable;
6+
use PHPUnit\Runner\Version;
97

10-
/**
11-
* @internal This class is not meant to be used or overwritten outside the framework itself.
12-
*/
13-
final class ArraySubset extends Constraint
14-
{
15-
/**
16-
* @var iterable
17-
*/
18-
private $subset;
19-
20-
/**
21-
* @var bool
22-
*/
23-
private $strict;
24-
25-
/**
26-
* Create a new array subset constraint instance.
27-
*
28-
* @param iterable $subset
29-
* @param bool $strict
30-
* @return void
31-
*/
32-
public function __construct(iterable $subset, bool $strict = false)
8+
if (str_starts_with(Version::series(), '10')) {
9+
class ArraySubset extends Constraint
3310
{
34-
$this->strict = $strict;
35-
$this->subset = $subset;
11+
use Concerns\ArraySubset;
3612
}
37-
38-
/**
39-
* Evaluates the constraint for parameter $other.
40-
*
41-
* If $returnResult is set to false (the default), an exception is thrown
42-
* in case of a failure. null is returned otherwise.
43-
*
44-
* If $returnResult is true, the result of the evaluation is returned as
45-
* a boolean value instead: true in case of success, false in case of a
46-
* failure.
47-
*
48-
* @param mixed $other
49-
* @param string $description
50-
* @param bool $returnResult
51-
* @return bool|null
52-
*
53-
* @throws \PHPUnit\Framework\ExpectationFailedException
54-
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
55-
*/
56-
public function evaluate($other, string $description = '', bool $returnResult = false): ?bool
13+
} else {
14+
readonly class ArraySubset extends Constraint
5715
{
58-
// type cast $other & $this->subset as an array to allow
59-
// support in standard array functions.
60-
$other = $this->toArray($other);
61-
$this->subset = $this->toArray($this->subset);
62-
63-
$patched = array_replace_recursive($other, $this->subset);
64-
65-
if ($this->strict) {
66-
$result = $other === $patched;
67-
} else {
68-
$result = $other == $patched;
69-
}
70-
71-
if ($returnResult) {
72-
return $result;
73-
}
74-
75-
if (! $result) {
76-
$f = new ComparisonFailure(
77-
$patched,
78-
$other,
79-
var_export($patched, true),
80-
var_export($other, true)
81-
);
82-
83-
$this->fail($other, $description, $f);
84-
}
85-
86-
return null;
87-
}
88-
89-
/**
90-
* Returns a string representation of the constraint.
91-
*
92-
* @return string
93-
*
94-
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
95-
*/
96-
public function toString(): string
97-
{
98-
return 'has the subset '.$this->exporter()->export($this->subset);
99-
}
100-
101-
/**
102-
* Returns the description of the failure.
103-
*
104-
* The beginning of failure messages is "Failed asserting that" in most
105-
* cases. This method should return the second part of that sentence.
106-
*
107-
* @param mixed $other
108-
* @return string
109-
*
110-
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
111-
*/
112-
protected function failureDescription($other): string
113-
{
114-
return 'an array '.$this->toString();
115-
}
116-
117-
/**
118-
* Returns the description of the failure.
119-
*
120-
* The beginning of failure messages is "Failed asserting that" in most
121-
* cases. This method should return the second part of that sentence.
122-
*
123-
* @param iterable $other
124-
* @return array
125-
*/
126-
private function toArray(iterable $other): array
127-
{
128-
if (is_array($other)) {
129-
return $other;
130-
}
131-
132-
if ($other instanceof ArrayObject) {
133-
return $other->getArrayCopy();
134-
}
135-
136-
if ($other instanceof Traversable) {
137-
return iterator_to_array($other);
138-
}
139-
140-
// Keep BC even if we know that array would not be the expected one
141-
return (array) $other;
16+
use Concerns\ArraySubset;
14217
}
14318
}

0 commit comments

Comments
 (0)