Skip to content

Commit dacb337

Browse files
authored
Ensure tested PHP code is valid (#177)
1 parent 57f589c commit dacb337

15 files changed

+152
-75
lines changed

tests/Rule/DeadCodeRuleTest.php

+62-6
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,20 @@
4242
use ShipMonk\PHPStan\DeadCode\Provider\VendorUsageProvider;
4343
use ShipMonk\PHPStan\DeadCode\Provider\VirtualUsageData;
4444
use ShipMonk\PHPStan\DeadCode\Transformer\FileSystem;
45+
use Throwable;
46+
use Traversable;
47+
use function array_merge;
48+
use function error_reporting;
4549
use function file_get_contents;
4650
use function is_array;
51+
use function iterator_to_array;
52+
use function ob_end_clean;
53+
use function ob_start;
4754
use function preg_replace;
4855
use function str_replace;
4956
use function strpos;
57+
use const E_ALL;
58+
use const E_DEPRECATED;
5059
use const PHP_VERSION_ID;
5160

5261
/**
@@ -126,6 +135,53 @@ public function testDeadWithGroups($files, bool $requirementsMet = true): void
126135
$this->doTestDead($files, $requirementsMet);
127136
}
128137

138+
/**
139+
* Ensure we test real PHP code
140+
* - mainly targets invalid class/trait/iface compositions
141+
*
142+
* @runInSeparateProcess
143+
*/
144+
public function testNoFatalError(): void
145+
{
146+
if (PHP_VERSION_ID < 8_04_00) {
147+
self::markTestSkipped('Requires PHP 8.4+ to allow any PHP feature in test code');
148+
}
149+
150+
// when lowest versions are installed, we get "Implicitly marking parameter xxx as nullable is deprecated" for symfony deps
151+
error_reporting(E_ALL & ~E_DEPRECATED);
152+
153+
$required = [];
154+
155+
$fileProviders = array_merge(
156+
iterator_to_array(self::provideFiles(), false),
157+
iterator_to_array(self::provideGroupingFiles(), false),
158+
iterator_to_array(self::provideAutoRemoveFiles(), false),
159+
);
160+
161+
foreach ($fileProviders as $args) {
162+
$files = is_array($args[0]) ? $args[0] : [$args[0]];
163+
164+
foreach ($files as $file) {
165+
if (isset($required[$file])) {
166+
continue;
167+
}
168+
169+
try {
170+
ob_start();
171+
require $file;
172+
ob_end_clean();
173+
174+
} catch (Throwable $e) {
175+
self::fail("Fatal error in {$e->getFile()}:{$e->getLine()}:\n {$e->getMessage()}");
176+
}
177+
178+
$required[$file] = true;
179+
}
180+
}
181+
182+
$this->expectNotToPerformAssertions();
183+
}
184+
129185
/**
130186
* @param string|non-empty-list<string> $files
131187
*/
@@ -371,9 +427,9 @@ public function testGrouping($files, array $expectedErrors): void
371427
}
372428

373429
/**
374-
* @return iterable<string, array{0: string|list<string>, 1: list<array{0: string, 1: int, 2?: string|null}>}>
430+
* @return Traversable<string, array{0: string|list<string>, 1: list<array{0: string, 1: int, 2?: string|null}>}>
375431
*/
376-
public static function provideGroupingFiles(): iterable
432+
public static function provideGroupingFiles(): Traversable
377433
{
378434
yield 'default' => [
379435
__DIR__ . '/data/grouping/default.php',
@@ -454,9 +510,9 @@ public static function provideGroupingFiles(): iterable
454510
}
455511

456512
/**
457-
* @return array<string, array{0: string|list<string>, 1?: bool}>
513+
* @return Traversable<string, array{0: string|list<string>, 1?: bool}>
458514
*/
459-
public static function provideFiles(): iterable
515+
public static function provideFiles(): Traversable
460516
{
461517
// methods
462518
yield 'method-anonym' => [__DIR__ . '/data/methods/anonym.php'];
@@ -572,9 +628,9 @@ public static function provideFiles(): iterable
572628
}
573629

574630
/**
575-
* @return iterable<string, array{0: string}>
631+
* @return Traversable<string, array{0: string}>
576632
*/
577-
public function provideAutoRemoveFiles(): iterable
633+
public static function provideAutoRemoveFiles(): Traversable
578634
{
579635
yield 'sample' => [__DIR__ . '/data/removing/sample.php'];
580636
yield 'no-namespace' => [__DIR__ . '/data/removing/no-namespace.php'];

tests/Rule/data/constants/constant-function.php

+6-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ class Test extends TestParent {
1212
}
1313

1414

15-
$fn = 'constant';
16-
echo constant('DeadConstFn\Test::A');
17-
echo constant('Unknown::A');
18-
echo $fn('\DeadConstFn\Test::B');
15+
function test() {
16+
$fn = 'constant';
17+
echo constant('DeadConstFn\Test::A');
18+
echo constant('Unknown::A');
19+
echo $fn('\DeadConstFn\Test::B');
20+
}

tests/Rule/data/constants/mixed/tracked.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,6 @@ function testMethodExists(Iface $iface)
6161
}
6262
}
6363

64-
new Tester();
64+
function test() {
65+
new Tester();
66+
}

tests/Rule/data/excluders/tests/src/code.php

+6-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ class DeclaredInSrcUsedInSrc {
1212
const CONST = 1;
1313
}
1414

15-
echo DeclaredInSrcUsedInSrc::CONST;
16-
echo DeclaredInSrcUsedInBoth::CONST;
17-
echo DeclaredInTestsUsedInBoth::CONST;
18-
echo DeclaredInTestsUsedInSrc::CONST;
15+
function test2() {
16+
echo DeclaredInSrcUsedInSrc::CONST;
17+
echo DeclaredInSrcUsedInBoth::CONST;
18+
echo DeclaredInTestsUsedInBoth::CONST;
19+
echo DeclaredInTestsUsedInSrc::CONST;
20+
}

tests/Rule/data/excluders/tests/tests/code.php

+6-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ class DeclaredInTestsUsedInSrc {
1212
const CONST = 1;
1313
}
1414

15-
echo DeclaredInSrcUsedInBoth::CONST;
16-
echo DeclaredInSrcUsedInTests::CONST;
17-
echo DeclaredInTestsUsedInTests::CONST;
18-
echo DeclaredInTestsUsedInBoth::CONST;
15+
function test1() {
16+
echo DeclaredInSrcUsedInBoth::CONST;
17+
echo DeclaredInSrcUsedInTests::CONST;
18+
echo DeclaredInTestsUsedInTests::CONST;
19+
echo DeclaredInTestsUsedInBoth::CONST;
20+
}

tests/Rule/data/methods/array-map-1.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,6 @@ public function method7(): void {} // error: Unused DeadMap\Child::method7
3535

3636
}
3737

38-
new ArrayMapTest();
38+
function test() {
39+
new ArrayMapTest();
40+
}

tests/Rule/data/methods/basic.php

+26-24
Original file line numberDiff line numberDiff line change
@@ -32,36 +32,21 @@ public function interfaceMethod(): void
3232
}
3333
}
3434

35-
final class TestChild extends TestParent {
36-
37-
public function __construct(TestA|TestB $class)
38-
{
39-
$class->commonMethod();
40-
$class->differentMethod();
41-
$this->overwrittenParentMethodUsedByChild();
42-
$this->childMethodNowUsed();
43-
}
35+
trait TestTrait {
4436

45-
public function childMethodNowUsed(TestInterface|TestA $class, TestInterface $interface): void
37+
public function __construct() // error: Unused DeadBasic\TestTrait::__construct
4638
{
47-
$class->differentMethod();
48-
$interface->interfaceMethod();
4939
}
5040

51-
public function childMethodUnused(): void // error: Unused DeadBasic\TestChild::childMethodUnused
41+
public function traitMethodUsed(): void
5242
{
5343

5444
}
5545

56-
public function childMethodUsed(): void
46+
public function traitMethodUnused(): void // error: Unused DeadBasic\TestTrait::traitMethodUnused
5747
{
5848

5949
}
60-
61-
public function overwrittenParentMethodUsedByChild(): void
62-
{
63-
$this->parentMethodUsed($this);
64-
}
6550
}
6651

6752
abstract class TestParent {
@@ -89,21 +74,38 @@ public function parentMethodUnused(): void // error: Unused DeadBasic\TestParen
8974
}
9075
}
9176

