diff --git a/package.json b/package.json index 6669cc7..2218f53 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "theme-check-normalization-smoke": "tsx scripts/theme-check-normalization-smoke.ts", "phpunit-diagnostic-artifact-smoke": "tsx scripts/phpunit-diagnostic-artifact-smoke.ts", "plugin-check-normalization-smoke": "tsx scripts/plugin-check-normalization-smoke.ts", + "bench-bootstrap-files-smoke": "tsx scripts/bench-bootstrap-files-smoke.ts", "recipe-bench-smoke": "tsx scripts/recipe-bench-smoke.ts", "recipe-browser-bench-metrics-smoke": "tsx scripts/recipe-browser-bench-metrics-smoke.ts", "recipe-browser-smoke": "tsx scripts/recipe-browser-smoke.ts", diff --git a/packages/runtime-core/src/command-registry.ts b/packages/runtime-core/src/command-registry.ts index ecd726c..8c49caf 100644 --- a/packages/runtime-core/src/command-registry.ts +++ b/packages/runtime-core/src/command-registry.ts @@ -74,6 +74,7 @@ export const commandRegistry = [ { name: "warmup", description: "Warmup iterations before measurement.", format: "non-negative integer" }, { name: "dependency-slugs", description: "Comma-separated plugin dependency slugs to load.", format: "comma-separated slugs" }, { name: "env-json", description: "Benchmark environment object.", format: "JSON object" }, + { name: "bootstrap-files-json", description: "Component-relative bootstrap file fallbacks; the first existing file is loaded before workloads execute.", format: "JSON array" }, { name: "workloads-json", description: "Explicit workload list.", format: "JSON array" }, ], outputShape: "Benchmark results JSON envelope with component_id, iterations, and scenarios.", diff --git a/packages/runtime-playground/src/bench-command-handlers.ts b/packages/runtime-playground/src/bench-command-handlers.ts index bfce4c0..d2a1561 100644 --- a/packages/runtime-playground/src/bench-command-handlers.ts +++ b/packages/runtime-playground/src/bench-command-handlers.ts @@ -5,6 +5,7 @@ export interface BenchRunCodeOptions { warmupIterations: number dependencySlugs: string[] env: Record + bootstrapFiles: string[] workloads: unknown[] wpCliBridge?: { url: string; token: string } } @@ -19,6 +20,7 @@ $iterations = max(1, (int) ${JSON.stringify(String(options.iterations))}); $warmup_iterations = max(0, (int) ${JSON.stringify(String(options.warmupIterations))}); $dependency_slugs = json_decode(${JSON.stringify(JSON.stringify(options.dependencySlugs))}, true); $bench_env = json_decode(${JSON.stringify(JSON.stringify(options.env))}, true); +$bootstrap_files = json_decode(${JSON.stringify(JSON.stringify(options.bootstrapFiles))}, true); $configured_workloads = json_decode(${JSON.stringify(JSON.stringify(options.workloads))}, true); $wp_cli_bridge_url = ${JSON.stringify(options.wpCliBridge?.url ?? null)}; $wp_cli_bridge_token = ${JSON.stringify(options.wpCliBridge?.token ?? null)}; @@ -155,6 +157,21 @@ foreach ($plugins_to_activate as $plugin_to_activate) { throw new RuntimeException($activation->get_error_message()); } } +$loaded_bootstrap_file = ''; +foreach (is_array($bootstrap_files) ? $bootstrap_files : array() as $bootstrap_file) { + if (!is_string($bootstrap_file) || $bootstrap_file === '' || str_contains($bootstrap_file, '..')) { + continue; + } + $bootstrap_path = $plugin_path . '/' . ltrim($bootstrap_file, '/'); + if (file_exists($bootstrap_path)) { + require_once $bootstrap_path; + $loaded_bootstrap_file = $bootstrap_file; + break; + } +} +if (is_array($bootstrap_files) && count($bootstrap_files) > 0 && $loaded_bootstrap_file === '') { + throw new RuntimeException('No configured wordpress.bench bootstrap files were found.'); +} if (!empty($plugins_to_activate)) { do_action('plugins_loaded'); do_action('init'); diff --git a/packages/runtime-playground/src/wordpress-command-runners.ts b/packages/runtime-playground/src/wordpress-command-runners.ts index 448388f..3d2f166 100644 --- a/packages/runtime-playground/src/wordpress-command-runners.ts +++ b/packages/runtime-playground/src/wordpress-command-runners.ts @@ -199,12 +199,13 @@ export async function runBenchCommand({ const warmupIterations = nonNegativeIntegerArg(args, "warmup", 1) const dependencySlugs = commaListArg(args, "dependency-slugs") const env = jsonObjectArg(args, "env-json") + const bootstrapFiles = jsonArrayArg(args, "bootstrap-files-json").filter((file): file is string => typeof file === "string") const workloads = jsonArrayArg(args, "workloads-json") const bridge = benchWorkloadsUseWpCli(workloads) ? await createRuntimeWpCliBridge(server) : undefined let response: PlaygroundRunResponse try { response = await runPlaygroundCommand("wordpress.bench", server, { - code: bootstrapPhpCode(runtimeSpec, benchRunCode({ componentId, pluginSlug, iterations, warmupIterations, dependencySlugs, env, workloads, wpCliBridge: bridge }), []), + code: bootstrapPhpCode(runtimeSpec, benchRunCode({ componentId, pluginSlug, iterations, warmupIterations, dependencySlugs, env, bootstrapFiles, workloads, wpCliBridge: bridge }), []), }) assertPlaygroundResponseOk("wordpress.bench", response) } finally { diff --git a/scripts/bench-bootstrap-files-smoke.ts b/scripts/bench-bootstrap-files-smoke.ts new file mode 100644 index 0000000..862c74c --- /dev/null +++ b/scripts/bench-bootstrap-files-smoke.ts @@ -0,0 +1,27 @@ +import assert from "node:assert/strict" +import { benchRunCode } from "../packages/runtime-playground/src/bench-command-handlers.js" + +const code = benchRunCode({ + componentId: "fixture-plugin", + pluginSlug: "fixture-plugin", + iterations: 1, + warmupIterations: 0, + dependencySlugs: [], + env: {}, + bootstrapFiles: ["lib/compat/new.php", "lib/compat/old.php"], + workloads: [], +}) + +assert.match(code, /\$bootstrap_files = json_decode/) +assert.match(code, /lib\/compat\/new\.php/) +assert.match(code, /lib\/compat\/old\.php/) +assert.match(code, /foreach \(is_array\(\$bootstrap_files\)/) +assert.match(code, /require_once \$bootstrap_path/) +assert.match(code, /break;/) +assert.match(code, /do_action\('plugins_loaded'\)/) +assert.ok( + code.indexOf("require_once $bootstrap_path") < code.indexOf("do_action('plugins_loaded')"), + "bootstrap files should load before synthetic plugins_loaded/init hooks" +) + +console.log("Bench bootstrap files smoke passed")