Skip to content

Commit 065f97d

Browse files
fix(jco): tests, async tick() behavior
fix(jco): bug in runtime helpers preopens impl fix(jco): browser tests chore(jco): uncomment browser tests fix(jco): remove tick behavior
1 parent 0aecfdc commit 065f97d

File tree

8 files changed

+186
-140
lines changed

8 files changed

+186
-140
lines changed

crates/js-component-bindgen/src/esm_bindgen.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,9 @@ impl EsmBindgen {
400400
output,
401401
r#"
402402
if ({local_name} === undefined) {{
403-
throw new Error("unexpectedly undefined instance import '{local_name}', was '{external_name}' available at instantiation?");
403+
const err = new Error("unexpectedly undefined instance import '{local_name}', was '{external_name}' available at instantiation?");
404+
console.error("ERROR:", err.toString());
405+
throw err;
404406
}}
405407
"#,
406408
);
@@ -478,7 +480,9 @@ impl EsmBindgen {
478480
output,
479481
r#"
480482
if ({local_name} === undefined) {{
481-
throw new Error("unexpectedly undefined local import '{local_name}', was '{member_name}' available at instantiation?");
483+
const err = new Error("unexpectedly undefined local import '{local_name}', was '{member_name}' available at instantiation?");
484+
console.error("ERROR:", err.toString());
485+
throw err;
482486
}}
483487
"#,
484488
);

