fix(wasm): self-heal fiber trampoline so schematic load doesn't hang
The emscripten fiber glue gates Fibers.trampoline() on Fibers.trampolineRunning and resets it at the end of its loop. At startup emscripten_set_main_loop(...,1) throws "unwind" to establish the main loop, and KiCad does so from inside a tool coroutine, so the throw propagates THROUGH the trampoline and skips the reset — leaving the flag stuck true. Every fiber swap after startup then becomes a silent no-op, so opening a schematic (SetScreen -> RunAction(selectionClear) -> fiber swap) hangs forever with the editor stuck on "untitled". Wrap the trampoline loop in try/finally (inject-dyncall-shims.sh section "3c") so the flag is always reset. Add tests/kicad/eeschema-load.spec.ts, which opens a small wires/junctions schematic via Module.kicadOpenFile and asserts the editor title switches away from "untitled": it times out (RED) without the shim and passes (GREEN) with it. Also add features/web-init/0002-url-regex-modal-followup.md capturing the unrelated URL-detection wxRegEx modal surfaced once loading works. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ddd959fbc2
commit
18a9de0449
3 changed files with 252 additions and 0 deletions
138
tests/kicad/eeschema-load.spec.ts
Normal file
138
tests/kicad/eeschema-load.spec.ts
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import { test, expect } from './fixtures';
|
||||
|
||||
/**
|
||||
* Eeschema schematic-LOAD regression test (fiber / Asyncify trampoline shim).
|
||||
*
|
||||
* This guards the fix in scripts/common/inject-dyncall-shims.sh
|
||||
* ("3c. Fiber trampoline self-heal").
|
||||
*
|
||||
* Background: KiCad's tool framework runs action handlers in coroutines that
|
||||
* switch stacks via emscripten_fiber_swap. The emscripten fiber glue gates its
|
||||
* context switch on `Fibers.trampolineRunning` and resets that flag at the end of
|
||||
* `Fibers.trampoline()`. At startup `emscripten_set_main_loop(...,1)` throws
|
||||
* "unwind" to establish the main loop; KiCad does that from inside a tool
|
||||
* coroutine, so the throw propagates THROUGH the trampoline and skips the reset.
|
||||
* The flag then stays `true` forever, `Fibers.trampoline()` becomes a permanent
|
||||
* no-op, and EVERY fiber swap after startup silently fails to switch contexts.
|
||||
*
|
||||
* Opening a schematic calls SCH_EDIT_FRAME::SetScreen() ->
|
||||
* m_toolManager->RunAction(selectionClear), which performs such a fiber swap. So
|
||||
* without the shim, OpenProjectFiles() suspends in selectionClear and never
|
||||
* resumes: the load hangs and the editor title stays "untitled".
|
||||
*
|
||||
* The shim wraps the trampoline loop in try/finally so the flag is always reset.
|
||||
* With it, the load completes and the title switches to the opened file.
|
||||
*
|
||||
* Assertion strategy: open a minimal (text-free) schematic via the programmatic
|
||||
* Module.kicadOpenFile() hook and poll the editor title. GREEN once it shows the
|
||||
* file name; RED (poll timeout) if the load hangs because the shim is missing.
|
||||
*
|
||||
* The schematic holds a few wires + junctions (a box with a crossbar) so a dev
|
||||
* can eyeball a screenshot and immediately see whether it rendered. It uses ONLY
|
||||
* geometry — no text/symbol fields — both to keep the visual unambiguous and to
|
||||
* avoid an unrelated, still-open URL-detection wxRegEx bug that pops a modal when
|
||||
* text is rendered (see the regex follow-up). version 20250114 is within this
|
||||
* build's supported schematic version (SEXPR_SCHEMATIC_FILE_VERSION 20251012).
|
||||
*/
|
||||
|
||||
const SAMPLE_SCH = `(kicad_sch
|
||||
\t(version 20250114)
|
||||
\t(generator "eeschema")
|
||||
\t(generator_version "9.0")
|
||||
\t(uuid "11111111-1111-1111-1111-111111111111")
|
||||
\t(paper "A4")
|
||||
\t(lib_symbols)
|
||||
\t(wire (pts (xy 50.8 50.8) (xy 101.6 50.8)) (stroke (width 0) (type default)) (uuid "22222222-0000-0000-0000-000000000001"))
|
||||
\t(wire (pts (xy 50.8 101.6) (xy 101.6 101.6)) (stroke (width 0) (type default)) (uuid "22222222-0000-0000-0000-000000000002"))
|
||||
\t(wire (pts (xy 50.8 50.8) (xy 50.8 101.6)) (stroke (width 0) (type default)) (uuid "22222222-0000-0000-0000-000000000003"))
|
||||
\t(wire (pts (xy 101.6 50.8) (xy 101.6 101.6)) (stroke (width 0) (type default)) (uuid "22222222-0000-0000-0000-000000000004"))
|
||||
\t(wire (pts (xy 50.8 76.2) (xy 101.6 76.2)) (stroke (width 0) (type default)) (uuid "22222222-0000-0000-0000-000000000005"))
|
||||
\t(junction (at 50.8 76.2) (diameter 1.016) (color 0 0 0 0) (uuid "33333333-0000-0000-0000-000000000001"))
|
||||
\t(junction (at 101.6 76.2) (diameter 1.016) (color 0 0 0 0) (uuid "33333333-0000-0000-0000-000000000002"))
|
||||
\t(sheet_instances
|
||||
\t\t(path "/"
|
||||
\t\t\t(page "1")
|
||||
\t\t)
|
||||
\t)
|
||||
)
|
||||
`;
|
||||
|
||||
type EmscriptenFS = {
|
||||
mkdirTree(path: string): void;
|
||||
writeFile(path: string, data: string): void;
|
||||
};
|
||||
type KicadModule = { kicadOpenFile(path: string): unknown };
|
||||
|
||||
test.describe('Eeschema schematic load', () => {
|
||||
test('opens a .kicad_sch via kicadOpenFile and finishes loading (fiber shim regression)', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto('/kicad/eeschema.html');
|
||||
|
||||
// Editor must be fully up before we drive the open: a visible canvas, the
|
||||
// wx element registry, the embind open hook, and a top-level Frame (so
|
||||
// kicadOpenFile's GetTopWindow() resolves to the editor, not a wizard).
|
||||
await expect(page.locator('#canvas')).toBeVisible({ timeout: 90000 });
|
||||
await page.waitForFunction(() => !!window.wxElementRegistry, null, { timeout: 90000 });
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
typeof (window as unknown as { Module?: KicadModule }).Module?.kicadOpenFile ===
|
||||
'function',
|
||||
null,
|
||||
{ timeout: 90000 }
|
||||
);
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
!!window.wxElementRegistry &&
|
||||
window.wxElementRegistry
|
||||
.findAll({ visible: true })
|
||||
.some((e) => /Frame$/.test(e.typeName) || (e.name || '').endsWith('Frame')),
|
||||
null,
|
||||
{ timeout: 90000 }
|
||||
);
|
||||
|
||||
// Sanity: editor starts on an untitled schematic.
|
||||
expect(await page.title()).toMatch(/untitled/i);
|
||||
|
||||
// Write a minimal, version-compatible schematic into MEMFS and open it.
|
||||
// kicadOpenFile runs OpenProjectFiles under Asyncify: it suspends and
|
||||
// returns a placeholder, so we ignore the return and poll the title.
|
||||
const openedPath = await page.evaluate((content) => {
|
||||
const w = window as unknown as { FS: EmscriptenFS; Module: KicadModule };
|
||||
const dir = '/home/kicad/documents';
|
||||
try {
|
||||
w.FS.mkdirTree(dir);
|
||||
} catch {
|
||||
/* already exists */
|
||||
}
|
||||
const path = `${dir}/regression.kicad_sch`;
|
||||
w.FS.writeFile(path, content);
|
||||
w.Module.kicadOpenFile(path);
|
||||
return path;
|
||||
}, SAMPLE_SCH);
|
||||
expect(openedPath).toContain('regression.kicad_sch');
|
||||
|
||||
// With the fiber trampoline self-heal shim the load completes and the
|
||||
// title switches to the opened file. WITHOUT it, the selectionClear fiber
|
||||
// swap hangs and the title stays "untitled" -> this poll times out (RED).
|
||||
await expect
|
||||
.poll(async () => page.title(), {
|
||||
message:
|
||||
'Schematic load did not complete (title stayed "untitled"). ' +
|
||||
'The fiber trampoline self-heal shim (inject-dyncall-shims.sh "3c") is ' +
|
||||
'likely missing or broken.',
|
||||
timeout: 30000,
|
||||
intervals: [500],
|
||||
})
|
||||
.toMatch(/regression/i);
|
||||
|
||||
// Give the canvas a moment to paint the loaded geometry, then capture a
|
||||
// screenshot so a dev can eyeball the rendered wires/junctions (box with
|
||||
// a crossbar) as a quick "is it working?" check.
|
||||
await page.waitForTimeout(1000);
|
||||
await page.screenshot({
|
||||
path: 'test-results/eeschema-load-rendered.png',
|
||||
scale: 'device',
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue