Skip to content

Commit f21ea1b

Browse files
authored
test: Allow to specify the running PHP version (#1039)
This is not 100% correct as it won't change the actual runtime but it will adapt the PHP Parser version used.
1 parent 49af316 commit f21ea1b

File tree

12 files changed

+209
-16
lines changed

12 files changed

+209
-16
lines changed

phpstan.neon.dist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,5 @@ parameters:
5858
path: 'src/Symbol/SymbolsRegistry.php'
5959
- message: '#Stmt\:\:\$stmts#'
6060
path: 'src/PhpParser/NodeVisitor/ClassAliasStmtAppender.php'
61+
- message: '#PhpVersion::fromComponents#'
62+
path: 'tests/SpecFramework/SpecScenario.php'

src/Container.php

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@
3030
use PhpParser\PhpVersion;
3131
use PhpParser\PrettyPrinter\Standard;
3232
use Symfony\Component\Filesystem\Filesystem;
33+
use Webmozart\Assert\Assert;
3334

3435
final class Container
3536
{
3637
private Filesystem $filesystem;
3738
private ConfigurationFactory $configFactory;
3839
private Parser $parser;
40+
private ?PhpVersion $phpVersion = null;
3941
private Reflector $reflector;
4042
private ScoperFactory $scoperFactory;
4143
private EnrichedReflectorFactory $enrichedReflectorFactory;
@@ -64,11 +66,11 @@ public function getConfigurationFactory(): ConfigurationFactory
6466
return $this->configFactory;
6567
}
6668

67-
public function getScoperFactory(): ScoperFactory
69+
public function getScoperFactory(?PhpVersion $phpVersion = null): ScoperFactory
6870
{
6971
if (!isset($this->scoperFactory)) {
7072
$this->scoperFactory = new ScoperFactory(
71-
$this->getParser(),
73+
$this->getParser($phpVersion),
7274
$this->getEnrichedReflectorFactory(),
7375
$this->getPrinter(),
7476
);
@@ -77,21 +79,33 @@ public function getScoperFactory(): ScoperFactory
7779
return $this->scoperFactory;
7880
}
7981

80-
public function getParser(): Parser
82+
public function getParser(?PhpVersion $phpVersion = null): Parser
8183
{
8284
if (!isset($this->parser)) {
83-
$this->parser = $this->createParser();
85+
$this->phpVersion = $phpVersion;
86+
$this->parser = $this->createParser($phpVersion);
87+
}
88+
89+
$parserVersion = $this->phpVersion;
90+
91+
$parserMessage = 'Cannot use the existing parser: its PHP version is different than the one requested.';
92+
93+
if (null === $parserVersion) {
94+
Assert::null($phpVersion, $parserMessage);
95+
} else {
96+
Assert::notNull($phpVersion, $parserMessage);
97+
Assert::true($parserVersion->equals($phpVersion), $parserMessage);
8498
}
8599

86100
return $this->parser;
87101
}
88102

89-
private function createParser(): Parser
103+
private function createParser(?PhpVersion $phpVersion): Parser
90104
{
91-
$version = PhpVersion::getNewestSupported();
105+
$version = $phpVersion ?? PhpVersion::getHostVersion();
92106
$lexer = $version->isHostVersion() ? new Lexer() : new Emulative($version);
93107

94-
return $version->id >= 80000
108+
return $version->id >= 80_000
95109
? new Php8($lexer, $version)
96110
: new Php7($lexer, $version);
97111
}

tests/ContainerTest.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
namespace Humbug\PhpScoper;
1616

17+
use InvalidArgumentException;
18+
use PhpParser\PhpVersion;
1719
use PHPUnit\Framework\Attributes\CoversClass;
1820
use PHPUnit\Framework\Attributes\DataProvider;
1921
use PHPUnit\Framework\TestCase;
@@ -58,4 +60,72 @@ public static function provideServiceGetter(): iterable
5860
}
5961
}
6062
}
63+
64+
#[DataProvider('samePhpVersionProvider')]
65+
public function test_it_can_get_the_parser_if_the_version_does_not_change(
66+
?PhpVersion $version1,
67+
?PhpVersion $version2,
68+
): void {
69+
$container = new Container();
70+
71+
$container->getParser($version1);
72+
$container->getParser($version2);
73+
74+
$this->addToAssertionCount(1);
75+
}
76+
77+
public static function samePhpVersionProvider(): iterable
78+
{
79+
yield 'no PHP version configured' => [
80+
null,
81+
null,
82+
];
83+
84+
$phpVersion = PhpVersion::fromString('7.2');
85+
86+
yield 'same PHP version instance' => [
87+
$phpVersion,
88+
$phpVersion,
89+
];
90+
91+
yield 'same PHP version, different instances' => [
92+
PhpVersion::fromString('7.3'),
93+
PhpVersion::fromString('7.3'),
94+
];
95+
}
96+
97+
#[DataProvider('differentPhpVersionProvider')]
98+
public function test_it_cannot_create_two_different_versions_of_the_parser(
99+
?PhpVersion $version1,
100+
?PhpVersion $version2,
101+
): void {
102+
$container = new Container();
103+
104+
$container->getParser($version1);
105+
106+
$this->expectException(InvalidArgumentException::class);
107+
108+
$container->getParser($version2);
109+
}
110+
111+
public static function differentPhpVersionProvider(): iterable
112+
{
113+
$phpVersion = PhpVersion::fromString('7.2');
114+
$anotherPhpVersion = PhpVersion::fromString('7.3');
115+
116+
yield 'no PHP version configured' => [
117+
null,
118+
$phpVersion,
119+
];
120+
121+
yield 'no PHP version requested' => [
122+
$phpVersion,
123+
null,
124+
];
125+
126+
yield 'different PHP versions' => [
127+
$phpVersion,
128+
$anotherPhpVersion,
129+
];
130+
}
61131
}

tests/Scoper/PhpScoperSpecTest.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Humbug\PhpScoper\Symbol\Reflector;
2727
use Humbug\PhpScoper\Symbol\SymbolsRegistry;
2828
use PhpParser\Error as PhpParserError;
29+
use PhpParser\PhpVersion;
2930
use PHPUnit\Framework\Attributes\DataProvider;
3031
use PHPUnit\Framework\Attributes\Group;
3132
use PHPUnit\Framework\TestCase;
@@ -59,7 +60,7 @@ public function test_it_uses_the_right_specs_directory(): void
5960
#[DataProvider('provideValidFiles')]
6061
public function test_can_scope_valid_files(SpecScenario $scenario): void
6162
{
62-
$scenario->checkPHPVersionRequirements();
63+
$scenario->checkPHPVersionRequirements($scenario->phpVersionUsed);
6364

6465
$filePath = 'file.php';
6566
$symbolsRegistry = new SymbolsRegistry();
@@ -68,6 +69,7 @@ public function test_can_scope_valid_files(SpecScenario $scenario): void
6869
$scenario->prefix,
6970
$scenario->symbolsConfiguration,
7071
$symbolsRegistry,
72+
$scenario->getPhpParserVersion(),
7173
);
7274

7375
try {
@@ -109,7 +111,8 @@ public static function provideValidFiles(): iterable
109111
private static function createScoper(
110112
string $prefix,
111113
SymbolsConfiguration $symbolsConfiguration,
112-
SymbolsRegistry $symbolsRegistry
114+
SymbolsRegistry $symbolsRegistry,
115+
?PhpVersion $phpVersionUsed,
113116
): Scoper {
114117
$container = new Container();
115118

@@ -126,7 +129,7 @@ private static function createScoper(
126129
);
127130

128131
return new PhpScoper(
129-
$container->getParser(),
132+
$container->getParser($phpVersionUsed),
130133
new FakeScoper(),
131134
new TraverserFactory(
132135
$enrichedReflector,

tests/SpecFramework/Config/Meta.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public function __construct(
2323
public string $prefix = 'Humbug',
2424
public ?int $minPhpVersion = null,
2525
public ?int $maxPhpVersion = null,
26+
public ?int $phpVersionUsed = null,
2627
public bool $exposeGlobalConstants = false,
2728
public bool $exposeGlobalClasses = false,
2829
public bool $exposeGlobalFunctions = false,

tests/SpecFramework/Config/SpecWithConfig.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public static function create(
3333
?string $prefix = null,
3434
?int $minPhpVersion = null,
3535
?int $maxPhpVersion = null,
36+
?int $phpVersionUsed = null,
3637
?bool $exposeGlobalConstants = null,
3738
?bool $exposeGlobalClasses = null,
3839
?bool $exposeGlobalFunctions = null,
@@ -55,6 +56,7 @@ public static function create(
5556
$prefix,
5657
$minPhpVersion,
5758
$maxPhpVersion,
59+
$phpVersionUsed,
5860
$exposeGlobalConstants,
5961
$exposeGlobalClasses,
6062
$exposeGlobalFunctions,
@@ -77,6 +79,7 @@ private function __construct(
7779
public ?string $prefix,
7880
public ?int $minPhpVersion,
7981
public ?int $maxPhpVersion,
82+
public ?int $phpVersionUsed,
8083
public ?bool $exposeGlobalConstants,
8184
public ?bool $exposeGlobalClasses,
8285
public ?bool $exposeGlobalFunctions,

tests/SpecFramework/SpecParser.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ private static function parseSpec(
120120
return new SpecScenario(
121121
$specWithConfig->minPhpVersion ?? $meta->minPhpVersion ?? null,
122122
$specWithConfig->maxPhpVersion ?? $meta->maxPhpVersion ?? null,
123+
$specWithConfig->phpVersionUsed ?? $meta->phpVersionUsed ?? null,
123124
$file,
124125
$completeTitle,
125126
$specWithConfig->inputCode,

tests/SpecFramework/SpecScenario.php

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,20 @@
1616

1717
use Humbug\PhpScoper\Configuration\SymbolsConfiguration;
1818
use Humbug\PhpScoper\Symbol\SymbolsRegistry;
19+
use PhpParser\PhpVersion;
1920
use PHPUnit\Framework\Assert;
2021
use PHPUnit\Framework\SkippedWithMessageException;
2122
use PHPUnit\Framework\TestCase;
2223
use Throwable;
2324
use function usort;
25+
use const PHP_VERSION_ID;
2426

2527
final readonly class SpecScenario
2628
{
2729
public function __construct(
2830
public ?int $minPhpVersion,
2931
public ?int $maxPhpVersion,
32+
public ?int $phpVersionUsed,
3033
public string $file,
3134
public string $title,
3235
public string $inputCode,
@@ -38,12 +41,28 @@ public function __construct(
3841
) {
3942
}
4043

41-
public function checkPHPVersionRequirements(): void
44+
public function getPhpParserVersion(): ?PhpVersion
4245
{
46+
$phpVersionId = $this->phpVersionUsed;
47+
48+
if (null === $phpVersionId) {
49+
return null;
50+
}
51+
52+
$minorRemainder = $phpVersionId % 1000;
53+
$minor = $minorRemainder / 100;
54+
$major = ($phpVersionId - $minorRemainder) / 10_000;
55+
56+
return PhpVersion::fromComponents($major, $minor);
57+
}
58+
59+
public function checkPHPVersionRequirements(?int $phpVersionIdUsed): void
60+
{
61+
$phpVersionIdUsed ??= PHP_VERSION_ID;
4362
$minPhpVersion = $this->minPhpVersion;
4463
$maxPhpVersion = $this->maxPhpVersion;
4564

46-
if (null !== $minPhpVersion && $minPhpVersion > PHP_VERSION_ID) {
65+
if (null !== $minPhpVersion && $minPhpVersion > $phpVersionIdUsed) {
4766
throw new SkippedWithMessageException(
4867
sprintf(
4968
'Min PHP version not matched for spec "%s".',
@@ -52,7 +71,7 @@ public function checkPHPVersionRequirements(): void
5271
);
5372
}
5473

55-
if (null !== $maxPhpVersion && $maxPhpVersion <= PHP_VERSION_ID) {
74+
if (null !== $maxPhpVersion && $maxPhpVersion <= $phpVersionIdUsed) {
5675
throw new SkippedWithMessageException(
5776
sprintf(
5877
'Max PHP version not matched for spec "%s".',

tests/SpecFrameworkTest/Fixtures/complete-spec-file.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
prefix: 'Humbug',
2222
minPhpVersion: 70_200,
2323
maxPhpVersion: 80_300,
24+
phpVersionUsed: 70_400,
2425
exposeGlobalConstants: true,
2526
exposeGlobalClasses: true,
2627
exposeGlobalFunctions: true,
@@ -62,6 +63,7 @@
6263
prefix: 'AnotherPrefix',
6364
minPhpVersion: 70_300,
6465
maxPhpVersion: 80_200,
66+
phpVersionUsed: 80_300,
6567
exposeGlobalConstants: false,
6668
exposeGlobalClasses: false,
6769
exposeGlobalFunctions: false,

tests/SpecFrameworkTest/SpecParserTest.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public static function specProvider(): iterable
6868
self::FIXTURE_DIR.'/simple-spec-file.php',
6969
[
7070
'Fixtures/simple-spec-file.php: 0' => new SpecScenario(
71+
null,
7172
null,
7273
null,
7374
'Fixtures/simple-spec-file.php',
@@ -84,6 +85,7 @@ public static function specProvider(): iterable
8485
[],
8586
),
8687
'Fixtures/simple-spec-file.php: A spec with a title' => new SpecScenario(
88+
null,
8789
null,
8890
null,
8991
'Fixtures/simple-spec-file.php:33',
@@ -108,7 +110,8 @@ public static function specProvider(): iterable
108110
'Fixtures/complete-spec-file.php: Spec with default meta values' => new SpecScenario(
109111
70_200,
110112
80_300,
111-
'Fixtures/complete-spec-file.php:39',
113+
70_400,
114+
'Fixtures/complete-spec-file.php:40',
112115
'[Example of simple spec file] Spec with default meta values',
113116
$specCode,
114117
'Humbug',
@@ -132,7 +135,8 @@ public static function specProvider(): iterable
132135
'Fixtures/complete-spec-file.php: Spec with the more verbose form' => new SpecScenario(
133136
70_200,
134137
80_300,
135-
'Fixtures/complete-spec-file.php:49',
138+
70_400,
139+
'Fixtures/complete-spec-file.php:50',
136140
'[Example of simple spec file] Spec with the more verbose form',
137141
$specCode,
138142
'Humbug',
@@ -156,7 +160,8 @@ public static function specProvider(): iterable
156160
'Fixtures/complete-spec-file.php: Spec with overridden meta values' => new SpecScenario(
157161
70_300,
158162
80_200,
159-
'Fixtures/complete-spec-file.php:61',
163+
80_300,
164+
'Fixtures/complete-spec-file.php:62',
160165
'[Example of simple spec file] Spec with overridden meta values',
161166
$specCode,
162167
'AnotherPrefix',

0 commit comments

Comments
 (0)