npm: surface readable diagnostics on WASM test failures
The example/suite runners discarded e.stack via console.error(e.message ?? e), hiding the wasm-function offsets that emsymbolizer needs to map an abort back to C source. A failing test only printed a terse message like "memory access out of bounds" with no trace. - Add reportError() to helpers.js/helpers-tcl.js; print the full stack (falling back to the message). - Wrap command execution in runScript() to name the command that aborted before the error propagates. - Use reportError() in all standalone runners and in all.js/all-tcl.js (full stack to stderr, one-line PASS/FAIL summary kept; tests still run independently). - build.sh: run the --test step in a subshell so its cd does not leak into the --pack step.
This commit is contained in:
parent
d37793e7d0
commit
50320a055a
|
|
@ -202,10 +202,10 @@ esac
|
|||
|
||||
# --- optional test -----------------------------------------------------------
|
||||
# Runs the same smoke test that CI runs (see .github/workflows/main-wasm.yml).
|
||||
# Run in a subshell so the cd does not leak into the --pack step below, which
|
||||
# relies on the script's working directory being unchanged.
|
||||
if [ $OPT_TEST -eq 1 ]; then
|
||||
cd "$SCRIPT_DIR"
|
||||
npm run test
|
||||
npm run test:tcl
|
||||
( cd "$SCRIPT_DIR" && npm run test && npm run test:tcl )
|
||||
fi
|
||||
|
||||
# --- optional pack -----------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { run as runGds } from './gds-tcl.js';
|
|||
import { run as runDrc } from './drc-tcl.js';
|
||||
import { run as runCif } from './cif-tcl.js';
|
||||
import { run as runPcell } from './pcell.js';
|
||||
import { reportError } from './helpers-tcl.js';
|
||||
|
||||
const PAD = 9;
|
||||
|
||||
|
|
@ -20,6 +21,9 @@ async function test(name, fn) {
|
|||
return true;
|
||||
} catch (e) {
|
||||
console.log(`FAIL ${e.message ?? e}`);
|
||||
// Full stack to stderr so CI shows the wasm-function offsets, while the
|
||||
// one-line summary above stays readable. Other tests still run.
|
||||
reportError(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { run as runExtract } from './extract.js';
|
|||
import { run as runGds } from './gds.js';
|
||||
import { run as runDrc } from './drc.js';
|
||||
import { run as runCif } from './cif.js';
|
||||
import { reportError } from './helpers.js';
|
||||
|
||||
const PAD = 9;
|
||||
|
||||
|
|
@ -16,6 +17,9 @@ async function test(name, fn) {
|
|||
return true;
|
||||
} catch (e) {
|
||||
console.log(`FAIL ${e.message ?? e}`);
|
||||
// Full stack to stderr so CI shows the wasm-function offsets, while the
|
||||
// one-line summary above stays readable. Other tests still run.
|
||||
reportError(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// cif-tcl.js — CIF export via the TCL variant.
|
||||
import { createMagic, vfsRead, loadCell, loadScript,
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers-tcl.js';
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT, reportError } from './helpers-tcl.js';
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolve } from 'node:path';
|
||||
|
|
@ -22,7 +22,7 @@ export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH, outputDi
|
|||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const { outPath, bytes } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
const { outPath, bytes } = await run().catch(e => { reportError(e); process.exit(1); });
|
||||
console.log(`\ncif: ${outPath} (${bytes} bytes)`);
|
||||
console.log('Done.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// Usage: node examples/cif.js [magFile [tech [outputDir]]]
|
||||
import { createMagic, vfsRead, loadCell, loadScript,
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers.js';
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT, reportError } from './helpers.js';
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolve } from 'node:path';
|
||||
|
|
@ -24,7 +24,7 @@ export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH, outputDi
|
|||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const { outPath, bytes } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
const { outPath, bytes } = await run().catch(e => { reportError(e); process.exit(1); });
|
||||
console.log(`\ncif: ${outPath} (${bytes} bytes)`);
|
||||
console.log('Done.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// drc-tcl.js — DRC check via the TCL variant.
|
||||
import { createMagic, loadCell, loadScript,
|
||||
DEFAULT_TECH, DEFAULT_MAG } from './helpers-tcl.js';
|
||||
DEFAULT_TECH, DEFAULT_MAG, reportError } from './helpers-tcl.js';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH } = {}) {
|
||||
|
|
@ -22,7 +22,7 @@ export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH } = {}) {
|
|||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const { violations } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
const { violations } = await run().catch(e => { reportError(e); process.exit(1); });
|
||||
console.log(`\nDRC violations: ${violations ?? '(count not found in output)'}`);
|
||||
console.log('Done.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// Usage: node examples/drc.js [magFile [tech]]
|
||||
import { createMagic, loadCell, loadScript,
|
||||
DEFAULT_TECH, DEFAULT_MAG } from './helpers.js';
|
||||
DEFAULT_TECH, DEFAULT_MAG, reportError } from './helpers.js';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH } = {}) {
|
||||
|
|
@ -25,7 +25,7 @@ export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH } = {}) {
|
|||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const { violations } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
const { violations } = await run().catch(e => { reportError(e); process.exit(1); });
|
||||
console.log(`\nDRC violations: ${violations ?? '(count not found in output)'}`);
|
||||
console.log('Done.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// extract-tcl.js — RC extraction via the TCL variant.
|
||||
import { createMagic, vfsWrite, vfsRead, loadCell, loadScript,
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers-tcl.js';
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT, reportError } from './helpers-tcl.js';
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolve } from 'node:path';
|
||||
|
|
@ -32,7 +32,7 @@ export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH, outputDi
|
|||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const { ext, spice } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
const { ext, spice } = await run().catch(e => { reportError(e); process.exit(1); });
|
||||
console.log(`\next: ${ext}`);
|
||||
if (spice) console.log(`spice: ${spice}`);
|
||||
console.log('Done.');
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// Usage: node examples/extract.js [magFile [tech [outputDir]]]
|
||||
import { createMagic, vfsWrite, vfsRead, loadCell, loadScript,
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers.js';
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT, reportError } from './helpers.js';
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolve } from 'node:path';
|
||||
|
|
@ -34,7 +34,7 @@ export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH, outputDi
|
|||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const { ext, spice } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
const { ext, spice } = await run().catch(e => { reportError(e); process.exit(1); });
|
||||
console.log(`\next: ${ext}`);
|
||||
if (spice) console.log(`spice: ${spice}`);
|
||||
console.log('Done.');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// gds-tcl.js — GDS export via the TCL variant.
|
||||
import { createMagic, vfsRead, loadCell, loadScript,
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers-tcl.js';
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT, reportError } from './helpers-tcl.js';
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolve } from 'node:path';
|
||||
|
|
@ -22,7 +22,7 @@ export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH, outputDi
|
|||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const { outPath, bytes } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
const { outPath, bytes } = await run().catch(e => { reportError(e); process.exit(1); });
|
||||
console.log(`\ngds: ${outPath} (${bytes} bytes)`);
|
||||
console.log('Done.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// Usage: node examples/gds.js [magFile [tech [outputDir]]]
|
||||
import { createMagic, vfsRead, loadCell, loadScript,
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers.js';
|
||||
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT, reportError } from './helpers.js';
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolve } from 'node:path';
|
||||
|
|
@ -24,7 +24,7 @@ export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH, outputDi
|
|||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const { outPath, bytes } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
const { outPath, bytes } = await run().catch(e => { reportError(e); process.exit(1); });
|
||||
console.log(`\ngds: ${outPath} (${bytes} bytes)`);
|
||||
console.log('Done.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,14 @@ export class MagicWasm {
|
|||
const path = '/tmp/_magic_script.tcl';
|
||||
this.FS.mkdirTree('/tmp');
|
||||
this.FS.writeFile(path, text);
|
||||
this._sourceFile(path);
|
||||
try {
|
||||
this._sourceFile(path);
|
||||
} catch (e) {
|
||||
// A WASM trap during Tcl evaluation otherwise gives no hint of the
|
||||
// source; flag it before the error propagates.
|
||||
console.error('[magic-tcl] script evaluation failed');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
// Evaluate a Tcl expression directly (needed for proc definitions in PCell).
|
||||
runTcl(text) {
|
||||
|
|
@ -74,3 +81,11 @@ export function loadScript(name, tech, cell) {
|
|||
.replaceAll('__TECH__', tech)
|
||||
.replaceAll('__CELL__', cell);
|
||||
}
|
||||
|
||||
// Print a failure with enough detail to be actionable in CI. WASM aborts
|
||||
// surface as a RuntimeError whose .message is terse while .stack carries the
|
||||
// wasm-function offsets that emsymbolizer maps back to C source — so always
|
||||
// prefer the stack.
|
||||
export function reportError(e) {
|
||||
console.error(e?.stack ?? String(e?.message ?? e));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,11 +38,28 @@ export class MagicWasm {
|
|||
runScript(text) {
|
||||
for (const line of text.split('\n')) {
|
||||
const l = line.trim();
|
||||
if (l && !l.startsWith('#')) this._run(l);
|
||||
if (l && !l.startsWith('#')) {
|
||||
try {
|
||||
this._run(l);
|
||||
} catch (e) {
|
||||
// Name the command that aborted before the error propagates — a
|
||||
// WASM trap otherwise gives no hint which step failed.
|
||||
console.error(`[magic] command failed: ${l}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print a failure with enough detail to be actionable in CI. WASM aborts
|
||||
// surface as a RuntimeError whose .message is terse ("memory access out of
|
||||
// bounds") while .stack carries the wasm-function offsets that emsymbolizer
|
||||
// maps back to C source — so always prefer the stack.
|
||||
export function reportError(e) {
|
||||
console.error(e?.stack ?? String(e?.message ?? e));
|
||||
}
|
||||
|
||||
// Creates a fresh Magic WASM instance and calls init().
|
||||
// onPrint / onPrintErr default to console.log/error with a [magic] prefix.
|
||||
// All output lines are also collected into the returned `lines` array.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
// and verifies that both GDS outputs are non-empty.
|
||||
//
|
||||
// Usage: node examples/pcell.js
|
||||
import { createMagic, vfsRead, loadScript, DEFAULT_TECH, DEFAULT_OUT } from './helpers-tcl.js';
|
||||
import { createMagic, vfsRead, loadScript, DEFAULT_TECH, DEFAULT_OUT, reportError } from './helpers-tcl.js';
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolve } from 'node:path';
|
||||
|
|
@ -32,7 +32,7 @@ export async function run({ tech = DEFAULT_TECH, outputDir = DEFAULT_OUT } = {})
|
|||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const results = await run().catch(e => { console.error(e.message ?? e); process.exit(1); });
|
||||
const results = await run().catch(e => { reportError(e); process.exit(1); });
|
||||
for (const [name, { outPath, bytes }] of Object.entries(results))
|
||||
console.log(` ${name}.gds: ${outPath} (${bytes} bytes)`);
|
||||
console.log('Done.');
|
||||
|
|
|
|||
Loading…
Reference in New Issue