Download test/workspace.test.js from webml-community/MiniCPM5-2B-WebGPU-Pi: direct link, hf CLI and curl.
- Browser
- Download file 9.07 kB
-
https://huggingface.co/spaces/webml-community/MiniCPM5-2B-WebGPU-Pi/resolve/main/test/workspace.test.js
- Command line
-
hf download hf://spaces/webml-community/MiniCPM5-2B-WebGPU-Pi/test/workspace.test.js
-
curl -L -o workspace.test.js https://huggingface.co/spaces/webml-community/MiniCPM5-2B-WebGPU-Pi/resolve/main/test/workspace.test.js
9.07 kB
| import test from 'node:test'; | |
| import assert from 'node:assert/strict'; | |
| import { mkdtemp, readFile, writeFile, rm } from 'node:fs/promises'; | |
| import { tmpdir } from 'node:os'; | |
| import { join } from 'node:path'; | |
| import { | |
| createWorkspace, createStore, DEFAULT_FILES, MAX_EDITOR_BYTES, WORKSPACE_DB_NAME, | |
| } from '../src/workspace.js'; | |
| const tool = (workspace, name) => workspace.tools.find(tool => tool.name === name); | |
| test('real shell combines the demo data with pipes, awk, jq, redirects, and subshells', async () => { | |
| const workspace = createWorkspace(); | |
| const response = await tool(workspace, 'bash').execute({ | |
| command: `cat sales.csv | tail -n +2 | awk -F, '{total += $2} END {print total}' > units.txt | |
| printf '%s\\n' "$(jq -r '.[0].product' products.json):$(cat units.txt)"`, | |
| }); | |
| assert.equal(response.content, 'Notebook:50\n\nExit code: 0'); | |
| assert.deepEqual(response.details, { exitCode: 0 }); | |
| assert.equal(await workspace.read('units.txt'), '50\n'); | |
| }); | |
| test('each command resets cwd and variables while keeping file changes', async () => { | |
| const workspace = createWorkspace(); | |
| const bash = tool(workspace, 'bash'); | |
| await bash.execute({ command: 'mkdir nested; cd nested; VALUE=private; echo saved > note.txt' }); | |
| const response = await bash.execute({ command: 'printf "%s:%s:%s" "$PWD" "${VALUE-unset}" "$(cat nested/note.txt)"' }); | |
| assert.equal(response.content, '/workspace:unset:saved\nExit code: 0'); | |
| }); | |
| test('file tools use plain JSON Schema and text results with exact replacement semantics', async () => { | |
| const workspace = createWorkspace(); | |
| for (const { parameters } of workspace.tools) { | |
| assert.equal(parameters.type, 'object'); | |
| assert.equal(parameters.additionalProperties, false); | |
| assert.doesNotThrow(() => JSON.stringify(parameters)); | |
| } | |
| await tool(workspace, 'write').execute({ path: 'notes/new.txt', content: 'hello 🌍\nhello again' }); | |
| const response = await tool(workspace, 'read').execute({ path: 'notes/new.txt' }); | |
| assert.equal(response.content, 'hello 🌍\nhello again'); | |
| assert.equal(response.details.path, '/workspace/notes/new.txt'); | |
| await assert.rejects(tool(workspace, 'edit').execute({ path: 'notes/new.txt', oldText: 'hello', newText: 'bad' }), /exactly once/); | |
| await assert.rejects(tool(workspace, 'edit').execute({ path: 'notes/new.txt', oldText: '', newText: 'bad' }), /exactly once/); | |
| await tool(workspace, 'edit').execute({ path: 'notes/new.txt', oldText: 'hello 🌍', newText: '$& is literal' }); | |
| assert.equal(await workspace.read('notes/new.txt'), '$& is literal\nhello again'); | |
| }); | |
| test('a failed command rejects with its exit code and useful output', async () => { | |
| const workspace = createWorkspace(); | |
| await assert.rejects(tool(workspace, 'bash').execute({ command: 'echo problem >&2; exit 7' }), error => { | |
| assert.equal(error.exitCode, 7); | |
| assert.match(error.message, /STDERR:\nproblem/); | |
| assert.match(error.message, /Exit code: 7/); | |
| return true; | |
| }); | |
| assert.equal((await tool(workspace, 'bash').execute({ command: 'echo recovered' })).details.exitCode, 0); | |
| }); | |
| test('editor limit counts UTF-8 bytes and tool output is truncated without changing the file', async () => { | |
| const workspace = createWorkspace(); | |
| await assert.rejects(workspace.write('large.txt', '🌍'.repeat(MAX_EDITOR_BYTES / 4 + 1)), /up to 1 MiB/); | |
| const original = 'x'.repeat(7_000); | |
| await workspace.write('large.txt', original); | |
| const response = await tool(workspace, 'read').execute({ path: 'large.txt' }); | |
| assert.match(response.content, /Output truncated at 6,000 characters/); | |
| assert.equal(await workspace.read('large.txt'), original); | |
| }); | |
| test('snapshot and serialization preserve default files and exclude generated command/device entries', async () => { | |
| const workspace = createWorkspace(); | |
| assert.deepEqual(await workspace.snapshot(), DEFAULT_FILES); | |
| const entries = await workspace.serialize(); | |
| assert.ok(entries.some(([path]) => path === '/workspace')); | |
| assert.ok(!entries.some(([path]) => /^\/(?:bin|usr\/bin|dev|proc)(?:\/|$)/.test(path))); | |
| const restored = createWorkspace(undefined, entries); | |
| assert.deepEqual(await restored.snapshot(), DEFAULT_FILES); | |
| }); | |
| test('binary contents, executable modes, empty directories, and symbolic links survive serialization', async () => { | |
| const workspace = createWorkspace({}); | |
| await workspace.ready; | |
| const binary = new Uint8Array([0, 255, 128, 65, 10, 0, 254]); | |
| await workspace.bash.fs.writeFile('/workspace/binary.dat', binary); | |
| await workspace.bash.fs.chmod('/workspace/binary.dat', 0o640); | |
| await workspace.bash.fs.mkdir('/workspace/empty'); | |
| await workspace.bash.fs.chmod('/workspace/empty', 0o750); | |
| await workspace.write('hello.sh', '#!/bin/bash\necho hello\n'); | |
| await workspace.bash.fs.chmod('/workspace/hello.sh', 0o755); | |
| await workspace.bash.fs.symlink('hello.sh', '/workspace/hello-link'); | |
| const restored = createWorkspace(undefined, await workspace.serialize()); | |
| await restored.ready; | |
| assert.deepEqual(await restored.bash.fs.readFileBuffer('/workspace/binary.dat'), binary); | |
| assert.equal((await restored.bash.fs.stat('/workspace/binary.dat')).mode & 0o777, 0o640); | |
| assert.equal((await restored.bash.fs.stat('/workspace/empty')).mode & 0o777, 0o750); | |
| assert.equal((await restored.bash.fs.stat('/workspace/hello.sh')).mode & 0o777, 0o755); | |
| assert.equal(await restored.bash.fs.readlink('/workspace/hello-link'), 'hello.sh'); | |
| assert.equal(await restored.read('hello-link'), '#!/bin/bash\necho hello\n'); | |
| assert.equal((await tool(restored, 'bash').execute({ command: 'bash hello-link' })).content, 'hello\n\nExit code: 0'); | |
| }); | |
| test('aborting a live shell interrupts it and later commands remain usable', async () => { | |
| const workspace = createWorkspace(); | |
| const controller = new AbortController(); | |
| const running = tool(workspace, 'bash').execute({ command: 'sleep 10; echo should-not-run' }, { signal: controller.signal }); | |
| const timer = setTimeout(() => controller.abort(new DOMException('Stopped by user', 'AbortError')), 20); | |
| try { | |
| await assert.rejects(running, /Stopped by user|abort/i); | |
| } finally { | |
| clearTimeout(timer); | |
| } | |
| assert.equal((await tool(workspace, 'bash').execute({ command: 'echo ready' })).content, 'ready\n\nExit code: 0'); | |
| }); | |
| test('pre-aborted file operations do not change files', async () => { | |
| const workspace = createWorkspace(); | |
| const controller = new AbortController(); | |
| controller.abort(new DOMException('Stopped', 'AbortError')); | |
| for (const [name, args] of [ | |
| ['write', { path: 'sales.csv', content: 'lost' }], | |
| ['edit', { path: 'sales.csv', oldText: 'Notebook', newText: 'lost' }], | |
| ['read', { path: 'sales.csv' }], | |
| ['bash', { command: 'echo lost > sales.csv' }], | |
| ]) await assert.rejects(tool(workspace, name).execute(args, { signal: controller.signal }), /Stopped/); | |
| assert.equal(await workspace.read('sales.csv'), DEFAULT_FILES['/workspace/sales.csv']); | |
| }); | |
| test('shell cannot use host binaries or network commands and does not call fetch', async () => { | |
| const workspace = createWorkspace(); | |
| const originalFetch = globalThis.fetch; | |
| let calls = 0; | |
| globalThis.fetch = async () => { calls++; throw new Error('Unexpected network call'); }; | |
| try { | |
| for (const command of ['curl https://example.com', 'wget https://example.com', 'node -e "process.exit(0)"', 'python3 -c "print(1)"', 'npm --version', 'js "console.log(1)"']) { | |
| await assert.rejects(tool(workspace, 'bash').execute({ command }), /command not (?:found|available)/); | |
| } | |
| assert.equal(calls, 0); | |
| } finally { | |
| globalThis.fetch = originalFetch; | |
| } | |
| }); | |
| test('shell absolute paths stay in the virtual filesystem', async () => { | |
| const temporary = await mkdtemp(join(tmpdir(), 'minicpm-workspace-test-')); | |
| const hostFile = join(temporary, 'private.txt'); | |
| try { | |
| await writeFile(hostFile, 'host-only-secret'); | |
| const workspace = createWorkspace(); | |
| await assert.rejects(tool(workspace, 'bash').execute({ command: `cat '${hostFile}'` }), /No such file|ENOENT|not found/); | |
| await tool(workspace, 'bash').execute({ command: `mkdir -p '${temporary}'; printf virtual > '${hostFile}'` }); | |
| assert.equal(await workspace.read(hostFile), 'virtual'); | |
| assert.equal(await readFile(hostFile, 'utf8'), 'host-only-secret'); | |
| } finally { | |
| await rm(temporary, { recursive: true, force: true }); | |
| } | |
| }); | |
| test('shell rejects oversized source and excessive virtual filesystem allocation', async () => { | |
| const workspace = createWorkspace({}); | |
| await assert.rejects(tool(workspace, 'bash').execute({ command: '#'.repeat(131_073) }), /script input size limit/); | |
| await workspace.ready; | |
| await assert.rejects(workspace.bash.fs.writeFile('/workspace/excessive.bin', new Uint8Array(16 * 1024 * 1024 + 1)), /ENOSPC|byte limit/); | |
| }); | |
| test('new storage name avoids legacy Pi message data and missing IndexedDB is explicit', async () => { | |
| assert.equal(WORKSPACE_DB_NAME, 'minicpm5-gguf-workspace'); | |
| await assert.rejects(createStore(null), /Browser storage is unavailable/); | |
| }); | |