92-
trait TestTrait {
77+
final class TestChild extends TestParent {
9378

94-
public function __construct() // error: Unused DeadBasic\TestTrait::__construct
79+
public function __construct(TestA|TestB $class)
9580
{
81+
$class->commonMethod();
82+
$class->differentMethod();
83+
$this->overwrittenParentMethodUsedByChild();
84+
$this->childMethodNowUsed();
9685
}
9786

98-
public function traitMethodUsed(): void
87+
public function childMethodNowUsed(TestInterface|TestA $class, TestInterface $interface): void
88+
{
89+
$class->differentMethod();
90+
$interface->interfaceMethod();
91+
}
92+
93+
public function childMethodUnused(): void // error: Unused DeadBasic\TestChild::childMethodUnused
9994
{
10095

10196
}
10297

103-
public function traitMethodUnused(): void // error: Unused DeadBasic\TestTrait::traitMethodUnused
98+
public function childMethodUsed(): void
10499
{
105100

106101
}
102+
103+
public function overwrittenParentMethodUsedByChild(): void
104+
{
105+
$this->parentMethodUsed($this);
106+
}
107107
}
108108

109-
new TestChild();
109+
function test() {
110+
new TestChild();
111+
}

tests/Rule/data/methods/ctor-private.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace CtorPrivate;
44

5-
class StaticCtor implements MyInterface
5+
class StaticCtor
66
{
77
private function __construct()
88
{

tests/Rule/data/methods/cycles.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ public function b(A $a): void // error: Unused Cycles\B::b
2929
}
3030
}
3131

32-
(new Foo())->recursion1();
32+
function test() {
33+
(new Foo())->recursion1();
34+
}

tests/Rule/data/methods/hierarchy-in-vendor.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
namespace HierachyInVendor;
44

5+
use PHPStan\Rules\Rule;
56
use PHPUnit\Framework\TestCase;
67
use ShipMonk\PHPStan\DeadCode\Rule\RuleTestCase;
78

8-
class SomeTest extends RuleTestCase {
9+
abstract class SomeTest extends RuleTestCase {
910

1011
public static function someMethod(): void {}
1112
}

tests/Rule/data/methods/magic.php

+6-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public function __destruct()
2222
$this->calledFromDestruct();
2323
}
2424

25-
public function __get()
25+
public function __get($what)
2626
{
2727
$this->calledFromGet();
2828
}
@@ -34,6 +34,8 @@ public function calledFromGet() {}
3434

3535
}
3636

37-
$invokable = Magic::create();
38-
$invokable->magic();
39-
$invokable();
37+
function test() {
38+
$invokable = Magic::create();
39+
$invokable->magic();
40+
$invokable();
41+
}

tests/Rule/data/methods/mixed/tracked.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,6 @@ function testMethodExists(Iface $iface)
6969
}
7070
}
7171

72-
new Tester();
72+
function test() {
73+
new Tester();
74+
}

tests/Rule/data/methods/traits-12.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ abstract public function method(): void;
88

99
class Origin {
1010

11-
public function method(): string {
12-
return 'Doing something';
13-
}
11+
public function method(): void {}
1412
}
1513

1614
class User extends Origin {

tests/Rule/data/methods/unknown-class.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22

33
namespace UnknownClass;
44

5-
Unknown::notFailing();
5+
function test() {
6+
Unknown::notFailing();
7+
}

tests/Rule/data/providers/reflection.php

+21-19
Original file line numberDiff line numberDiff line change
@@ -92,32 +92,34 @@ class GetAllConstantsChild extends GetAllConstantsParent {
9292
const CONSTANT = 1; // error: Unused Reflection\GetAllConstantsChild::CONSTANT
9393
}
9494

95-
GetAllConstantsChild::getConstants();
96-
GetAllConstantsChild::getConstants2();
95+
function test() {
96+
GetAllConstantsChild::getConstants();
97+
GetAllConstantsChild::getConstants2();
9798

9899

99-
$reflection1 = new \ReflectionClass(Holder1::class);
100-
$reflection1->getConstants();
101-
$reflection1->getMethod('used');
100+
$reflection1 = new \ReflectionClass(Holder1::class);
101+
$reflection1->getConstants();
102+
$reflection1->getMethod('used');
102103

103-
$reflection2 = new \ReflectionClass(Holder2::class);
104-
$reflection2->getReflectionConstant('CONST1');
105-
$reflection2->getConstant('CONST3');
106-
$reflection2->getConstructor();
104+
$reflection2 = new \ReflectionClass(Holder2::class);
105+
$reflection2->getReflectionConstant('CONST1');
106+
$reflection2->getConstant('CONST3');
107+
$reflection2->getConstructor();
107108

108-
$reflection3 = new \ReflectionClass(Holder3::class);
109-
$reflection3->getMethods();
110-
$reflection3->getReflectionConstants();
109+
$reflection3 = new \ReflectionClass(Holder3::class);
110+
$reflection3->getMethods();
111+
$reflection3->getReflectionConstants();
111112

112-
$reflection4 = new \ReflectionClass(Holder4::class);
113-
$reflection4->newInstanceWithoutConstructor();
113+
$reflection4 = new \ReflectionClass(Holder4::class);
114+
$reflection4->newInstanceWithoutConstructor();
114115

115-
$reflection4 = new \ReflectionClass(Holder5::class);
116-
$reflection4->newInstance();
116+
$reflection4 = new \ReflectionClass(Holder5::class);
117+
$reflection4->newInstance();
117118

118-
$enumReflection1 = new \ReflectionClass(EnumHolder1::class);
119-
$enumReflection1->getConstants();
120-
$enumReflection1->getMethod('used');
119+
$enumReflection1 = new \ReflectionClass(EnumHolder1::class);
120+
$enumReflection1->getConstants();
121+
$enumReflection1->getMethod('used');
122+
}
121123

122124
/**
123125
* @param class-string<HolderParent> $fqn

0 commit comments

Comments
 (0)