From 8c14a3eff40d83261bdf0009f89ca956ef3f84ae Mon Sep 17 00:00:00 2001 From: tolauwae Date: Thu, 1 Aug 2024 21:33:35 +0200 Subject: [PATCH 1/2] Add out-of-place emulator to emulator --- src/framework/Testee.ts | 38 +++++++++++++++++++++++----- src/framework/scenario/Actions.ts | 4 +-- src/framework/scenario/Step.ts | 3 +++ src/manage/Uploader.ts | 5 ++-- src/messaging/Message.ts | 7 ++++- src/testbeds/TestbedFactory.ts | 1 + src/testbeds/TestbedSpecification.ts | 17 +++++++++++++ 7 files changed, 63 insertions(+), 12 deletions(-) diff --git a/src/framework/Testee.ts b/src/framework/Testee.ts index 56e465c..1528363 100644 --- a/src/framework/Testee.ts +++ b/src/framework/Testee.ts @@ -6,7 +6,7 @@ import {TestbedFactory} from '../testbeds/TestbedFactory'; import {Kind} from './scenario/Step'; import {SourceMapFactory} from '../sourcemap/SourceMapFactory'; import {TestScenario} from './scenario/TestScenario'; -import {TestbedSpecification} from '../testbeds/TestbedSpecification'; +import {OutofPlaceSpecification, PlatformType, TestbedSpecification} from '../testbeds/TestbedSpecification'; import {CompileOutput, CompilerFactory} from '../manage/Compiler'; import {WABT} from '../util/env'; import {Completion, expect, Result, ScenarioResult, SuiteResults} from './Reporter'; @@ -38,6 +38,11 @@ export function getValue(object: any, field: string): any { return object; } +export enum Target { + supervisor, + proxy +} + export class Testee { // TODO unified with testbed interface /** The current state for each described test */ @@ -58,7 +63,9 @@ export class Testee { // TODO unified with testbed interface public readonly name: string; - public testbed?: Testbed; + private testbed?: Testbed; + + private proxy?: Testbed; private current?: string; // current program @@ -71,9 +78,25 @@ export class Testee { // TODO unified with testbed interface this.framework = Framework.getImplementation(); } - public async initialize(program: string, args: string[]): Promise { + public bed(target: Target = Target.supervisor): Testbed | void { + return target == Target.proxy ? this.proxy : this.testbed; + } + + public async initialize(program: string, args: string[] = []): Promise { return new Promise(async (resolve, reject) => { - const testbed: Testbed | void = await this.connector.initialize(this.specification, program, args ?? []).catch((e) => { + if (this.specification.type === PlatformType.emu2emu) { + const spec = (this.specification as OutofPlaceSpecification).proxy; + const proxy: Testbed | void = await this.connector.initialize(spec, program, args ?? []).catch((e) => { + reject(e) + }); + if (proxy) { + this.proxy = proxy; + await this.proxy.sendRequest(new SourceMap.Mapping(), Message.proxifyRequest); + args.push(`--proxy ${spec.options.port}`); + } + } + + const testbed: Testbed | void = await this.connector.initialize(this.specification, program, args).catch((e) => { reject(e) }); if (testbed) { @@ -84,6 +107,7 @@ export class Testee { // TODO unified with testbed interface } public async shutdown(): Promise { + await this.proxy?.kill(); return this.testbed?.kill(); } @@ -125,7 +149,7 @@ export class Testee { // TODO unified with testbed interface let compiled: CompileOutput = await new CompilerFactory(WABT).pickCompiler(description.program).compile(description.program); try { - await timeout(`uploading module`, testee.timeout, testee.testbed!.sendRequest(new SourceMap.Mapping(), Message.updateModule(compiled.file))).catch((e) => Promise.reject(e)); + await timeout(`uploading module`, testee.timeout, testee.bed()!.sendRequest(new SourceMap.Mapping(), Message.updateModule(compiled.file))).catch((e) => Promise.reject(e)); testee.current = description.program; } catch (e) { await testee.initialize(description.program, description.args ?? []).catch((o) => Promise.reject(o)); @@ -161,7 +185,7 @@ export class Testee { // TODO unified with testbed interface /** Perform the step and check if expectations were met */ await this.step(step.title, testee.timeout, async function () { let result: Result = new Result(step.title, 'incomplete'); - if (testee.testbed === undefined) { + if (testee.bed(step.target ?? Target.supervisor) === undefined) { testee.states.set(description.title, result); result.error('Cannot run test: no debugger connection.'); testee.states.set(description.title, result); @@ -177,7 +201,7 @@ export class Testee { // TODO unified with testbed interface } else { actual = await testee.recoverable(testee, step.instruction.value, map, (testee, req, map) => timeout(`sending instruction ${req.type}`, testee.timeout, - testee.testbed!.sendRequest(map, req)), + testee.bed(step.target ?? Target.supervisor)!.sendRequest(map, req)), (testee) => testee.run(`Recover: re-initialize ${testee.testbed?.name}`, testee.connector.timeout, async function () { await testee.initialize(description.program, description.args ?? []).catch((o) => { return Promise.reject(o) diff --git a/src/framework/scenario/Actions.ts b/src/framework/scenario/Actions.ts index 282010a..ccbe0a9 100644 --- a/src/framework/scenario/Actions.ts +++ b/src/framework/scenario/Actions.ts @@ -36,7 +36,7 @@ export function awaitBreakpoint(): Action { try { const breakpoint = breakpointHitParser(message); // on success: remove listener + resolve - testee.testbed?.removeListener(TestbedEvents.OnMessage, breakpointListener); + testee.bed()?.removeListener(TestbedEvents.OnMessage, breakpointListener); resolve(assertable(breakpoint)); } catch (e) { @@ -44,7 +44,7 @@ export function awaitBreakpoint(): Action { } // await breakpoint hit - testee.testbed?.on(TestbedEvents.OnMessage, breakpointListener) + testee.bed()?.on(TestbedEvents.OnMessage, breakpointListener) }); } }; diff --git a/src/framework/scenario/Step.ts b/src/framework/scenario/Step.ts index 2c3a0f6..da6edb7 100644 --- a/src/framework/scenario/Step.ts +++ b/src/framework/scenario/Step.ts @@ -1,5 +1,6 @@ import {Action} from './Actions'; import {Request} from '../../messaging/Message' +import {Target} from '../Testee'; export enum Description { /** required properties */ @@ -41,5 +42,7 @@ export interface Step { readonly instruction: Instruction; + target?: Target; + readonly expected?: Expectation[]; } \ No newline at end of file diff --git a/src/manage/Uploader.ts b/src/manage/Uploader.ts index 810435d..9638eab 100644 --- a/src/manage/Uploader.ts +++ b/src/manage/Uploader.ts @@ -9,7 +9,7 @@ import {SubProcess} from '../bridge/SubProcess'; import {Connection} from '../bridge/Connection'; import {Serial} from '../bridge/Serial'; import {CompileOutput} from './Compiler'; -import {TestbedSpecification, PlatformType, SerialOptions, SubprocessOptions} from '../testbeds/TestbedSpecification'; +import {PlatformType, SerialOptions, SubprocessOptions, TestbedSpecification} from '../testbeds/TestbedSpecification'; enum UploaderEvents { compiled = 'compiled', @@ -38,11 +38,12 @@ export class UploaderFactory { case PlatformType.arduino: return new ArduinoUploader(this.arduino, args, specification.options as SerialOptions); case PlatformType.emulator: + case PlatformType.emu2emu: return new EmulatorUploader(this.emulator, args, specification.options as SubprocessOptions); case PlatformType.debug: return new EmulatorConnector(specification.options as SubprocessOptions) } - throw new Error('Unsupported file type'); + throw new Error('Unsupported platform type'); } } diff --git a/src/messaging/Message.ts b/src/messaging/Message.ts index 5648c3a..649e362 100644 --- a/src/messaging/Message.ts +++ b/src/messaging/Message.ts @@ -1,5 +1,5 @@ import {WARDuino} from '../debug/WARDuino'; -import {ackParser, breakpointParser, invokeParser, stateParser} from './Parsers'; +import {ackParser, breakpointParser, identityParser, invokeParser, stateParser} from './Parsers'; import {Breakpoint} from '../debug/Breakpoint'; import {WASM} from '../sourcemap/Wasm'; import {write} from 'ieee754'; @@ -210,4 +210,9 @@ export namespace Message { type: Interrupt.dumpCallbackmapping, parser: stateParser } + + export const proxifyRequest: Request = { + type: Interrupt.proxify, + parser: identityParser + }; } diff --git a/src/testbeds/TestbedFactory.ts b/src/testbeds/TestbedFactory.ts index af7f47b..b7d90a8 100644 --- a/src/testbeds/TestbedFactory.ts +++ b/src/testbeds/TestbedFactory.ts @@ -28,6 +28,7 @@ export class TestbedFactory { case PlatformType.arduino: return new Arduino(connection as Serial); case PlatformType.emulator: + case PlatformType.emu2emu: case PlatformType.debug: return new Emulator(connection as SubProcess); default: diff --git a/src/testbeds/TestbedSpecification.ts b/src/testbeds/TestbedSpecification.ts index eaf0ef7..f0d3779 100644 --- a/src/testbeds/TestbedSpecification.ts +++ b/src/testbeds/TestbedSpecification.ts @@ -1,6 +1,7 @@ export enum PlatformType { arduino, emulator, + emu2emu, debug } @@ -18,12 +19,28 @@ export interface SubprocessOptions extends ConnectionOptions { port: number } +export interface SupervisorOptions extends ConnectionOptions { + port: number, + proxy: number +} export interface TestbedSpecification { readonly type: PlatformType; readonly options: ConnectionOptions; } +export class OutofPlaceSpecification implements TestbedSpecification { + public readonly type: PlatformType = PlatformType.emu2emu; + public readonly options: SupervisorOptions; + + public readonly proxy: EmulatorSpecification; + + constructor(supervisor: number, proxy: number) { + this.options = {port: supervisor, proxy: proxy}; + this.proxy = new EmulatorSpecification(proxy); + } +} + export class EmulatorSpecification implements TestbedSpecification { public readonly type: PlatformType; public readonly options: SubprocessOptions; From 380518c91258e145b54724b5e22f26f050b02dd9 Mon Sep 17 00:00:00 2001 From: tolauwae Date: Fri, 2 Aug 2024 16:12:01 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=A8=20Add=20Target=20to=20scenario=20?= =?UTF-8?q?steps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/framework/scenario/Invoker.ts | 13 +++- test/dummy.wast | 28 ++++++++ test/test.ts | 109 ++++++++++++++++++++++++++++-- 3 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 test/dummy.wast diff --git a/src/framework/scenario/Invoker.ts b/src/framework/scenario/Invoker.ts index 8717958..2462fda 100644 --- a/src/framework/scenario/Invoker.ts +++ b/src/framework/scenario/Invoker.ts @@ -1,17 +1,24 @@ import {Expectation, Expected, Instruction, Kind, Step} from './Step'; import {WASM} from '../../sourcemap/Wasm'; -import {Exception, Message} from '../../messaging/Message'; +import {Message} from '../../messaging/Message'; +import {Target} from '../Testee'; import Value = WASM.Value; export class Invoker implements Step { readonly title: string; readonly instruction: Instruction; readonly expected?: Expectation[]; + readonly target?: Target; - constructor(func: string, args: Value[], result: Value) { - this.title = `ASSERT: ${func} ${args.map(val => val.value).join(' ')} ${result.value}`; + constructor(func: string, args: Value[], result: Value, target?: Target) { + let prefix = ""; this.instruction = invoke(func, args); this.expected = returns(result); + if (target !== undefined) { + this.target = target; + prefix = `${target === Target.supervisor ? '[supervisor] ' : '[proxy] '}` + } + this.title = `${prefix}CHECK: (${func} ${args.map(val => val.value).join(' ')}) returns ${result.value}`; } } diff --git a/test/dummy.wast b/test/dummy.wast new file mode 100644 index 0000000..ce91e63 --- /dev/null +++ b/test/dummy.wast @@ -0,0 +1,28 @@ + +(module + ;; Type declarations + (type $int32->int32->int32 (func (param i32 i32) (result i32))) + (type $int32->int32 (func (param i32) (result i32))) + (type $int32->void (func (param i32))) + (type $void->void (func)) + + ;; Imports from the WARDuino VM + (import "env" "dummy" (func $env.dummy (type $int32->int32->int32))) + (import "env" "print_int" (func $env.print_int (type $int32->void))) + + ;; Memory + (memory 1) + + ;; Main function + (func $main (export "main") (type $void->void) + (call $env.dummy (i32.const 32) (i32.const 64)) + (drop) + (call $env.print_int (i32.load (i32.const 32))) + ) + + (func $dummy (export "dummy") (type $int32->int32->int32) + (call $env.dummy (local.get 0) (local.get 1))) + + (func $load (export "load") (type $int32->int32) + (i32.load (local.get 0))) +) diff --git a/test/test.ts b/test/test.ts index 72729b4..9b94b4e 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,12 +1,25 @@ -import {ArduinoSpecification, EmulatorSpecification, Expected, Framework, HybridScheduler, Invoker, Kind, Message, Step, WASM} from '../src/index'; +import { + EmulatorSpecification, + Expected, + Framework, + Invoker, + Kind, + Message, + OutofPlaceSpecification, + Step, + Target, + WASM +} from '../src/index'; +import {WARDuino} from '../src/debug/WARDuino'; import dump = Message.dump; import stepOver = Message.stepOver; +import step = Message.step; const framework = Framework.getImplementation(); const spec = framework.suite('Test Wasm spec'); // must be called first -spec.testee('emulator [:8500]', new EmulatorSpecification(8500)); +spec.testee('emulator[:8500]', new EmulatorSpecification(8500)); const steps: Step[] = []; @@ -24,7 +37,8 @@ spec.test({ }); const debug = framework.suite('Test Debugger interface'); -debug.testee('emulator [:8520]', new EmulatorSpecification(8520)); +debug.testee('emulator[:8520]', new EmulatorSpecification(8520)); +debug.testee('emulator[:8522]', new EmulatorSpecification(8522)); // debug.testee('esp wrover', new ArduinoSpecification('/dev/ttyUSB0', 'esp32:esp32:esp32wrover'), new HybridScheduler(), {connectionTimout: 0}); debug.test({ @@ -57,4 +71,91 @@ debug.test({ }] }); -framework.run([spec, debug]); +const primitives = framework.suite('Test primitives'); + +primitives.testee('debug[:8700]', new EmulatorSpecification(8700)); + +primitives.test({ + title: `Test store primitive`, + program: 'test/dummy.wast', + dependencies: [], + steps: [{ + title: 'CHECK: execution at start of main', + instruction: {kind: Kind.Request, value: dump}, + expected: [{'pc': {kind: 'primitive', value: 129} as Expected}] + }, + + new Invoker('load', [WASM.i32(32)], WASM.i32(0)), + + { + title: 'Send STEP command', + instruction: {kind: Kind.Request, value: step} + }, + + { + title: 'Send STEP command', + instruction: {kind: Kind.Request, value: step} + }, + + { + title: 'Send STEP command', + instruction: {kind: Kind.Request, value: step} + }, + + new Invoker('load', [WASM.i32(32)], WASM.i32(42)) + ] +}) + +const oop = framework.suite('Test Out-of-place primitives'); + +oop.testee('supervisor[:8100] - proxy[:8150]', new OutofPlaceSpecification(8100, 8150)); + +oop.test({ + title: `Test store primitive`, + program: 'test/dummy.wast', + dependencies: [], + steps: [ + { + title: '[supervisor] CHECK: execution at start of main', + instruction: {kind: Kind.Request, value: dump}, + expected: [{'pc': {kind: 'primitive', value: 129} as Expected}] + }, + + { + title: '[proxy] CHECK: execution at start of main', + instruction: {kind: Kind.Request, value: dump}, + expected: [{'pc': {kind: 'primitive', value: 129} as Expected}], + target: Target.proxy + }, + + new Invoker('load', [WASM.i32(32)], WASM.i32(0), Target.proxy), + + { + title: '[supervisor] Send STEP command', + instruction: {kind: Kind.Request, value: step} + }, + + { + title: '[supervisor] Send STEP command', + instruction: {kind: Kind.Request, value: step} + }, + + { + title: '[supervisor] Send STEP command', + instruction: {kind: Kind.Request, value: step} + }, + + { + title: '[supervisor] CHECK: execution took three steps', + instruction: {kind: Kind.Request, value: dump}, + expected: [{'pc': {kind: 'primitive', value: 136} as Expected}] + }, + + new Invoker('load', [WASM.i32(32)], WASM.i32(42), Target.proxy), + + new Invoker('load', [WASM.i32(32)], WASM.i32(42), Target.supervisor) + ] +}); + + +framework.run([spec, debug, primitives, oop]);