From 1ae95f0bcb8825cd009ee02966459280f78d0dd2 Mon Sep 17 00:00:00 2001 From: Nick Gasson Date: Sat, 4 Nov 2023 16:33:58 +0000 Subject: [PATCH] Add tests for Conduit --- contrib/gui/component/app.tsx | 2 +- contrib/gui/lib/conduit.ts | 50 ++++++++++++++--------- contrib/gui/lib/model.ts | 8 ++-- contrib/gui/lib/trace.test.ts | 8 ++-- contrib/gui/lib/trace.ts | 4 ++ contrib/gui/package-lock.json | 34 +++++++++++++++- contrib/gui/package.json | 4 +- contrib/gui/test/conduit.test.ts | 69 ++++++++++++++++++++++++++++++++ 8 files changed, 150 insertions(+), 29 deletions(-) create mode 100644 contrib/gui/test/conduit.test.ts diff --git a/contrib/gui/component/app.tsx b/contrib/gui/component/app.tsx index acd5f340..a3126209 100644 --- a/contrib/gui/component/app.tsx +++ b/contrib/gui/component/app.tsx @@ -28,7 +28,7 @@ import WaveView from "./WaveView"; import Sidebar from "./Sidebar"; import { bindEvents } from "../lib/react-util"; -const conduit = new Conduit(); +const conduit = new Conduit(url => new WebSocket(url)); const model = new Model(conduit); const toaster: Toaster = OverlayToaster.create({ position: "top-right" }); diff --git a/contrib/gui/lib/conduit.ts b/contrib/gui/lib/conduit.ts index 20b5d380..728e5f15 100644 --- a/contrib/gui/lib/conduit.ts +++ b/contrib/gui/lib/conduit.ts @@ -68,22 +68,30 @@ class PacketBuffer { } } +interface IWebSocket { + send(text: string): void; + close(): void; + onmessage: ((ev: MessageEvent) => any) | null; + onclose: ((ev: CloseEvent) => any) | null; + onopen: ((ev: Event) => any) | null; +} + class Conduit { - private socket: WebSocket; - - public onConsoleOutput: (data: string) => void = console.log; - public onAddWave: (path: string, value: string) => void = () => {}; - public onSignalUpdate: (path: string, value: string) => void = () => {}; - public onInitCommand: (cmd: string) => void = console.log; - public onClose: () => void = () => {}; - public onStartSim: (top: string) => void = () => {}; - public onRestartSim: () => void = () => {}; - public onQuitSim: () => void = () => {}; - public onNextTimeStep: (now: bigint) => void = () => {}; - - constructor() { - this.socket = new WebSocket("ws://localhost:8888"); - this.socket.binaryType = "arraybuffer"; + private socket: IWebSocket; + + onConsoleOutput: (data: string) => void = console.log; + onAddWave: (path: string, value: string) => void = () => {}; + onSignalUpdate: (path: string, value: string) => void = () => {}; + onInitCommand: (cmd: string) => void = console.log; + onOpen: (() => void) | null = null; + onClose: (() => void) | null = null; + onStartSim: ((top: string) => void) | null = null; + onRestartSim: (() => void) | null = null; + onQuitSim: (() => void) | null = null; + onNextTimeStep: (now: bigint) => void = () => {}; + + constructor(makeWebSocket: (url: string) => IWebSocket) { + this.socket = makeWebSocket("ws://localhost:8888"); this.socket.onmessage = (e) => { if (typeof e.data == "string") { @@ -94,7 +102,11 @@ class Conduit { }; this.socket.onclose = () => { - this.onClose(); + this.onClose?.(); + }; + + this.socket.onopen = () => { + this.onOpen?.(); }; } @@ -116,10 +128,10 @@ class Conduit { this.parseStartSim(packet); break; case ServerOpcode.S2C_RESTART_SIM: - this.onRestartSim(); + this.onRestartSim?.(); break; case ServerOpcode.S2C_QUIT_SIM: - this.onQuitSim(); + this.onQuitSim?.(); break; case ServerOpcode.S2C_NEXT_TIME_STEP: this.parseNextTimeStep(packet); @@ -145,7 +157,7 @@ class Conduit { } private parseStartSim(packet: PacketBuffer) { - this.onStartSim(packet.unpackString()); + this.onStartSim?.(packet.unpackString()); } private parseSignalUpdate(packet: PacketBuffer) { diff --git a/contrib/gui/lib/model.ts b/contrib/gui/lib/model.ts index a33dd5d1..b6885b79 100644 --- a/contrib/gui/lib/model.ts +++ b/contrib/gui/lib/model.ts @@ -22,12 +22,14 @@ import type { IWaveMap } from "./trace"; export class Signal { private model: Model; private _path: string; - private _trace: Trace = new Trace(); + private _trace: Trace; + private initValue: string; // XXX: why is this needed? constructor(model: Model, path: string, value: string) { this.model = model; this._path = path; - this._trace.update(0n, value); + this._trace = new Trace(value); + this.initValue = value; } get path() { @@ -39,7 +41,7 @@ export class Signal { } reset() { - this._trace = new Trace(); + this._trace = new Trace(this.initValue); } mapWaveform(start: bigint, end: bigint, fn: IWaveMap): void { diff --git a/contrib/gui/lib/trace.test.ts b/contrib/gui/lib/trace.test.ts index 249a3b0a..c32784bf 100644 --- a/contrib/gui/lib/trace.test.ts +++ b/contrib/gui/lib/trace.test.ts @@ -1,10 +1,10 @@ import Trace from "./trace"; test("mapWaveform basic", () => { - const t = new Trace(); - t.update(0n, "1"); - t.update(1n, "0"); - t.update(5n, "1"); + const t = new Trace("l1"); + t.update(0n, "l1"); + t.update(1n, "l0"); + t.update(5n, "l1"); diff --git a/contrib/gui/lib/trace.ts b/contrib/gui/lib/trace.ts index 69ef68b6..9e2ab598 100644 --- a/contrib/gui/lib/trace.ts +++ b/contrib/gui/lib/trace.ts @@ -25,6 +25,10 @@ class Trace { private times: bigint[] = []; private values: TraceValue[] = []; + constructor(value: string) { + this.update(0n, value); + } + update(when: bigint, value: string) { this.times.push(when); this.values.push(value); diff --git a/contrib/gui/package-lock.json b/contrib/gui/package-lock.json index ed9ac4ec..7cde73bc 100644 --- a/contrib/gui/package-lock.json +++ b/contrib/gui/package-lock.json @@ -20,6 +20,7 @@ "@types/jest": "^29.5.6", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", + "@types/ws": "^8.5.8", "@typescript-eslint/eslint-plugin": "^6.7.0", "@typescript-eslint/parser": "^6.7.0", "@vitejs/plugin-react": "^4.0.3", @@ -27,7 +28,8 @@ "eslint-plugin-react": "^7.33.2", "jest": "^29.7.0", "typescript": "^4.2.4", - "vite": "^4.4.4" + "vite": "^4.4.4", + "ws": "^8.14.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2253,6 +2255,15 @@ "integrity": "sha512-g7CK9nHdwjK2n0ymT2CW698FuWJRIx+RP6embAzZ2Qi8/ilIrA1Imt2LVSeHUzKvpoi7BhmmQcXz95eS0f2JXw==", "dev": true }, + "node_modules/@types/ws": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.8.tgz", + "integrity": "sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.29", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.29.tgz", @@ -8696,6 +8707,27 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/contrib/gui/package.json b/contrib/gui/package.json index 40a06b57..e7377a4c 100644 --- a/contrib/gui/package.json +++ b/contrib/gui/package.json @@ -23,6 +23,7 @@ "@types/jest": "^29.5.6", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", + "@types/ws": "^8.5.8", "@typescript-eslint/eslint-plugin": "^6.7.0", "@typescript-eslint/parser": "^6.7.0", "@vitejs/plugin-react": "^4.0.3", @@ -30,6 +31,7 @@ "eslint-plugin-react": "^7.33.2", "jest": "^29.7.0", "typescript": "^4.2.4", - "vite": "^4.4.4" + "vite": "^4.4.4", + "ws": "^8.14.2" } } diff --git a/contrib/gui/test/conduit.test.ts b/contrib/gui/test/conduit.test.ts new file mode 100644 index 00000000..46edf7f4 --- /dev/null +++ b/contrib/gui/test/conduit.test.ts @@ -0,0 +1,69 @@ +// +// Copyright (C) 2023 Nick Gasson +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +import Conduit from "../lib/conduit"; +import WebSocket from "ws"; + +class BrowserWebSocket { + private socket: WebSocket; + + onmessage: ((ev: { data: string | ArrayBuffer }) => any) | null = null; + onclose: ((ev: {[key: string]: any}) => any) | null = null; + onopen: ((ev: object) => any) | null = null; + + constructor(url: string) { + this.socket = new WebSocket(url); + this.socket.binaryType = "arraybuffer"; + + this.socket.on("open", () => { this.onopen?.({}); }); + + this.socket.on("close", () => { this.onclose?.({}); }); + + this.socket.on("error", console.error); + + this.socket.on("message", (data, isBinary) => { + if (isBinary) { + this.onmessage?.({ data: data as ArrayBuffer }); + } else { + this.onmessage?.({ data: data.toString() }); + } + }); + } + + send(text: string) { + this.socket.send(text); + } + + close() { + this.socket.close(); + } +} + +test("sanity", (done) => { + const c = new Conduit((url) => new BrowserWebSocket(url)); + + c.onOpen = () => {}; + + c.onConsoleOutput = () => {}; + + c.onStartSim = (top) => { + expect(top).toBe("WORK.LFSR.elab"); + c.close(); + }; + + c.onClose = done; +}); -- 2.39.2