Skip to content

Commit 02ee8ba

Browse files
phpstan-botstaabm
andauthored
Refactor: Add BleedingEdgeToggle::withBleedingEdge() and use it instead of manual set/restore in tests (#5915)
Co-authored-by: staabm <120441+staabm@users.noreply.github.com> Co-authored-by: Markus Staab <maggus.staab@googlemail.com>
1 parent 1fdf7f9 commit 02ee8ba

4 files changed

Lines changed: 178 additions & 46 deletions

File tree

src/DependencyInjection/BleedingEdgeToggle.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace PHPStan\DependencyInjection;
44

5+
use Generator;
6+
use PHPStan\ShouldNotHappenException;
7+
58
final class BleedingEdgeToggle
69
{
710

@@ -17,4 +20,32 @@ public static function setBleedingEdge(bool $bleedingEdge): void
1720
self::$bleedingEdge = $bleedingEdge;
1821
}
1922

23+
/**
24+
* Runs the callback with the toggle set to $bleedingEdge and restores the previous
25+
* value before returning, so the global toggle is never observable as mutated outside
26+
* this call. When used from a data provider, the data sets must be produced by the
27+
* callback so that the contained objects are constructed while the toggle is set -
28+
* holding the toggle across a `yield` would otherwise leak it into unrelated tests.
29+
*
30+
* @template T
31+
* @param callable(): T $callback
32+
* @return T
33+
*/
34+
public static function withBleedingEdge(bool $bleedingEdge, callable $callback)
35+
{
36+
$backup = self::$bleedingEdge;
37+
self::$bleedingEdge = $bleedingEdge;
38+
try {
39+
$result = $callback();
40+
41+
if ($result instanceof Generator) {
42+
throw new ShouldNotHappenException('callback is not allowed to yield, to prevent leaking the toggle into unrelated tests.');
43+
}
44+
45+
return $result;
46+
} finally {
47+
self::$bleedingEdge = $backup;
48+
}
49+
}
50+
2051
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\DependencyInjection;
4+
5+
use Override;
6+
use PHPStan\ShouldNotHappenException;
7+
use PHPUnit\Framework\TestCase;
8+
use RuntimeException;
9+
use Throwable;
10+
11+
final class BleedingEdgeToggleTest extends TestCase
12+
{
13+
14+
private bool $backup;
15+
16+
#[Override]
17+
protected function setUp(): void
18+
{
19+
$this->backup = BleedingEdgeToggle::isBleedingEdge();
20+
}
21+
22+
#[Override]
23+
protected function tearDown(): void
24+
{
25+
BleedingEdgeToggle::setBleedingEdge($this->backup);
26+
}
27+
28+
public function testTogglesDuringCallbackAndRestoresAfterwards(): void
29+
{
30+
BleedingEdgeToggle::setBleedingEdge(false);
31+
32+
$observed = BleedingEdgeToggle::withBleedingEdge(true, static fn (): bool => BleedingEdgeToggle::isBleedingEdge());
33+
34+
$this->assertTrue($observed);
35+
$this->assertFalse(BleedingEdgeToggle::isBleedingEdge());
36+
}
37+
38+
public function testRestoresPreviousValueWhenAlreadyEnabled(): void
39+
{
40+
BleedingEdgeToggle::setBleedingEdge(true);
41+
42+
$observed = BleedingEdgeToggle::withBleedingEdge(false, static fn (): bool => BleedingEdgeToggle::isBleedingEdge());
43+
44+
$this->assertFalse($observed);
45+
$this->assertTrue(BleedingEdgeToggle::isBleedingEdge());
46+
}
47+
48+
public function testReturnsCallbackResult(): void
49+
{
50+
$result = BleedingEdgeToggle::withBleedingEdge(true, fn (): string => $this->makeValue());
51+
52+
$this->assertSame('value', $result);
53+
}
54+
55+
public function testRestoresPreviousValueWhenCallbackThrows(): void
56+
{
57+
BleedingEdgeToggle::setBleedingEdge(false);
58+
59+
$thrown = false;
60+
try {
61+
BleedingEdgeToggle::withBleedingEdge(true, static function (): void {
62+
throw new RuntimeException('boom');
63+
});
64+
} catch (Throwable $e) {
65+
$thrown = $e instanceof RuntimeException && $e->getMessage() === 'boom';
66+
}
67+
68+
$this->assertTrue($thrown);
69+
$this->assertFalse(BleedingEdgeToggle::isBleedingEdge());
70+
}
71+
72+
public function testThrowsAndRestoresWhenCallbackYields(): void
73+
{
74+
BleedingEdgeToggle::setBleedingEdge(false);
75+
76+
$thrown = false;
77+
try {
78+
BleedingEdgeToggle::withBleedingEdge(true, static function () {
79+
yield 1;
80+
});
81+
} catch (ShouldNotHappenException) {
82+
$thrown = true;
83+
}
84+
85+
$this->assertTrue($thrown);
86+
$this->assertFalse(BleedingEdgeToggle::isBleedingEdge());
87+
}
88+
89+
public function testProducesDataSetsWhileToggleIsSet(): void
90+
{
91+
BleedingEdgeToggle::setBleedingEdge(false);
92+
93+
$dataSets = BleedingEdgeToggle::withBleedingEdge(true, static fn (): array => [
94+
BleedingEdgeToggle::isBleedingEdge(),
95+
BleedingEdgeToggle::isBleedingEdge(),
96+
]);
97+
98+
$this->assertSame([true, true], $dataSets);
99+
$this->assertFalse(BleedingEdgeToggle::isBleedingEdge());
100+
}
101+
102+
private function makeValue(): string
103+
{
104+
return 'value';
105+
}
106+
107+
}

tests/PHPStan/Type/Constant/ConstantArrayTypeBuilderTest.php

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Type\IntegerType;
1111
use PHPStan\Type\NullType;
1212
use PHPStan\Type\StringType;
13+
use PHPStan\Type\Type;
1314
use PHPStan\Type\TypeCombinator;
1415
use PHPStan\Type\VerbosityLevel;
1516
use function sprintf;
@@ -325,16 +326,9 @@ public function testGetArrayEmptyWithUnknownSealednessStaysConstantArrayType():
325326

326327
public function testGetArraySealedEmptyStaysConstantArrayType(): void
327328
{
328-
$bleedingEdgeBackup = BleedingEdgeToggle::isBleedingEdge();
329-
BleedingEdgeToggle::setBleedingEdge(true);
330-
try {
331-
$builder = ConstantArrayTypeBuilder::createEmpty();
332-
$array = $builder->getArray();
333-
$this->assertInstanceOf(ConstantArrayType::class, $array);
334-
$this->assertSame('array{}', $array->describe(VerbosityLevel::precise()));
335-
} finally {
336-
BleedingEdgeToggle::setBleedingEdge($bleedingEdgeBackup);
337-
}
329+
$array = BleedingEdgeToggle::withBleedingEdge(true, static fn (): Type => ConstantArrayTypeBuilder::createEmpty()->getArray());
330+
$this->assertInstanceOf(ConstantArrayType::class, $array);
331+
$this->assertSame('array{}', $array->describe(VerbosityLevel::precise()));
338332
}
339333

340334
public function testGetArrayEmptyWithRealUnsealedCollapsesToArrayType(): void

tests/PHPStan/Type/TypeCombinatorTest.php

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3226,18 +3226,18 @@ public function testUnion(
32263226
string $expectedTypeDescription,
32273227
): void
32283228
{
3229-
$bleedingEdgeBackup = BleedingEdgeToggle::isBleedingEdge();
32303229
$typeStringResolver = self::getContainer()->getByType(TypeStringResolver::class);
3231-
foreach ($types as $i => $type) {
3232-
BleedingEdgeToggle::setBleedingEdge(true);
3233-
if (!is_string($type)) {
3234-
continue;
3235-
}
3230+
$types = BleedingEdgeToggle::withBleedingEdge(true, static function () use ($types, $typeStringResolver): array {
3231+
foreach ($types as $i => $type) {
3232+
if (!is_string($type)) {
3233+
continue;
3234+
}
32363235

3237-
$types[$i] = $typeStringResolver->resolve($type, null);
3238-
}
3236+
$types[$i] = $typeStringResolver->resolve($type, null);
3237+
}
32393238

3240-
BleedingEdgeToggle::setBleedingEdge($bleedingEdgeBackup);
3239+
return $types;
3240+
});
32413241
$actualType = TypeCombinator::union(...$types);
32423242
$this->assertSame(
32433243
$expectedTypeDescription,
@@ -3276,18 +3276,18 @@ public function testUnionInversed(
32763276
): void
32773277
{
32783278
$types = array_reverse($types);
3279-
$bleedingEdgeBackup = BleedingEdgeToggle::isBleedingEdge();
32803279
$typeStringResolver = self::getContainer()->getByType(TypeStringResolver::class);
3281-
foreach ($types as $i => $type) {
3282-
BleedingEdgeToggle::setBleedingEdge(true);
3283-
if (!is_string($type)) {
3284-
continue;
3285-
}
3280+
$types = BleedingEdgeToggle::withBleedingEdge(true, static function () use ($types, $typeStringResolver): array {
3281+
foreach ($types as $i => $type) {
3282+
if (!is_string($type)) {
3283+
continue;
3284+
}
32863285

3287-
$types[$i] = $typeStringResolver->resolve($type, null);
3288-
}
3286+
$types[$i] = $typeStringResolver->resolve($type, null);
3287+
}
32893288

3290-
BleedingEdgeToggle::setBleedingEdge($bleedingEdgeBackup);
3289+
return $types;
3290+
});
32913291

32923292
$actualType = TypeCombinator::union(...$types);
32933293
$this->assertSame(
@@ -5640,18 +5640,18 @@ public function testIntersect(
56405640
string $expectedTypeDescription,
56415641
): void
56425642
{
5643-
$bleedingEdgeBackup = BleedingEdgeToggle::isBleedingEdge();
56445643
$typeStringResolver = self::getContainer()->getByType(TypeStringResolver::class);
5645-
foreach ($types as $i => $type) {
5646-
BleedingEdgeToggle::setBleedingEdge(true);
5647-
if (!is_string($type)) {
5648-
continue;
5649-
}
5644+
$types = BleedingEdgeToggle::withBleedingEdge(true, static function () use ($types, $typeStringResolver): array {
5645+
foreach ($types as $i => $type) {
5646+
if (!is_string($type)) {
5647+
continue;
5648+
}
56505649

5651-
$types[$i] = $typeStringResolver->resolve($type, null);
5652-
}
5650+
$types[$i] = $typeStringResolver->resolve($type, null);
5651+
}
56535652

5654-
BleedingEdgeToggle::setBleedingEdge($bleedingEdgeBackup);
5653+
return $types;
5654+
});
56555655

56565656
$actualType = TypeCombinator::intersect(...$types);
56575657
$this->assertSame(
@@ -5676,18 +5676,18 @@ public function testIntersectInversed(
56765676
string $expectedTypeDescription,
56775677
): void
56785678
{
5679-
$bleedingEdgeBackup = BleedingEdgeToggle::isBleedingEdge();
56805679
$typeStringResolver = self::getContainer()->getByType(TypeStringResolver::class);
5681-
foreach ($types as $i => $type) {
5682-
BleedingEdgeToggle::setBleedingEdge(true);
5683-
if (!is_string($type)) {
5684-
continue;
5685-
}
5680+
$types = BleedingEdgeToggle::withBleedingEdge(true, static function () use ($types, $typeStringResolver): array {
5681+
foreach ($types as $i => $type) {
5682+
if (!is_string($type)) {
5683+
continue;
5684+
}
56865685

5687-
$types[$i] = $typeStringResolver->resolve($type, null);
5688-
}
5686+
$types[$i] = $typeStringResolver->resolve($type, null);
5687+
}
56895688

5690-
BleedingEdgeToggle::setBleedingEdge($bleedingEdgeBackup);
5689+
return $types;
5690+
});
56915691

56925692
$actualType = TypeCombinator::intersect(...array_reverse($types));
56935693
$this->assertSame(

0 commit comments

Comments
 (0)