From f1ab677bfca4350a663bfe634080a9ab04bc7a23 Mon Sep 17 00:00:00 2001 From: Tom Lauwaerts Date: Tue, 11 Aug 2026 09:38:32 +0200 Subject: [PATCH] Fix exit error + parsing error --- .github/workflows/test.yml | 24 ++++++++++++---- src/framework/Verifier.ts | 42 +++++++++++++++++---------- src/messaging/Parsers.ts | 20 ++++++++++--- tests/examples/example.ts | 2 +- tests/unit/parsing.test.ts | 59 ++++++++++++++++++++++++++++++++++++-- 5 files changed, 120 insertions(+), 27 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aff8049..3b1d7bd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,7 +75,7 @@ jobs: unit: name: Unit tests runs-on: ubuntu-latest - needs: build-wabt + needs: [build-wabt, build-wdcli] if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v4 @@ -91,15 +91,30 @@ jobs: name: wabt-build-${{ github.run_id }} path: .tools + - name: Download WARDuino CLI + uses: actions/download-artifact@v4 + with: + name: warduino-build-${{ github.run_id }} + path: .warduino + - name: Verify tools run: | - chmod u+x $GITHUB_WORKSPACE/.tools/* - $GITHUB_WORKSPACE/.tools/wasm-objdump --version + chmod u+x "$GITHUB_WORKSPACE"/.tools/* + "$GITHUB_WORKSPACE"/.tools/wasm-objdump --version + find "$GITHUB_WORKSPACE"/.warduino -type f -name wdcli -exec chmod u+x {} \; + EMULATOR_PATH="$(find "$GITHUB_WORKSPACE"/.warduino -type f -name wdcli -print -quit)" + test -n "$EMULATOR_PATH" + echo "EMULATOR=$EMULATOR_PATH" >> $GITHUB_ENV - name: Run ava unit tests run: npm run test:ava env: - WABT: ${GITHUB_WORKSPACE}/.tools + WABT: ${{ github.workspace }}/.tools + + - name: Run example tests + run: npm run test:example + env: + WABT: ${{ github.workspace }}/.tools coverage: name: Code coverage @@ -146,4 +161,3 @@ jobs: valColorRange: ${{ env.LINE_COVERAGE }} maxColorRange: 100 minColorRange: 0 - diff --git a/src/framework/Verifier.ts b/src/framework/Verifier.ts index 86dd711..ab2795d 100644 --- a/src/framework/Verifier.ts +++ b/src/framework/Verifier.ts @@ -17,25 +17,33 @@ export class Verifier { let result = new StepOutcome(this.step).update(Outcome.succeeded); for (const expectation of this.step.expected ?? []) { for (const [field, entry] of Object.entries(expectation)) { + let value; try { - const value = getValue(actual, field); - - if (entry.kind === 'primitive') { - result = this.expectPrimitive(value, entry.value); - } else if (entry.kind === 'description') { - result = this.expectDescription(value, entry.value); - } else if (entry.kind === 'comparison') { - result = this.expectComparison(actual, value, entry.value, entry.message); - } else if (entry.kind === 'behaviour') { - if (previous === undefined) { - return this.error('Invalid test: no [previous] to compare behaviour to.'); - } - result = this.expectBehaviour(value, getValue(previous, field), entry.value); - } + value = getValue(actual, field); } catch { return this.error(`Failure: ${JSONStringify(actual)} state does not contain '${field}'.`); } + if (entry.kind === 'primitive') { + result = this.expectPrimitive(value, entry.value); + } else if (entry.kind === 'description') { + result = this.expectDescription(value, entry.value); + } else if (entry.kind === 'comparison') { + result = this.expectComparison(actual, value, entry.value, entry.message); + } else if (entry.kind === 'behaviour') { + if (previous === undefined) { + return this.error('Invalid test: no [previous] to compare behaviour to.'); + } + + let previousValue; + try { + previousValue = getValue(previous, field); + } catch { + return this.error(`Failure: ${JSONStringify(previous)} previous state does not contain '${field}'.`); + } + result = this.expectBehaviour(value, previousValue, entry.value); + } + if (result.outcome !== Outcome.succeeded) { return result; } @@ -123,5 +131,9 @@ export class Verifier { /* eslint @typescript-eslint/no-explicit-any: off */ function deepEqual(a: any, b: any): boolean { - return a === b || (isNaN(Number(a)) && isNaN(Number(b))) || a.equals(b); + if (typeof a === 'number' && typeof b === 'number') { + return Object.is(a, b); + } + + return a === b || (a !== undefined && a !== null && typeof a.equals === 'function' && a.equals(b)); } diff --git a/src/messaging/Parsers.ts b/src/messaging/Parsers.ts index 558f499..9cfb69d 100644 --- a/src/messaging/Parsers.ts +++ b/src/messaging/Parsers.ts @@ -77,7 +77,7 @@ function stacking(objects: { value: string, type: any }[]): WASM.Value[] { const stacked: WASM.Value[] = []; for (const object of objects) { const type: WASM.Type = extractType(object); - let buff; + let buff: Buffer; switch (type) { case WASM.Integer.u32: case WASM.Integer.u64: @@ -96,11 +96,11 @@ function stacking(objects: { value: string, type: any }[]): WASM.Value[] { stacked.push({value: WasmInt.finite(signed(BigInt(object.value), 64)), type: type}); break; case WASM.Float.f32: - buff = Buffer.from(Number(object.value).toString(16), 'hex'); + buff = floatBitsBuffer(object.value, 4); stacked.push({value: ieee754.read(buff, 0, false, 23, buff.length), type: type}); break; case WASM.Float.f64: - buff = Buffer.from(BigInt(object.value).toString(16), 'hex'); + buff = floatBitsBuffer(object.value, 8); stacked.push({value: ieee754.read(buff, 0, false, 52, buff.length), type: type}); break; case WASM.Special.unknown: @@ -110,8 +110,20 @@ function stacking(objects: { value: string, type: any }[]): WASM.Value[] { return stacked; } +function floatBitsBuffer(value: string | number | bigint, bytes: number): Buffer { + const raw = value.toString().trim(); + const hex = /^[0-9]+$/.test(raw) + ? BigInt(raw).toString(16) + : raw.replace(/^0x/i, ''); + const length = bytes * 2; + if (!/^[0-9a-fA-F]+$/.test(hex) || hex.length > length) { + throw Error(`Invalid ${bytes * 8}-bit float bit pattern: ${value}`); + } + return Buffer.from(hex.padStart(length, '0'), 'hex'); +} + // Strips all trailing newlines function stripEnd(text: string): string { return text.replace(/\s+$/g, ''); -} \ No newline at end of file +} diff --git a/tests/examples/example.ts b/tests/examples/example.ts index fff0642..855181d 100644 --- a/tests/examples/example.ts +++ b/tests/examples/example.ts @@ -156,5 +156,5 @@ reverse.test({ new Invoker('read', [WASM.i32(15n)], WASM.i32(1n))] }) -framework.reporter.verbosity(Verbosity.debug); +framework.reporter.verbosity(Verbosity.normal); framework.analyse([spec, debug, threethree, copysign]); diff --git a/tests/unit/parsing.test.ts b/tests/unit/parsing.test.ts index e32aa99..977928a 100644 --- a/tests/unit/parsing.test.ts +++ b/tests/unit/parsing.test.ts @@ -1,8 +1,10 @@ import test from 'ava'; -import {invokeParser, signed, stateParser} from "../../src/messaging/Parsers"; +import {invokeParser, signed} from "../../src/messaging/Parsers"; import {Exception, WARDuino} from "../../src"; import {WASM} from "../../src/sourcemap/Wasm"; -import State = WARDuino.State; +import {Verifier} from "../../src/framework/Verifier"; +import {Expected, Kind, Step} from "../../src/framework/scenario/Step"; +import {Outcome} from "../../src/reporter/describers/Describer"; import Type = WASM.Type; import WasmInt = WASM.WasmInt; @@ -75,3 +77,56 @@ test('[invoke parser] : 64-bit float', t => { t.true(isNaN(result.value)); } }); + +test('[invoke parser] : f32 hex bit pattern with alpha digits', t => { + const result: WASM.Value | Exception = invokeParser(`{\"stack\": [{\"idx\":0,\"type\":\"F32\",\"value\":\"a6800001\"}]}\n`); + + if ('text' in result) { + t.fail(`Expected parsed value, got exception: ${result.text}`); + return; + } + + t.is(result.type, WASM.Float.f32); + t.is(result.value, -8.881785255792436e-16); +}); + +test('[invoke parser] : f32 decimal bit pattern whose hex has only digits', t => { + const result: WASM.Value | Exception = invokeParser(`{\"stack\": [{\"idx\":0,\"type\":\"F32\",\"value\":\"645922818\"}]}\n`); + + if ('text' in result) { + t.fail(`Expected parsed value, got exception: ${result.text}`); + return; + } + + t.is(result.type, WASM.Float.f32); + t.is(result.value, 8.88178631458362e-16); +}); + +test('[invoke parser] : f32 decimal bit pattern with alpha hex equivalent', t => { + const result: WASM.Value | Exception = invokeParser(`{\"stack\": [{\"idx\":0,\"type\":\"F32\",\"value\":\"2793406465\"}]}\n`); + + if ('text' in result) { + t.fail(`Expected parsed value, got exception: ${result.text}`); + return; + } + + t.is(result.type, WASM.Float.f32); + t.is(result.value, -8.881785255792436e-16); +}); + +test('[verifier] : numeric mismatch is reported as failure, not missing field', t => { + const step: Step = { + title: 'CHECK: numeric mismatch', + instruction: { + kind: Kind.Request, + value: {type: WARDuino.Interrupt.invoke, payload: () => '', parser: invokeParser} + }, + expected: [{'value': {kind: 'primitive', value: -8.881785255792436e-16} as Expected}] + }; + + const result = new Verifier(step).verify({value: 0, type: 'f32'}); + + t.is(result.outcome, Outcome.failed); + t.false(result.clarification.includes(`state does not contain 'value'`)); + t.true(result.clarification.includes('Expected')); +});