Skip to content

Commit 7575ebd

Browse files
Fix resolving mounted symlinks in secondary PHP instances (#2444)
## Motivation for the change, related issues It appears that plugin symlinks are resolved by the primary PHP instance but then fail to be resolved within a secondary PHP instance. ## Implementation details Mounts /internal/symlinks in secondary PHP instances. Fixes #2405 ## Testing Instructions (or ideally a Blueprint) - CI, including new unit test(s) --------- Co-authored-by: Adam Zieliński <[email protected]>
1 parent 64561e8 commit 7575ebd

File tree

4 files changed

+79
-3
lines changed

4 files changed

+79
-3
lines changed

packages/php-wasm/node/src/lib/load-runtime.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export async function loadNodeRuntime(
9595
* in the Emscripten's filesystem and mount the OS directory
9696
* to the Emscripten filesystem.
9797
*
98-
* The directory is mounted to the `/internals/symlinks` directory to avoid
98+
* The directory is mounted to the `/internal/symlinks` directory to avoid
9999
* conflicts with existing VFS directories.
100100
* We can set a arbitrary mount path because readlink is the source of truth
101101
* for the path and Emscripten will accept it as if it was the real link path.
@@ -111,7 +111,7 @@ export async function loadNodeRuntime(
111111
)
112112
);
113113
const symlinkPath = joinPaths(
114-
`/internals/symlinks`,
114+
`/internal/symlinks`,
115115
absoluteSourcePath
116116
);
117117
if (

packages/playground/cli/src/run-cli.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,8 @@ export interface RunCLIServer extends AsyncDisposable {
336336
playground: RemoteAPI<PlaygroundCliWorker>;
337337
server: Server;
338338
[Symbol.asyncDispose](): Promise<void>;
339+
// Expose the number of worker threads to the test runner.
340+
workerThreadCount: number;
339341
}
340342

341343
export async function runCLI(args: RunCLIArgs): Promise<RunCLIServer> {
@@ -565,6 +567,7 @@ export async function runCLI(args: RunCLIArgs): Promise<RunCLIServer> {
565567
);
566568
await new Promise((resolve) => server.close(resolve));
567569
},
570+
workerThreadCount: totalWorkerCount,
568571
};
569572
} catch (error) {
570573
if (!args.debug) {

packages/playground/cli/tests/run-cli.spec.ts

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ import { mkdtemp, writeFile } from 'node:fs/promises';
77
import { tmpdir } from 'node:os';
88
import { promisify } from 'node:util';
99
import { exec } from 'node:child_process';
10-
import { readdirSync } from 'node:fs';
10+
import {
11+
mkdirSync,
12+
readdirSync,
13+
writeFileSync,
14+
symlinkSync,
15+
unlinkSync,
16+
existsSync,
17+
} from 'node:fs';
1118
import { createHash } from 'node:crypto';
1219
import { MinifiedWordPressVersionsList } from '@wp-playground/wordpress-builds';
1320

@@ -235,5 +242,70 @@ describe('run-cli', () => {
235242
*/
236243
expect(await getDirectoryChecksum(tmpDir)).toBe(checksum);
237244
});
245+
246+
test('should be able to follow external symlinks in primary and secondary PHP instances', async () => {
247+
// TODO: Make sure test always uses a single worker.
248+
// TODO: Is there a way to confirm we are testing use of a non-primary PHP instance?
249+
const tmpDir = await mkdtemp(
250+
path.join(tmpdir(), 'playground-test-')
251+
);
252+
writeFileSync(
253+
path.join(tmpDir, 'sleep.php'),
254+
'<?php sleep(1); echo "Slept"; '
255+
);
256+
const symlinkPath = path.join(
257+
import.meta.dirname,
258+
'mount-examples',
259+
'symlinking',
260+
'symlinked-script'
261+
);
262+
263+
mkdirSync( path.dirname( symlinkPath ), { recursive: true } );
264+
265+
try {
266+
if (existsSync(symlinkPath)) {
267+
unlinkSync(symlinkPath);
268+
}
269+
// TODO: Confirm that symlink target is outside of current working dir tree.
270+
symlinkSync(tmpDir, symlinkPath);
271+
cliServer = await runCLI({
272+
debug: true,
273+
command: 'server',
274+
followSymlinks: true,
275+
'mount-before-install': [
276+
{
277+
hostPath: symlinkPath,
278+
vfsPath: '/wordpress/wp-content/test-script',
279+
},
280+
],
281+
});
282+
expect(cliServer.workerThreadCount).toBe(1);
283+
// Make multiple simultaneous requests to force the use of a secondary PHP instance.
284+
// TODO: Find way to confirm this.
285+
const responses = await Promise.all([
286+
cliServer.playground.request({
287+
url: '/wp-content/test-script/sleep.php',
288+
method: 'GET',
289+
}),
290+
cliServer.playground.request({
291+
url: '/wp-content/test-script/sleep.php',
292+
method: 'GET',
293+
}),
294+
// Test a third request to hopefully test more than one secondary instance.
295+
cliServer.playground.request({
296+
url: '/wp-content/test-script/sleep.php',
297+
method: 'GET',
298+
}),
299+
]);
300+
responses.forEach((response) => {
301+
expect(response.httpStatusCode).toBe(200);
302+
expect(response.text).toContain('Slept');
303+
});
304+
} finally {
305+
if (existsSync(symlinkPath)) {
306+
unlinkSync(symlinkPath);
307+
}
308+
}
309+
});
238310
});
239311
});

packages/playground/wordpress/src/boot.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ export async function bootRequestHandler(options: BootRequestHandlerOptions) {
266266
'/tmp',
267267
requestHandler.documentRoot,
268268
'/internal/shared',
269+
'/internal/symlinks',
269270
]);
270271
}
271272

0 commit comments

Comments
 (0)