Skip to content

Commit d18f5fa

Browse files
committed
Remove node created by mount after unmount
1 parent deb9f85 commit d18f5fa

File tree

2 files changed

+140
-60
lines changed

2 files changed

+140
-60
lines changed

packages/php-wasm/node/src/lib/node-fs-mount.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function createNodeFsMountHandler(localPath: string): MountHandler {
1818
* PHP-WASM source: https://github.com/WordPress/wordpress-playground/blob/5821cee231f452d050fd337b99ad0b26ebda487e/packages/php-wasm/compile/php/Dockerfile#L2148
1919
*/
2020
let lookup;
21+
let unlinkPath: string | undefined;
2122
try {
2223
lookup = FS.lookupPath(vfsMountPoint);
2324
} catch (e) {
@@ -27,10 +28,11 @@ export function createNodeFsMountHandler(localPath: string): MountHandler {
2728
throw e;
2829
}
2930
if (statSync(localPath).isFile()) {
31+
unlinkPath = vfsMountPoint;
3032
FS.writeFile(vfsMountPoint, '');
3133
} else if (statSync(localPath).isDirectory()) {
32-
FS.mkdirTree(dirname(vfsMountPoint));
3334
FS.mkdirTree(vfsMountPoint);
35+
unlinkPath = vfsMountPoint;
3436
} else {
3537
throw new Error(
3638
'Unsupported file type. PHP-wasm supports only symlinks that link to files, directories, or symlinks.'
@@ -44,8 +46,14 @@ export function createNodeFsMountHandler(localPath: string): MountHandler {
4446
}
4547
FS.mount(FS.filesystems['NODEFS'], { root: localPath }, vfsMountPoint);
4648
return () => {
47-
// TODO: Delete the mount point if was created during the mount.
4849
FS!.unmount(vfsMountPoint);
50+
if (unlinkPath) {
51+
if (FS.isDir(lookup.node.mode)) {
52+
FS.rmdir(unlinkPath);
53+
} else {
54+
FS.unlink(unlinkPath);
55+
}
56+
}
4957
};
5058
};
5159
}

packages/php-wasm/node/src/test/mounting.spec.ts

Lines changed: 130 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createNodeFsMountHandler, loadNodeRuntime } from '..';
22
import { PHP } from '@php-wasm/universal';
33
import { RecommendedPHPVersion } from '@wp-playground/common';
4-
import path from 'path';
4+
import path, { dirname } from 'path';
55
import fs from 'fs';
66

77
describe('Mounting', () => {
@@ -14,72 +14,74 @@ describe('Mounting', () => {
1414
php.exit();
1515
});
1616

17-
describe('Basic mounting functionality', () => {
18-
it('Should mount a file with exact content match', async () => {
19-
const testFilePath = path.join(
20-
__dirname,
21-
'test-data',
22-
'long-post-body.txt'
23-
);
17+
it('Should mount a file with exact content match', async () => {
18+
const testFilePath = path.join(
19+
__dirname,
20+
'test-data',
21+
'long-post-body.txt'
22+
);
2423

25-
await php.mount(
26-
'/single-file.txt',
27-
createNodeFsMountHandler(testFilePath)
28-
);
24+
const unmount = await php.mount(
25+
'/single-file.txt',
26+
createNodeFsMountHandler(testFilePath)
27+
);
2928

30-
const vfsContent = await php.readFileAsText('/single-file.txt');
31-
const localContent = fs.readFileSync(testFilePath, 'utf8');
32-
expect(vfsContent).toEqual(localContent);
33-
});
29+
const vfsContent = await php.readFileAsText('/single-file.txt');
30+
const localContent = fs.readFileSync(testFilePath, 'utf8');
31+
expect(vfsContent).toEqual(localContent);
3432

35-
it('Should mount nested directories with recursive structure matching', async () => {
36-
const testDataPath = path.join(__dirname, 'test-data');
37-
await php.mount(
38-
'/nested-test',
39-
createNodeFsMountHandler(testDataPath)
40-
);
33+
await unmount();
34+
expect(php.isFile('/single-file.txt')).toBe(false);
35+
});
4136

42-
// Recursively compare directory structure
43-
const compareDirectories = (vfsPath: string, localPath: string) => {
44-
if (!fs.existsSync(localPath)) return;
37+
it('Should mount nested directories with recursive structure matching', async () => {
38+
const testDataPath = path.join(__dirname, 'test-data');
39+
const unmount = await php.mount(
40+
'/nested-test',
41+
createNodeFsMountHandler(testDataPath)
42+
);
4543

46-
const localFiles = fs.readdirSync(localPath);
47-
const vfsFiles = php.listFiles(vfsPath);
48-
expect(vfsFiles.sort()).toEqual(localFiles.sort());
44+
// Recursively compare directory structure
45+
const compareDirectories = (vfsPath: string, localPath: string) => {
46+
if (!fs.existsSync(localPath)) return;
4947

50-
localFiles.forEach((file) => {
51-
const localFilePath = path.join(localPath, file);
52-
const vfsFilePath = `${vfsPath}/${file}`;
53-
const localStats = fs.statSync(localFilePath);
48+
const localFiles = fs.readdirSync(localPath);
49+
const vfsFiles = php.listFiles(vfsPath);
50+
expect(vfsFiles.sort()).toEqual(localFiles.sort());
5451

55-
expect(php.isFile(vfsFilePath)).toBe(localStats.isFile());
56-
expect(php.isDir(vfsFilePath)).toBe(
57-
localStats.isDirectory()
58-
);
52+
localFiles.forEach((file) => {
53+
const localFilePath = path.join(localPath, file);
54+
const vfsFilePath = `${vfsPath}/${file}`;
55+
const localStats = fs.statSync(localFilePath);
5956

60-
if (localStats.isDirectory()) {
61-
compareDirectories(vfsFilePath, localFilePath);
62-
}
63-
});
64-
};
65-
66-
compareDirectories('/nested-test', testDataPath);
67-
68-
// Test specific nested file content
69-
const nestedFilePath =
70-
'/nested-test/nested-symlinked-folder/nested-document.txt';
71-
const localNestedPath = path.join(
72-
testDataPath,
73-
'nested-symlinked-folder',
74-
'nested-document.txt'
75-
);
57+
expect(php.isFile(vfsFilePath)).toBe(localStats.isFile());
58+
expect(php.isDir(vfsFilePath)).toBe(localStats.isDirectory());
7659

77-
if (fs.existsSync(localNestedPath)) {
78-
const vfsContent = await php.readFileAsText(nestedFilePath);
79-
const localContent = fs.readFileSync(localNestedPath, 'utf8');
80-
expect(vfsContent).toEqual(localContent);
81-
}
82-
});
60+
if (localStats.isDirectory()) {
61+
compareDirectories(vfsFilePath, localFilePath);
62+
}
63+
});
64+
};
65+
66+
compareDirectories('/nested-test', testDataPath);
67+
68+
// Test specific nested file content
69+
const nestedFilePath =
70+
'/nested-test/nested-symlinked-folder/nested-document.txt';
71+
const localNestedPath = path.join(
72+
testDataPath,
73+
'nested-symlinked-folder',
74+
'nested-document.txt'
75+
);
76+
77+
if (fs.existsSync(localNestedPath)) {
78+
const vfsContent = await php.readFileAsText(nestedFilePath);
79+
const localContent = fs.readFileSync(localNestedPath, 'utf8');
80+
expect(vfsContent).toEqual(localContent);
81+
}
82+
83+
await unmount();
84+
expect(php.isDir('/nested-test')).toBe(false);
8385
});
8486

8587
describe('File types and system operations', () => {
@@ -163,4 +165,74 @@ describe('Mounting', () => {
163165
expect(vfsPhpFiles.sort()).toEqual(localFiles.sort());
164166
});
165167
});
168+
169+
describe('Unmounting', () => {
170+
it('Should unmount a file and remove created node from VFS', async () => {
171+
const testFilePath = path.join(
172+
__dirname,
173+
'test-data',
174+
'long-post-body.txt'
175+
);
176+
177+
const unmount = await php.mount(
178+
'/single-file.txt',
179+
createNodeFsMountHandler(testFilePath)
180+
);
181+
182+
expect(php.isFile('/single-file.txt')).toBe(true);
183+
184+
await unmount();
185+
expect(php.isFile('/single-file.txt')).toBe(false);
186+
});
187+
188+
it('Should unmount a directory and remove created node from VFS', async () => {
189+
const testDataPath = path.join(__dirname, 'test-data');
190+
const unmount = await php.mount(
191+
'/nested-test',
192+
createNodeFsMountHandler(testDataPath)
193+
);
194+
195+
expect(php.isDir('/nested-test')).toBe(true);
196+
197+
await unmount();
198+
expect(php.isDir('/nested-test')).toBe(false);
199+
});
200+
201+
it('Should unmount a file, but not remove the parent directory from VFS if it was created manually', async () => {
202+
const testFilePath = path.join(
203+
__dirname,
204+
'test-data',
205+
'long-post-body.txt'
206+
);
207+
208+
const mountPoint = '/sub-dir/single-file.txt';
209+
210+
await php.mkdir(dirname(mountPoint));
211+
212+
const unmount = await php.mount(
213+
mountPoint,
214+
createNodeFsMountHandler(testFilePath)
215+
);
216+
217+
expect(php.isFile(mountPoint)).toBe(true);
218+
219+
await unmount();
220+
expect(php.isDir(dirname(mountPoint))).toBe(true);
221+
});
222+
223+
it('Should unmount a directory, but not remove the parent directory from VFS if it was created manually', async () => {
224+
const testDataPath = path.join(__dirname, 'test-data');
225+
226+
await php.mkdir('/nested-test');
227+
const unmount = await php.mount(
228+
'/nested-test',
229+
createNodeFsMountHandler(testDataPath)
230+
);
231+
232+
expect(php.isDir('/nested-test')).toBe(true);
233+
234+
await unmount();
235+
expect(php.isDir('/nested-test')).toBe(true);
236+
});
237+
});
166238
});

0 commit comments

Comments
 (0)