wasm-idle 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -37
- package/dist/playground/assetBridge.js +106 -31
- package/dist/playground/assets.d.ts +2 -2
- package/dist/playground/c3.js +22 -0
- package/dist/playground/c3Memory.d.ts +19 -0
- package/dist/playground/c3Memory.js +117 -0
- package/dist/playground/cachedClang.js +143 -34
- package/dist/playground/clangAssetIntegrity.js +6 -6
- package/dist/playground/clangWorkerProtocol.d.ts +12 -1
- package/dist/playground/cobol.d.ts +3 -0
- package/dist/playground/cobol.js +10 -1
- package/dist/playground/commonlispAssets.d.ts +2 -2
- package/dist/playground/compiledArtifactCache.d.ts +3 -1
- package/dist/playground/compiledArtifactCache.js +31 -5
- package/dist/playground/container/ContainerExplorer.svelte +307 -0
- package/dist/playground/container/ContainerExplorer.svelte.d.ts +14 -0
- package/dist/playground/container/binding.d.ts +26 -0
- package/dist/playground/container/binding.js +247 -0
- package/dist/playground/container/clang-runtime.d.ts +38 -0
- package/dist/playground/container/clang-runtime.js +343 -0
- package/dist/playground/container/client.d.ts +15 -0
- package/dist/playground/container/client.js +189 -0
- package/dist/playground/container/explorer.d.ts +31 -0
- package/dist/playground/container/explorer.js +112 -0
- package/dist/playground/container/owner.worker.js +390 -0
- package/dist/playground/container/protocol.d.ts +73 -0
- package/dist/playground/container/protocol.js +28 -0
- package/dist/playground/container/release.d.ts +31 -0
- package/dist/playground/container/release.js +612 -0
- package/dist/playground/container/runtime-adapter.d.ts +73 -0
- package/dist/playground/container/runtime-adapter.js +474 -0
- package/dist/playground/container/shell-control.d.ts +49 -0
- package/dist/playground/container/shell-control.js +303 -0
- package/dist/playground/container/workspace.d.ts +8 -0
- package/dist/playground/container/workspace.js +29 -0
- package/dist/playground/d.d.ts +6 -1
- package/dist/playground/d.js +83 -5
- package/dist/playground/dotnet.js +6 -8
- package/dist/playground/fortran.d.ts +3 -0
- package/dist/playground/fortran.js +10 -1
- package/dist/playground/go.js +14 -3
- package/dist/playground/goDebugInstrumentation.js +184 -54
- package/dist/playground/java.js +3 -2
- package/dist/playground/javaRuntimeErrors.d.ts +9 -0
- package/dist/playground/javaRuntimeErrors.js +60 -0
- package/dist/playground/lean4Assets.d.ts +4 -4
- package/dist/playground/lfortran.js +10 -0
- package/dist/playground/lfortranAssets.d.ts +2 -2
- package/dist/playground/nim.js +22 -0
- package/dist/playground/objectivec.d.ts +3 -0
- package/dist/playground/objectivec.js +11 -2
- package/dist/playground/options.d.ts +2 -0
- package/dist/playground/python.js +8 -0
- package/dist/playground/runtimeAssetCache.d.ts +1 -32
- package/dist/playground/runtimeAssetCache.js +1 -193
- package/dist/playground/runtimeSession.d.ts +2 -2
- package/dist/playground/runtimeSession.js +11 -11
- package/dist/playground/rust.d.ts +6 -0
- package/dist/playground/rust.js +103 -6
- package/dist/playground/staticStdinRing.d.ts +2 -1
- package/dist/playground/staticStdinRing.js +21 -10
- package/dist/playground/staticWorkerRuntime.d.ts +15 -1
- package/dist/playground/staticWorkerRuntime.js +119 -17
- package/dist/playground/tinygo.d.ts +3 -0
- package/dist/playground/tinygo.js +81 -6
- package/dist/playground/v.d.ts +3 -0
- package/dist/playground/v.js +10 -1
- package/dist/playground/wasmAwkVersion.d.ts +13 -13
- package/dist/playground/wasmAwkVersion.js +4 -4
- package/dist/playground/wasmBqnVersion.d.ts +2 -2
- package/dist/playground/wasmBqnVersion.js +2 -2
- package/dist/playground/wasmC3Version.d.ts +2 -2
- package/dist/playground/wasmC3Version.js +2 -2
- package/dist/playground/wasmClojureScriptVersion.d.ts +2 -2
- package/dist/playground/wasmClojureScriptVersion.js +2 -2
- package/dist/playground/wasmCommonLispVersion.d.ts +2 -2
- package/dist/playground/wasmCommonLispVersion.js +2 -2
- package/dist/playground/wasmDIntegrity.d.ts +5 -5
- package/dist/playground/wasmDIntegrity.js +5 -5
- package/dist/playground/wasmDVersion.d.ts +16 -1
- package/dist/playground/wasmDVersion.js +16 -1
- package/dist/playground/wasmDebugVersion.d.ts +1 -1
- package/dist/playground/wasmDebugVersion.js +1 -1
- package/dist/playground/wasmForthVersion.d.ts +2 -2
- package/dist/playground/wasmForthVersion.js +2 -2
- package/dist/playground/wasmGleamVersion.d.ts +2 -2
- package/dist/playground/wasmGleamVersion.js +2 -2
- package/dist/playground/wasmGrainVersion.d.ts +2 -2
- package/dist/playground/wasmGrainVersion.js +2 -2
- package/dist/playground/wasmJVersion.d.ts +2 -2
- package/dist/playground/wasmJVersion.js +2 -2
- package/dist/playground/wasmJanetVersion.js +2 -2
- package/dist/playground/wasmJuliaVersion.js +2 -2
- package/dist/playground/wasmLean4Version.d.ts +5 -5
- package/dist/playground/wasmLean4Version.js +5 -5
- package/dist/playground/wasmLfortranVersion.d.ts +2 -2
- package/dist/playground/wasmLfortranVersion.js +2 -2
- package/dist/playground/wasmNimVersion.d.ts +9 -9
- package/dist/playground/wasmNimVersion.js +6 -6
- package/dist/playground/wasmOcamlVersion.d.ts +2 -2
- package/dist/playground/wasmOcamlVersion.js +1 -1
- package/dist/playground/wasmPascalVersion.js +2 -2
- package/dist/playground/wasmPerlVersion.js +2 -2
- package/dist/playground/wasmPrologVersion.d.ts +2 -2
- package/dist/playground/wasmPrologVersion.js +2 -2
- package/dist/playground/wasmReScriptVersion.d.ts +2 -2
- package/dist/playground/wasmReScriptVersion.js +2 -2
- package/dist/playground/wasmRustVersion.d.ts +8 -8
- package/dist/playground/wasmRustVersion.js +7 -7
- package/dist/playground/wasmTclVersion.d.ts +4 -4
- package/dist/playground/wasmTclVersion.js +2 -2
- package/dist/playground/wasmTinyGoVersion.d.ts +14 -14
- package/dist/playground/wasmTinyGoVersion.js +14 -14
- package/dist/playground/wasmTypeScriptVersion.d.ts +5 -5
- package/dist/playground/wasmTypeScriptVersion.js +5 -5
- package/dist/playground/worker/cachedClangCompile.js +52 -4
- package/dist/playground/worker/cachedClangExecute.js +6 -5
- package/dist/playground/worker/clang.js +32 -10
- package/dist/playground/worker/cobol.js +2 -2
- package/dist/playground/worker/d.js +12 -5
- package/dist/playground/worker/fortran.js +2 -2
- package/dist/playground/worker/go.js +16 -5
- package/dist/playground/worker/haskell.js +10 -7
- package/dist/playground/worker/java.js +75 -10
- package/dist/playground/worker/lua.js +2 -3
- package/dist/playground/worker/php.js +15 -9
- package/dist/playground/worker/python.js +46 -9
- package/dist/playground/worker/pythonExecution.js +17 -1
- package/dist/playground/worker/pythonWorkspaceFiles.d.ts +6 -0
- package/dist/playground/worker/pythonWorkspaceFiles.js +26 -0
- package/dist/playground/worker/pythonWorkspaceImports.d.ts +4 -0
- package/dist/playground/worker/pythonWorkspaceImports.js +129 -0
- package/dist/playground/worker/r.js +13 -6
- package/dist/playground/worker/rust.js +34 -2
- package/dist/playground/worker/sqlite.js +4 -3
- package/dist/playground/worker/tinygo.js +11 -3
- package/dist/playground/worker/typescript.js +2 -3
- package/dist/playground/worker/v.js +2 -2
- package/dist/playground/worker/wat.js +2 -3
- package/dist/playground/worker/zig.js +20 -6
- package/dist/playground/workerSession.d.ts +1 -28
- package/dist/playground/workerSession.js +1 -164
- package/package.json +20 -13
- package/dist/playground/worker/python.runtime.cases.js +0 -374
- /package/dist/playground/{worker/python.runtime.cases.d.ts → container/owner.worker.d.ts} +0 -0
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
// @vitest-environment node
|
|
2
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import { PYTHON_FLUSH_HOOK_FACTORY } from './pythonStdio';
|
|
4
|
-
const workerAssets = vi.hoisted(() => ({
|
|
5
|
-
configureWorkerRuntimeAssetAllowlist: vi.fn(),
|
|
6
|
-
configureWorkerRuntimeAssets: vi.fn(),
|
|
7
|
-
handleWorkerAssetMessage: vi.fn(() => false),
|
|
8
|
-
loadWorkerRuntimeAsset: vi.fn(),
|
|
9
|
-
hasWorkerRuntimeModuleBridge: vi.fn(() => true),
|
|
10
|
-
loadWorkerRuntimeModule: vi.fn(async () => new WebAssembly.Module(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0])))
|
|
11
|
-
}));
|
|
12
|
-
vi.mock('$lib/playground/worker/assets', () => workerAssets);
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
vi.resetModules();
|
|
15
|
-
vi.restoreAllMocks();
|
|
16
|
-
vi.unstubAllGlobals();
|
|
17
|
-
workerAssets.configureWorkerRuntimeAssetAllowlist.mockReset();
|
|
18
|
-
workerAssets.configureWorkerRuntimeAssets.mockReset();
|
|
19
|
-
workerAssets.handleWorkerAssetMessage.mockReset().mockReturnValue(false);
|
|
20
|
-
workerAssets.loadWorkerRuntimeAsset.mockReset();
|
|
21
|
-
workerAssets.loadWorkerRuntimeModule.mockClear();
|
|
22
|
-
});
|
|
23
|
-
const packageAsset = 'demo-1.0-py3-none-any.whl';
|
|
24
|
-
let nextRuntimeModuleId = 0;
|
|
25
|
-
async function createRuntimeHarness({ version = '314.0.7', lock = {
|
|
26
|
-
info: {
|
|
27
|
-
arch: 'wasm32',
|
|
28
|
-
abi_version: 'test-abi',
|
|
29
|
-
platform: 'test-platform',
|
|
30
|
-
python: '3.14.2',
|
|
31
|
-
version
|
|
32
|
-
},
|
|
33
|
-
packages: {
|
|
34
|
-
demo: {
|
|
35
|
-
depends: [],
|
|
36
|
-
file_name: packageAsset,
|
|
37
|
-
imports: ['demo'],
|
|
38
|
-
install_dir: 'site',
|
|
39
|
-
name: 'demo',
|
|
40
|
-
package_type: 'package',
|
|
41
|
-
sha256: 'a'.repeat(64),
|
|
42
|
-
version: '1.0'
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
} } = {}) {
|
|
46
|
-
const postMessage = vi.fn();
|
|
47
|
-
const runtimeOptions = [];
|
|
48
|
-
const moduleEvaluations = [];
|
|
49
|
-
const installFlushHooks = Object.assign((drain) => Object.assign(() => drain(), { destroy: vi.fn() }), { destroy: vi.fn() });
|
|
50
|
-
const pyodide = {
|
|
51
|
-
setStdin: vi.fn(),
|
|
52
|
-
setStdout: vi.fn(),
|
|
53
|
-
setStderr: vi.fn(),
|
|
54
|
-
FS: { mkdirTree: vi.fn(), writeFile: vi.fn() },
|
|
55
|
-
runPython: vi.fn((source) => {
|
|
56
|
-
if (source !== PYTHON_FLUSH_HOOK_FACTORY)
|
|
57
|
-
throw new Error('Unexpected Python initialization');
|
|
58
|
-
return installFlushHooks;
|
|
59
|
-
}),
|
|
60
|
-
loadPackagesFromImports: vi.fn(async () => undefined),
|
|
61
|
-
runPythonAsync: vi.fn(async () => {
|
|
62
|
-
const readyName = Object.keys(globalThis).find((name) => name.startsWith('__wasm_idle_python_execution_ready_'));
|
|
63
|
-
if (readyName)
|
|
64
|
-
globalThis[readyName]();
|
|
65
|
-
}),
|
|
66
|
-
setInterruptBuffer: vi.fn()
|
|
67
|
-
};
|
|
68
|
-
const moduleSources = {
|
|
69
|
-
'pyodide.asm.mjs': `
|
|
70
|
-
globalThis.__pythonModuleEvaluations.push('asm');
|
|
71
|
-
export default async function createPyodideModule() { return {}; }
|
|
72
|
-
`,
|
|
73
|
-
'pyodide.mjs': `
|
|
74
|
-
globalThis.__pythonModuleEvaluations.push('entry');
|
|
75
|
-
export const version = ${JSON.stringify(version)};
|
|
76
|
-
export async function loadPyodide(options) {
|
|
77
|
-
if (typeof options.createPyodideModule !== 'function') throw new Error('module factory missing');
|
|
78
|
-
await options.createPyodideModule();
|
|
79
|
-
globalThis.__pythonRuntimeOptions.push(options);
|
|
80
|
-
return globalThis.__pythonRuntimeMock;
|
|
81
|
-
}
|
|
82
|
-
`,
|
|
83
|
-
'pyodide-lock.json': JSON.stringify(lock)
|
|
84
|
-
};
|
|
85
|
-
workerAssets.loadWorkerRuntimeAsset.mockImplementation(async (asset) => ({
|
|
86
|
-
bytes: new TextEncoder().encode(moduleSources[asset]),
|
|
87
|
-
mimeType: asset.endsWith('.json') ? 'application/json' : 'text/javascript'
|
|
88
|
-
}));
|
|
89
|
-
class ModuleBlob {
|
|
90
|
-
parts;
|
|
91
|
-
options;
|
|
92
|
-
constructor(parts, options) {
|
|
93
|
-
this.parts = parts;
|
|
94
|
-
this.options = options;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
vi.stubGlobal('self', globalThis);
|
|
98
|
-
vi.stubGlobal('postMessage', postMessage);
|
|
99
|
-
vi.stubGlobal('__pythonRuntimeMock', pyodide);
|
|
100
|
-
vi.stubGlobal('__pythonRuntimeOptions', runtimeOptions);
|
|
101
|
-
vi.stubGlobal('__pythonModuleEvaluations', moduleEvaluations);
|
|
102
|
-
vi.stubGlobal('Blob', ModuleBlob);
|
|
103
|
-
vi.spyOn(URL, 'createObjectURL').mockImplementation((source) => {
|
|
104
|
-
const bytes = source.parts
|
|
105
|
-
.map((part) => typeof part === 'string' ? Buffer.from(part) : Buffer.from(new Uint8Array(part)))
|
|
106
|
-
.reduce((left, right) => Buffer.concat([left, right]));
|
|
107
|
-
return `data:text/javascript;base64,${bytes.toString('base64')}#${++nextRuntimeModuleId}`;
|
|
108
|
-
});
|
|
109
|
-
const revokeObjectURL = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => { });
|
|
110
|
-
await import('./python');
|
|
111
|
-
return {
|
|
112
|
-
onmessage: globalThis.self.onmessage,
|
|
113
|
-
postMessage,
|
|
114
|
-
pyodide,
|
|
115
|
-
revokeObjectURL,
|
|
116
|
-
runtimeOptions,
|
|
117
|
-
moduleSources,
|
|
118
|
-
moduleEvaluations
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
describe('Python worker runtime dispatch', () => {
|
|
122
|
-
it('rejects an asm module without the exported factory before initializing Python', async () => {
|
|
123
|
-
const { moduleSources, onmessage, postMessage, runtimeOptions } = await createRuntimeHarness();
|
|
124
|
-
moduleSources['pyodide.asm.mjs'] = 'export const unrelated = true;';
|
|
125
|
-
await onmessage({ data: {
|
|
126
|
-
load: true,
|
|
127
|
-
assets: { baseUrl: 'https://assets.example.test/python/', useAssetBridge: true }
|
|
128
|
-
} });
|
|
129
|
-
expect(runtimeOptions).toHaveLength(0);
|
|
130
|
-
expect(postMessage).toHaveBeenCalledWith({ error: 'Pyodide module factory is unavailable' });
|
|
131
|
-
});
|
|
132
|
-
it.each([
|
|
133
|
-
{
|
|
134
|
-
name: 'bridged',
|
|
135
|
-
assetConfig: { baseUrl: 'https://wasm-idle.invalid/python/', maxAssetBytes: 4096, useAssetBridge: true },
|
|
136
|
-
expectedAssets: ['pyodide.asm.mjs', 'pyodide.mjs'],
|
|
137
|
-
expectedPackageBaseUrl: 'https://wasm-idle.invalid/python/'
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
name: 'direct',
|
|
141
|
-
assetConfig: { baseUrl: 'https://assets.example.test/python/', maxAssetBytes: 4096, useAssetBridge: false },
|
|
142
|
-
expectedAssets: ['pyodide.asm.mjs', 'pyodide.mjs', 'pyodide-lock.json'],
|
|
143
|
-
expectedPackageBaseUrl: 'https://cdn.jsdelivr.net/pyodide/v314.0.7/full/'
|
|
144
|
-
}
|
|
145
|
-
])('loads $name runtime assets with bounded package configuration and settles empty code', async ({ assetConfig, expectedAssets, expectedPackageBaseUrl }) => {
|
|
146
|
-
const { onmessage, postMessage, pyodide, revokeObjectURL, runtimeOptions } = await createRuntimeHarness();
|
|
147
|
-
await onmessage({ data: { load: true, assets: assetConfig } });
|
|
148
|
-
expect(workerAssets.configureWorkerRuntimeAssets).toHaveBeenCalledWith(assetConfig);
|
|
149
|
-
expect(workerAssets.loadWorkerRuntimeAsset.mock.calls.map(([asset]) => asset)).toEqual(expectedAssets);
|
|
150
|
-
expect(runtimeOptions).toHaveLength(1);
|
|
151
|
-
expect(runtimeOptions[0]).toMatchObject({ indexURL: assetConfig.baseUrl, packageBaseUrl: expectedPackageBaseUrl, createPyodideModule: expect.any(Function) });
|
|
152
|
-
if (assetConfig.useAssetBridge) {
|
|
153
|
-
expect(runtimeOptions[0]).not.toHaveProperty('lockFileContents');
|
|
154
|
-
expect(workerAssets.configureWorkerRuntimeAssetAllowlist).not.toHaveBeenCalled();
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
expect(runtimeOptions[0]).toHaveProperty('lockFileContents.packages.demo.file_name', packageAsset);
|
|
158
|
-
expect(workerAssets.configureWorkerRuntimeAssetAllowlist).toHaveBeenCalledWith({
|
|
159
|
-
baseUrl: expectedPackageBaseUrl,
|
|
160
|
-
assets: [packageAsset],
|
|
161
|
-
runtimeAssets: ['pyodide.mjs', 'pyodide.asm.mjs', 'pyodide-lock.json', 'pyodide.asm.wasm', 'python_stdlib.zip']
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
expect(revokeObjectURL).toHaveBeenCalledTimes(2);
|
|
165
|
-
expect(postMessage).toHaveBeenCalledWith({ load: true });
|
|
166
|
-
postMessage.mockClear();
|
|
167
|
-
await onmessage({ data: {
|
|
168
|
-
code: '', prepare: false,
|
|
169
|
-
buffer: new SharedArrayBuffer(4096), debugBuffer: new SharedArrayBuffer(4096),
|
|
170
|
-
watchBuffer: new SharedArrayBuffer(4096), watchResultBuffer: new SharedArrayBuffer(4096),
|
|
171
|
-
interrupt: new SharedArrayBuffer(1), workspaceFiles: []
|
|
172
|
-
} });
|
|
173
|
-
expect(pyodide.runPythonAsync).toHaveBeenCalledOnce();
|
|
174
|
-
expect(postMessage).toHaveBeenCalledWith({ results: true });
|
|
175
|
-
});
|
|
176
|
-
it.each([
|
|
177
|
-
{ name: 'untrusted runtime version', version: '314.0.7/../../untrusted', lock: undefined, error: 'Pyodide runtime version is invalid', expectedAssets: ['pyodide.asm.mjs', 'pyodide.mjs', 'pyodide-lock.json'] },
|
|
178
|
-
{ name: 'unsafe lock package path', version: '314.0.7', lock: { packages: { demo: { file_name: '../untrusted.whl' } } }, error: 'Python runtime lock file has an unsafe package asset name', expectedAssets: ['pyodide.asm.mjs', 'pyodide.mjs', 'pyodide-lock.json'] }
|
|
179
|
-
])('fails closed for an $name', async ({ version, lock, error, expectedAssets }) => {
|
|
180
|
-
const { onmessage, postMessage, runtimeOptions } = await createRuntimeHarness({ version, ...(lock ? { lock } : {}) });
|
|
181
|
-
await onmessage({ data: { load: true, assets: { baseUrl: 'https://assets.example.test/python/', maxAssetBytes: 4096, useAssetBridge: false } } });
|
|
182
|
-
expect(workerAssets.loadWorkerRuntimeAsset.mock.calls.map(([asset]) => asset)).toEqual(expectedAssets);
|
|
183
|
-
expect(workerAssets.configureWorkerRuntimeAssetAllowlist).not.toHaveBeenCalled();
|
|
184
|
-
expect(runtimeOptions).toHaveLength(0);
|
|
185
|
-
expect(postMessage).toHaveBeenCalledWith({ error });
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
const directAssets = { baseUrl: 'https://assets.example.test/python/', maxAssetBytes: 4096, useAssetBridge: false };
|
|
189
|
-
function executionData(code = 'import demo', workspaceFiles = [], activePath = 'main.py') {
|
|
190
|
-
return { code, activePath, workspaceFiles,
|
|
191
|
-
buffer: new SharedArrayBuffer(4096), debugBuffer: new SharedArrayBuffer(4096),
|
|
192
|
-
watchBuffer: new SharedArrayBuffer(4096), watchResultBuffer: new SharedArrayBuffer(4096), interrupt: new SharedArrayBuffer(1) };
|
|
193
|
-
}
|
|
194
|
-
describe('Python preparation output', () => {
|
|
195
|
-
it.each([false, true])('reports loading and preparation only through progress messages (log=%s)', async (log) => {
|
|
196
|
-
const { onmessage, postMessage, pyodide } = await createRuntimeHarness();
|
|
197
|
-
await onmessage({ data: { load: true, assets: directAssets, log } });
|
|
198
|
-
expect(postMessage.mock.calls.map(([message]) => message)).toEqual([
|
|
199
|
-
{ progress: { percent: 2, stage: 'Loading Pyodide module' } },
|
|
200
|
-
{ progress: { percent: 100, stage: 'Pyodide runtime ready' } }, { load: true }
|
|
201
|
-
]);
|
|
202
|
-
postMessage.mockClear();
|
|
203
|
-
await onmessage({ data: { ...executionData(), prepare: true, log } });
|
|
204
|
-
expect(pyodide.loadPackagesFromImports).toHaveBeenCalledOnce();
|
|
205
|
-
expect(pyodide.runPythonAsync).not.toHaveBeenCalled();
|
|
206
|
-
expect(postMessage.mock.calls.map(([message]) => message)).toEqual([
|
|
207
|
-
{ progress: { percent: 5, stage: 'Preparing Python workspace' } },
|
|
208
|
-
{ progress: { percent: 15, stage: 'Resolving Python imports' } },
|
|
209
|
-
{ progress: { percent: 100, stage: 'Python packages ready' } }, { results: true }
|
|
210
|
-
]);
|
|
211
|
-
});
|
|
212
|
-
it('preserves user output that matches a former preparation message', async () => {
|
|
213
|
-
const { onmessage, postMessage, pyodide } = await createRuntimeHarness();
|
|
214
|
-
await onmessage({ data: { load: true, assets: directAssets } });
|
|
215
|
-
const data = executionData('print("Done.")');
|
|
216
|
-
await onmessage({ data: { ...data, prepare: true, log: false } });
|
|
217
|
-
postMessage.mockClear();
|
|
218
|
-
const runPython = pyodide.runPythonAsync.getMockImplementation();
|
|
219
|
-
pyodide.runPythonAsync.mockImplementationOnce(async () => {
|
|
220
|
-
await runPython();
|
|
221
|
-
// Native Python writes bytes through Pyodide, not an emulated print callback.
|
|
222
|
-
pyodide.setStdout.mock.calls.at(-1)[0].write(new TextEncoder().encode('Done.\n'));
|
|
223
|
-
});
|
|
224
|
-
await onmessage({ data: { ...data, log: false } });
|
|
225
|
-
expect(postMessage.mock.calls.map(([message]) => message).filter((message) => 'output' in message)).toEqual([{ output: 'Done.\n' }]);
|
|
226
|
-
expect(postMessage).toHaveBeenCalledWith({ results: true });
|
|
227
|
-
});
|
|
228
|
-
it('preserves preparation errors without writing status text to user output', async () => {
|
|
229
|
-
const { onmessage, postMessage, pyodide } = await createRuntimeHarness();
|
|
230
|
-
await onmessage({ data: { load: true, assets: directAssets } });
|
|
231
|
-
postMessage.mockClear();
|
|
232
|
-
pyodide.loadPackagesFromImports.mockRejectedValueOnce(new Error('package unavailable'));
|
|
233
|
-
await onmessage({ data: { ...executionData(), prepare: true, log: false } });
|
|
234
|
-
expect(postMessage.mock.calls.map(([message]) => message)).toEqual([
|
|
235
|
-
{ progress: { percent: 5, stage: 'Preparing Python workspace' } },
|
|
236
|
-
{ progress: { percent: 15, stage: 'Resolving Python imports' } }, { error: 'package unavailable' }
|
|
237
|
-
]);
|
|
238
|
-
expect(pyodide.runPythonAsync).not.toHaveBeenCalled();
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
describe('Python bootstrap scheduling', () => {
|
|
242
|
-
it.each([false, true])('fetches independent assets concurrently and evaluates asm first (bridge=%s)', async (useAssetBridge) => {
|
|
243
|
-
const harness = await createRuntimeHarness();
|
|
244
|
-
const loadAsset = workerAssets.loadWorkerRuntimeAsset.getMockImplementation();
|
|
245
|
-
const pending = new Map();
|
|
246
|
-
workerAssets.loadWorkerRuntimeAsset.mockImplementation((asset) => new Promise((resolve) => {
|
|
247
|
-
pending.set(asset, () => resolve(loadAsset(asset)));
|
|
248
|
-
}));
|
|
249
|
-
const loading = harness.onmessage({ data: { load: true, assets: { ...directAssets, useAssetBridge } } });
|
|
250
|
-
const expected = ['pyodide.asm.mjs', 'pyodide.mjs'];
|
|
251
|
-
if (!useAssetBridge)
|
|
252
|
-
expected.push('pyodide-lock.json');
|
|
253
|
-
expect([...pending.keys()]).toEqual(expected);
|
|
254
|
-
expect(harness.moduleEvaluations).toEqual([]);
|
|
255
|
-
expect(harness.runtimeOptions).toHaveLength(0);
|
|
256
|
-
expect(harness.postMessage).not.toHaveBeenCalledWith({ load: true });
|
|
257
|
-
// Reverse completion order must not change module evaluation dependencies.
|
|
258
|
-
for (const asset of [...expected].reverse())
|
|
259
|
-
pending.get(asset)();
|
|
260
|
-
await loading;
|
|
261
|
-
expect(harness.moduleEvaluations).toEqual(['asm', 'entry']);
|
|
262
|
-
expect(harness.runtimeOptions).toHaveLength(1);
|
|
263
|
-
expect(harness.revokeObjectURL).toHaveBeenCalledTimes(2);
|
|
264
|
-
expect(harness.postMessage).toHaveBeenCalledWith({ load: true });
|
|
265
|
-
});
|
|
266
|
-
it.each(['pyodide.asm.mjs', 'pyodide.mjs', 'pyodide-lock.json'])('fails closed and can retry when %s cannot be downloaded', async (failedAsset) => {
|
|
267
|
-
const harness = await createRuntimeHarness();
|
|
268
|
-
const loadAsset = workerAssets.loadWorkerRuntimeAsset.getMockImplementation();
|
|
269
|
-
workerAssets.loadWorkerRuntimeAsset.mockImplementation((asset) => asset === failedAsset ? Promise.reject(new Error('asset unavailable')) : loadAsset(asset));
|
|
270
|
-
await harness.onmessage({ data: { load: true, assets: directAssets } });
|
|
271
|
-
expect(harness.runtimeOptions).toHaveLength(0);
|
|
272
|
-
expect(harness.moduleEvaluations).toEqual([]);
|
|
273
|
-
expect(workerAssets.configureWorkerRuntimeAssetAllowlist).not.toHaveBeenCalled();
|
|
274
|
-
expect(harness.postMessage).toHaveBeenCalledWith({ error: 'asset unavailable' });
|
|
275
|
-
expect(harness.postMessage).not.toHaveBeenCalledWith({ load: true });
|
|
276
|
-
workerAssets.loadWorkerRuntimeAsset.mockImplementation(loadAsset);
|
|
277
|
-
await harness.onmessage({ data: { load: true, assets: directAssets } });
|
|
278
|
-
expect(harness.runtimeOptions).toHaveLength(1);
|
|
279
|
-
expect(harness.postMessage).toHaveBeenCalledWith({ load: true });
|
|
280
|
-
});
|
|
281
|
-
it('revokes both module URLs when the entry module throws during evaluation', async () => {
|
|
282
|
-
const harness = await createRuntimeHarness();
|
|
283
|
-
harness.moduleSources['pyodide.mjs'] = "throw new Error('entry failed');";
|
|
284
|
-
await harness.onmessage({ data: { load: true, assets: directAssets } });
|
|
285
|
-
expect(harness.revokeObjectURL).toHaveBeenCalledTimes(2);
|
|
286
|
-
expect(harness.runtimeOptions).toHaveLength(0);
|
|
287
|
-
expect(harness.postMessage).toHaveBeenCalledWith({ error: 'entry failed' });
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
describe('Python prepare-to-run reuse', () => {
|
|
291
|
-
it.each([false, true])('reuses import analysis only for the next matching run (bridge=%s)', async (useAssetBridge) => {
|
|
292
|
-
const { onmessage, pyodide, postMessage } = await createRuntimeHarness();
|
|
293
|
-
await onmessage({ data: { load: true, assets: { ...directAssets, useAssetBridge } } });
|
|
294
|
-
const data = executionData('import demo', [{ path: 'helper.py', content: 'import demo' }]);
|
|
295
|
-
await onmessage({ data: { ...data, prepare: true } });
|
|
296
|
-
expect(pyodide.loadPackagesFromImports).toHaveBeenCalledOnce();
|
|
297
|
-
expect(pyodide.runPythonAsync).not.toHaveBeenCalled();
|
|
298
|
-
await onmessage({ data });
|
|
299
|
-
expect(pyodide.loadPackagesFromImports).toHaveBeenCalledOnce();
|
|
300
|
-
expect(pyodide.runPythonAsync).toHaveBeenCalledOnce();
|
|
301
|
-
// Rewriting files is intentional: preparation does not change workspace semantics.
|
|
302
|
-
expect(pyodide.FS.writeFile).toHaveBeenCalledTimes(2);
|
|
303
|
-
expect(postMessage).toHaveBeenCalledWith({ results: true });
|
|
304
|
-
await onmessage({ data });
|
|
305
|
-
expect(pyodide.loadPackagesFromImports).toHaveBeenCalledTimes(2);
|
|
306
|
-
expect(pyodide.runPythonAsync).toHaveBeenCalledTimes(2);
|
|
307
|
-
});
|
|
308
|
-
it.each([
|
|
309
|
-
['active source', executionData('import another', [{ path: 'a.py', content: 'import demo' }])],
|
|
310
|
-
['active path', executionData('import demo', [{ path: 'a.py', content: 'import demo' }], 'other.py')],
|
|
311
|
-
['workspace source', executionData('import demo', [{ path: 'a.py', content: 'import other' }])],
|
|
312
|
-
['workspace path', executionData('import demo', [{ path: 'b.py', content: 'import demo' }])],
|
|
313
|
-
['removed file', executionData('import demo')]
|
|
314
|
-
])('reanalyzes imports after a changed %s', async (_label, changed) => {
|
|
315
|
-
const { onmessage, pyodide } = await createRuntimeHarness();
|
|
316
|
-
await onmessage({ data: { load: true, assets: directAssets } });
|
|
317
|
-
const data = executionData('import demo', [{ path: 'a.py', content: 'import demo' }]);
|
|
318
|
-
await onmessage({ data: { ...data, prepare: true } });
|
|
319
|
-
await onmessage({ data: changed });
|
|
320
|
-
expect(pyodide.loadPackagesFromImports).toHaveBeenCalledTimes(2);
|
|
321
|
-
expect(pyodide.runPythonAsync).toHaveBeenCalledOnce();
|
|
322
|
-
});
|
|
323
|
-
it('does not confuse source boundaries that produce the same joined text', async () => {
|
|
324
|
-
const { onmessage, pyodide } = await createRuntimeHarness();
|
|
325
|
-
await onmessage({ data: { load: true, assets: directAssets } });
|
|
326
|
-
await onmessage({ data: { ...executionData('import demo', [{ path: 'a.py', content: 'import other' }]), prepare: true } });
|
|
327
|
-
await onmessage({ data: executionData('import demo\nimport other') });
|
|
328
|
-
expect(pyodide.loadPackagesFromImports).toHaveBeenCalledTimes(2);
|
|
329
|
-
expect(pyodide.loadPackagesFromImports.mock.calls[0]).toEqual(pyodide.loadPackagesFromImports.mock.calls[1]);
|
|
330
|
-
});
|
|
331
|
-
it('does not reuse an old preparation after a later preparation fails', async () => {
|
|
332
|
-
const { onmessage, pyodide, postMessage } = await createRuntimeHarness();
|
|
333
|
-
await onmessage({ data: { load: true, assets: directAssets } });
|
|
334
|
-
const data = executionData();
|
|
335
|
-
await onmessage({ data: { ...data, prepare: true } });
|
|
336
|
-
pyodide.loadPackagesFromImports.mockRejectedValueOnce(new Error('package failed'));
|
|
337
|
-
await onmessage({ data: { ...data, prepare: true } });
|
|
338
|
-
expect(postMessage).toHaveBeenCalledWith({ error: 'package failed' });
|
|
339
|
-
await onmessage({ data });
|
|
340
|
-
expect(pyodide.loadPackagesFromImports).toHaveBeenCalledTimes(3);
|
|
341
|
-
expect(pyodide.runPythonAsync).toHaveBeenCalledOnce();
|
|
342
|
-
});
|
|
343
|
-
it('consumes preparation even when user execution fails', async () => {
|
|
344
|
-
const { onmessage, pyodide, postMessage } = await createRuntimeHarness();
|
|
345
|
-
await onmessage({ data: { load: true, assets: directAssets } });
|
|
346
|
-
const data = executionData();
|
|
347
|
-
await onmessage({ data: { ...data, prepare: true } });
|
|
348
|
-
pyodide.runPythonAsync.mockRejectedValueOnce(new Error('user code failed'));
|
|
349
|
-
await onmessage({ data });
|
|
350
|
-
expect(postMessage).toHaveBeenCalledWith({ error: 'user code failed' });
|
|
351
|
-
expect(pyodide.loadPackagesFromImports).toHaveBeenCalledOnce();
|
|
352
|
-
await onmessage({ data });
|
|
353
|
-
expect(pyodide.loadPackagesFromImports).toHaveBeenCalledTimes(2);
|
|
354
|
-
});
|
|
355
|
-
it('clears preparation when a new load message is received', async () => {
|
|
356
|
-
const { onmessage, pyodide } = await createRuntimeHarness();
|
|
357
|
-
await onmessage({ data: { load: true, assets: directAssets } });
|
|
358
|
-
const data = executionData();
|
|
359
|
-
await onmessage({ data: { ...data, prepare: true } });
|
|
360
|
-
await onmessage({ data: { load: true, assets: directAssets } });
|
|
361
|
-
await onmessage({ data });
|
|
362
|
-
expect(pyodide.loadPackagesFromImports).toHaveBeenCalledTimes(2);
|
|
363
|
-
});
|
|
364
|
-
it('keeps empty prepared programs executable', async () => {
|
|
365
|
-
const { onmessage, pyodide, postMessage } = await createRuntimeHarness();
|
|
366
|
-
await onmessage({ data: { load: true, assets: directAssets } });
|
|
367
|
-
const data = executionData('');
|
|
368
|
-
await onmessage({ data: { ...data, prepare: true } });
|
|
369
|
-
await onmessage({ data });
|
|
370
|
-
expect(pyodide.loadPackagesFromImports).not.toHaveBeenCalled();
|
|
371
|
-
expect(pyodide.runPythonAsync).toHaveBeenCalledOnce();
|
|
372
|
-
expect(postMessage).toHaveBeenCalledWith({ results: true });
|
|
373
|
-
});
|
|
374
|
-
});
|
|
File without changes
|