Skip to content

Commit 559cee5

Browse files
authored
fix: verify PHPUnit before project discovery (#1775)
1 parent d690aa2 commit 559cee5

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

packages/runtime-playground/src/phpunit-command-handlers.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,18 @@ function pg_run_project_autoload_stage(string $project_autoload_file): void {
833833
}
834834
}
835835
836+
function pg_ensure_phpunit_harness_loaded(): void {
837+
if (class_exists('PHPUnit\\Framework\\TestSuite', true)) {
838+
return;
839+
}
840+
throw new RuntimeException(
841+
'PHPUnit harness is not initialized: PHPUnit\\Framework\\TestSuite is not available after bootstrap. '
842+
. 'In bootstrap-mode=project, the project bootstrap or project-autoload-file must load PHPUnit '
843+
. '(typically via the project vendor/autoload.php), or mount the WP Codebox PHPUnit harness with autoload-file=/wp-codebox-vendor/autoload.php. '
844+
. 'Aborting before test discovery to avoid an undefined-class fatal.'
845+
);
846+
}
847+
836848
function pg_run_install_stage(array $cfg) {
837849
global $argv, $pg_stage_output_buffering;
838850
pg_stage_begin('install');
@@ -1135,6 +1147,15 @@ try {
11351147
}
11361148
}
11371149
1150+
pg_stage_begin('verify_harness');
1151+
try {
1152+
pg_ensure_phpunit_harness_loaded();
1153+
pg_stage_ok('verify_harness');
1154+
} catch (Throwable $e) {
1155+
pg_stage_fail('verify_harness', $e);
1156+
exit(1);
1157+
}
1158+
11381159
${phpunitConfigDiscoveryPhp({
11391160
functionName: "wp_codebox_phpunit_parse_config",
11401161
logFunction: "pg_log",

tests/phpunit-project-autoload.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,68 @@ echo json_encode($cases);
9797
})
9898
}
9999

100+
function assertProjectBootstrapHarnessGuard(source: string): void {
101+
const tempDir = mkdtempSync(join(tmpdir(), "wp-codebox-phpunit-harness-guard-"))
102+
const stubFile = join(tempDir, "phpunit-testsuite-stub.php")
103+
const scriptPath = join(tempDir, "assert-harness-guard.php")
104+
105+
writeFileSync(stubFile, `<?php
106+
namespace PHPUnit\\Framework;
107+
class TestSuite {}
108+
`)
109+
110+
const ensureFn = extractPhpFunction(source, "pg_ensure_phpunit_harness_loaded")
111+
112+
writeFileSync(scriptPath, `<?php
113+
function pg_log($msg) {}
114+
function pg_stage_begin($stage) {}
115+
function pg_stage_ok($stage) {}
116+
function pg_stage_fail($stage, Throwable $e) {}
117+
${ensureFn}
118+
119+
if (class_exists('PHPUnit\\Framework\\TestSuite', false)) {
120+
throw new RuntimeException('precondition failed: PHPUnit\\Framework\\TestSuite must not be preloaded in the test environment');
121+
}
122+
123+
$reached_testsuite = false;
124+
$boundary_message = '';
125+
try {
126+
pg_ensure_phpunit_harness_loaded();
127+
$reached_testsuite = true;
128+
} catch (RuntimeException $e) {
129+
$boundary_message = $e->getMessage();
130+
} catch (Throwable $e) {
131+
throw new RuntimeException('REGRESSION: guard threw non-RuntimeException: ' . get_class($e) . ': ' . $e->getMessage());
132+
}
133+
134+
if ($reached_testsuite) {
135+
throw new RuntimeException('REGRESSION: harness guard did not fail when PHPUnit was unavailable; TestSuite construction would be reached');
136+
}
137+
foreach (array('PHPUnit\\Framework\\TestSuite', 'bootstrap-mode=project', 'project-autoload-file', 'autoload-file=/wp-codebox-vendor/autoload.php') as $needle) {
138+
if (strpos($boundary_message, $needle) === false) {
139+
throw new RuntimeException('REGRESSION: boundary error missing actionable hint: ' . $needle . '; message=' . $boundary_message);
140+
}
141+
}
142+
143+
spl_autoload_register(function ($class) {
144+
if ($class !== 'PHPUnit\\Framework\\TestSuite') {
145+
return;
146+
}
147+
require_once ${phpString(realpathSync(stubFile))};
148+
});
149+
150+
try {
151+
pg_ensure_phpunit_harness_loaded();
152+
} catch (Throwable $e) {
153+
throw new RuntimeException('REGRESSION: harness guard failed even though a project autoloader provides PHPUnit: ' . $e->getMessage());
154+
}
155+
156+
echo "BOUNDARY_OK\n";
157+
`)
158+
159+
assert.equal(execFileSync("php", [scriptPath], { encoding: "utf8" }), "BOUNDARY_OK\n")
160+
}
161+
100162
const recipe = buildWordPressPhpunitRecipe({
101163
pluginSlug: "woocommerce",
102164
extra_plugins: [{
@@ -191,6 +253,15 @@ assert.equal(projectModeCode.match(/return \$return_values\(\);/g)?.length, 3)
191253
assertPhpunitParseConfigFallbacksReturnFiveTuple(projectModeCode, "wp_codebox_phpunit_parse_config", "pg_log")
192254
assertSelectedTestFileResolution(projectModeCode)
193255

256+
assert.ok(projectModeCode.includes("function pg_ensure_phpunit_harness_loaded(): void"))
257+
assert.ok(projectModeCode.includes("PHPUnit harness is not initialized"))
258+
assert.ok(projectModeCode.includes("pg_stage_begin('verify_harness')"))
259+
const verifyHarnessIndex = projectModeCode.indexOf("pg_stage_begin('verify_harness')")
260+
const projectModeTestsuiteIndex = projectModeCode.indexOf("$suite = new PHPUnit\\Framework\\TestSuite(")
261+
assert.ok(verifyHarnessIndex > 0, "verify_harness stage must be present")
262+
assert.ok(projectModeTestsuiteIndex > verifyHarnessIndex, "harness verification must precede TestSuite construction")
263+
assertProjectBootstrapHarnessGuard(projectModeCode)
264+
194265
let capturedDefaultProjectCode = ""
195266
await runPhpunitCommand({
196267
artifactRoot: mkdtempSync(join(tmpdir(), "wp-codebox-phpunit-artifacts-")),

0 commit comments

Comments
 (0)