crates/js-component-bindgen/src/intrinsics/component.rs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,6 @@ impl ComponentIntrinsic {
163163
#parkedTasks = new Map();
164164
#suspendedTasksByTaskID = new Map();
165165
#suspendedTaskIDs = [];
166-
#taskResumerInterval = null;
167166
#pendingTasks = [];
168167
#errored = null;
169168
@@ -176,25 +175,8 @@ impl ComponentIntrinsic {
176175
this.#componentIdx = args.componentIdx;
177176
const self = this;
178177
179-
this.#taskResumerInterval = setTimeout(() => {{
180-
try {{
181-
if (self.errored()) {{
182-
self.stopTaskResumer();
183-
console.error(`(component ${{this.#errored.componentIdx}}) ASYNC ERROR:`, this.#errored);
184-
return;
185-
}}
186-
if (this.tick()) {{ setTimeout(() => {{ this.tick(); }}, 0); }}
187-
}} catch (err) {{
188-
{debug_log_fn}('[{class_name}#taskResumer()] tick failed', {{ err }});
189-
}}
190-
}}, 0);
191178
}};
192179
193-
stopTaskResumer() {{
194-
if (!this.#taskResumerInterval) {{ throw new Error('missing task resumer interval'); }}
195-
clearInterval(this.#taskResumerInterval);
196-
}}
197-
198180
componentIdx() {{ return this.#componentIdx; }}
199181
200182
errored() {{ return this.#errored !== null; }}
@@ -312,7 +294,7 @@ impl ComponentIntrinsic {
312294
313295
#removeSuspendedTaskMeta(taskID) {{
314296
{debug_log_fn}('[{class_name}#removeSuspendedTaskMeta()] removing suspended task', {{ taskID }});
315-
const idx = this.#suspendedTaskIDs.findIndex(t => t && t.taskID === taskID);
297+
const idx = this.#suspendedTaskIDs.findIndex(t => t === taskID);
316298
const meta = this.#suspendedTasksByTaskID.get(taskID);
317299
this.#suspendedTaskIDs[idx] = null;
318300
this.#suspendedTasksByTaskID.delete(taskID);

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/jco/test/browser.html

Lines changed: 83 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -14,68 +14,88 @@
1414
}
1515
</script>
1616
<script type="module">
17-
// verify preview2
18-
import "@bytecodealliance/preview2-shim/cli";
19-
import "@bytecodealliance/preview2-shim/clocks";
20-
import "@bytecodealliance/preview2-shim/filesystem";
21-
import "@bytecodealliance/preview2-shim/http";
22-
import "@bytecodealliance/preview2-shim/io";
23-
import "@bytecodealliance/preview2-shim/random";
24-
import "@bytecodealliance/preview2-shim/sockets";
25-
26-
import { transpile } from 'jco';
27-
const testName = window.location.hash.slice(1);
28-
document.body.innerHTML = '<h1>Running</h1>';
29-
switch (testName) {
30-
case 'transpile': {
31-
const componentUrl = new URL('./fixtures/components/lists.component.wasm', import.meta.url);
32-
const component = await (await fetch(componentUrl)).arrayBuffer();
33-
const output = await transpile(component, {
34-
name: 'test',
35-
noTypescript: true,
36-
noNodejsCompat: true,
37-
instantiation: { tag: 'async' },
38-
// force baseurls
39-
// todo: support a hook for inline blob url construction
40-
base64Cutoff: 1000000,
41-
map: [
42-
['wasi:cli/*', '@bytecodealliance/preview2-shim/cli#*'],
43-
['wasi:clocks/*', '@bytecodealliance/preview2-shim/clocks#*'],
44-
['wasi:filesystem/*', '@bytecodealliance/preview2-shim/filesystem#*'],
45-
['wasi:http/*', '@bytecodealliance/preview2-shim/http#*'],
46-
['wasi:io/*', '@bytecodealliance/preview2-shim/io#*'],
47-
['wasi:random/*', '@bytecodealliance/preview2-shim/random#*'],
48-
['wasi:sockets/*', '@bytecodealliance/preview2-shim/sockets#*']
49-
]
50-
});
51-
const source = output.files[5][1];
52-
const url = URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
53-
const mod = await import(url);
54-
ok();
55-
break;
56-
}
57-
default: {
58-
if (testName.startsWith('test:')) {
59-
const testModule = `/tmpdir/${testName.slice(5)}`;
60-
try {
61-
const mod = await import(testModule);
62-
if (!mod.test)
63-
throw new Error(`Expected test module "${testModule}" to export a "test" function`);
64-
mod.test();
65-
ok();
66-
} catch (e) {
67-
err(e.toString() + `\nrunning "${testModule}"`);
68-
}
69-
} else {
70-
err(`Unknown test case "${testName}"`);
71-
}
72-
}
73-
}
17+
document.body.innerHTML = 'INIT';
7418

75-
function ok () {
76-
document.body.innerHTML = '<h1>OK</h1>';
77-
}
78-
function err (e) {
79-
document.body.innerHTML = `<h1>Error: ${e}`;
80-
}
19+
// verify preview2
20+
import "@bytecodealliance/preview2-shim/cli";
21+
import "@bytecodealliance/preview2-shim/clocks";
22+
import "@bytecodealliance/preview2-shim/filesystem";
23+
import "@bytecodealliance/preview2-shim/http";
24+
import "@bytecodealliance/preview2-shim/io";
25+
import "@bytecodealliance/preview2-shim/random";
26+
import "@bytecodealliance/preview2-shim/sockets";
27+
28+
import { transpile } from 'jco';
29+
const testName = window.location.hash.slice(1);
30+
31+
async function main() {
32+
document.body.innerHTML = '<h1>Running</h1>';
33+
34+
switch (testName) {
35+
case 'transpile': {
36+
const componentUrl = new URL('./fixtures/components/lists.component.wasm', import.meta.url);
37+
const component = await (await fetch(componentUrl)).arrayBuffer();
38+
39+
// Perform the transpiliation
40+
let output;
41+
try {
42+
output = await transpile(component, {
43+
name: 'test',
44+
noTypescript: true,
45+
noNodejsCompat: true,
46+
instantiation: { tag: 'async' },
47+
// force baseurls
48+
// todo: support a hook for inline blob url construction
49+
base64Cutoff: 1000000,
50+
map: [
51+
['wasi:cli/*', '@bytecodealliance/preview2-shim/cli#*'],
52+
['wasi:clocks/*', '@bytecodealliance/preview2-shim/clocks#*'],
53+
['wasi:filesystem/*', '@bytecodealliance/preview2-shim/filesystem#*'],
54+
['wasi:http/*', '@bytecodealliance/preview2-shim/http#*'],
55+
['wasi:io/*', '@bytecodealliance/preview2-shim/io#*'],
56+
['wasi:random/*', '@bytecodealliance/preview2-shim/random#*'],
57+
['wasi:sockets/*', '@bytecodealliance/preview2-shim/sockets#*']
58+
]
59+
});
60+
} catch(err) {
61+
err(err);
62+
console.error(err);
63+
break;
64+
}
65+
66+
const source = output.files[5][1];
67+
const url = URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
68+
const mod = await import(url);
69+
ok();
70+
71+
break;
72+
}
73+
74+
default: {
75+
if (testName.startsWith('test:')) {
76+
const testModule = `/tmpdir/${testName.slice(5)}`;
77+
try {
78+
const mod = await import(testModule);
79+
if (!mod.test)
80+
throw new Error(`Expected test module "${testModule}" to export a "test" function`);
81+
mod.test();
82+
ok();
83+
} catch (e) {
84+
err(e.toString() + `\nrunning "${testModule}"`);
85+
}
86+
} else {
87+
err(`Unknown test case "${testName}"`);
88+
}
89+
}
90+
}
91+
}
92+
93+
function ok () {
94+
document.body.innerHTML = '<h1>OK</h1>';
95+
}
96+
function err (e) {
97+
document.body.innerHTML = `<h1>Error: ${e}`;
98+
}
99+
100+
await main()
81101
</script>

packages/jco/test/browser.js

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { mkdir, readFile, writeFile, rm, symlink } from 'node:fs/promises';
22
import { createServer } from 'node:http';
33
import { resolve, extname, dirname } from 'node:path';
4+
import { env } from 'node:process';
45

56
import puppeteer from 'puppeteer';
67
import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -100,13 +101,13 @@ suite('Browser', () => {
100101
} catch {}
101102
});
102103

103-
test('Transpilation', { retry: 3 }, async () => {
104-
await testPage(browser, port, 'transpile');
104+
test('basic transpile', { retry: 3 }, async () => {
105+
await testPage({ browser, port, hash: 'transpile' });
105106
});
106107

107108
test('IDL window', async () => {
108109
// Componentize the webidl DOM window test
109-
const { stderr } = await exec(
110+
const args = [
110111
'src/jco.js',
111112
'componentize',
112113
'test/fixtures/idl/dom.test.js',
@@ -121,22 +122,26 @@ suite('Browser', () => {
121122
'-n',
122123
'window-test',
123124
'-o',
124-
outFile
125-
);
125+
outFile,
126+
];
127+
console.log(`EXEC\n${args.join(" ")}\n===\n`);
128+
129+
const { stderr } = await exec(...args);
126130
assert.strictEqual(stderr, '');
127131

128132
// Transpile the test component
129133
const component = await readFile(outFile);
130134
const { files } = await transpile(component, { name: 'dom' });
131135

136+
132137
for (const [file, source] of Object.entries(files)) {
133138
const outPath = resolve(outDir, file);
134139
await mkdir(dirname(outPath), { recursive: true });
135140
await writeFile(outPath, source);
136141
}
137142

138143
// Run the test function in the browser from the generated tmpdir
139-
await testPage(browser, port, 'test:dom.js');
144+
await testPage({ browser, port, hash: 'test:dom.js' });
140145
});
141146

142147
test('IDL console', async () => {
@@ -170,12 +175,31 @@ suite('Browser', () => {
170175
await writeFile(outPath, source);
171176
}
172177

173-
await testPage(browser, port, 'test:console.js');
178+
await testPage({ browser, port, hash: 'test:console.js' });
174179
});
175180
});
176181

177-
export async function testPage(browser, port, hash) {
182+
/**
183+
* Test an individual browser page that is set up to do a transpilation
184+
* (see browser.html)
185+
*
186+
* NOTE: This test can fail if you are missing built outputs (e.g. in packages/jco/obj)
187+
* or transpilation output, or have bad code coming out of component bindgen.
188+
*
189+
* If you find no output at all from the page, it's likely that loading a script
190+
* has failed (something as simple as an incorrect path in the importmap, or missing
191+
* dependency, dependency with bad code, etc.), and you should likely enable puppeteer
192+
* logging.
193+
*
194+
* Consider setting JCO_DEBUG=true to see output from the headless browser.
195+
*
196+
*/
197+
export async function testPage(args) {
198+
const { browser, port, hash, expectedBodyContent } = args;
178199
const page = await browser.newPage();
200+
if (env.JCO_DEBUG) {
201+
page.on('console', msg => console.log('[browser]', msg.text()));
202+
}
179203

180204
assert.ok(
181205
(
@@ -191,6 +215,7 @@ export async function testPage(browser, port, hash) {
191215
while (bodyHtml === '<h1>Running</h1>') {
192216
bodyHtml = await body.evaluate((el) => el.innerHTML);
193217
}
194-
assert.strictEqual(bodyHtml, '<h1>OK</h1>');
218+
const expectedContent = expectedBodyContent ?? '<h1>OK</h1>';
219+
assert.strictEqual(bodyHtml, expectedContent);
195220
await page.close();
196221
}

packages/jco/test/helpers.js

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,24 +73,31 @@ export async function exec(cmd, ...args) {
7373
});
7474
cp.stdout.on('data', (chunk) => {
7575
stdout += chunk;
76+
if (env.JCO_DEBUG) {
77+
console.error(`[exec] [cmd=${cmd}] [stdout] ${chunk}`);
78+
}
7679
});
7780
cp.stderr.on('data', (chunk) => {
7881
stderr += chunk;
82+
if (env.JCO_DEBUG) {
83+
console.error(`[exec] [cmd=${cmd}] [stderr] ${chunk}`);
84+
}
7985
});
8086
cp.on('error', reject);
8187
cp.on('exit', (code) => {
82-
if (code !== 0) {
83-
const msg = [
84-
`error code [${code}] while executing [${processCmd} ${cmdArgs.join(' ')}]:`,
85-
'STDOUT:',
86-
stdout.toString(),
87-
'STDERR:',
88-
stderr.toString(),
89-
].join('\n');
90-
reject(new Error(msg));
88+
if (code === 0) {
89+
resolve();
9190
return;
9291
}
93-
resolve();
92+
93+
const msg = [
94+
`error code [${code}] while executing [${processCmd} ${cmdArgs.join(' ')}]:`,
95+
'STDOUT:',
96+
stdout.toString(),
97+
'STDERR:',
98+
stderr.toString(),
99+
].join('\n');
100+
reject(new Error(msg));
94101
});
95102
});
96103
return { stdout, stderr };
@@ -133,6 +140,9 @@ export async function setupAsyncTest(args) {
133140
'Both component.path and component.build should not be specified at the same time'
134141
);
135142
}
143+
if (componentPath && componentPath.endsWith(".wasm") && !componentName) {
144+
componentName = basename(componentPath).replace(/.wasm$/, "");
145+
}
136146

137147
// If this component should be built "just in time" -- i.e. created when this test is run
138148
let componentBuildCleanup;

0 commit comments

Comments
 (0)