From d8583e68cc50bfddb18b60c55b611ace3bb1849a Mon Sep 17 00:00:00 2001 From: sinclairzx81 Date: Mon, 12 Aug 2024 14:54:00 +0900 Subject: [PATCH] Revision 0.8.6 (#29) * Support Interactive Test Runner * Version --- example/index.css | 9 ---- example/index.html | 4 +- hammer.mjs | 5 +- package-lock.json | 4 +- package.json | 3 +- src/net/net.mts | 2 +- src/webrtc/webrtc.mts | 21 +++----- test/index.css | 5 ++ test/index.html | 8 +++ test/index.mts | 23 ++++---- test/test/reporter.mts | 120 ++++++++++++++++++++++++----------------- test/test/test.mts | 4 +- 12 files changed, 118 insertions(+), 90 deletions(-) create mode 100644 test/index.css create mode 100644 test/index.html diff --git a/example/index.css b/example/index.css index 934f5ca..e69de29 100644 --- a/example/index.css +++ b/example/index.css @@ -1,9 +0,0 @@ -html, -body { - background: #111; -} -canvas { - width: 320px; - height: 200px; - border: 1px dashed; -} diff --git a/example/index.html b/example/index.html index e3b7a2b..64a1e4a 100644 --- a/example/index.html +++ b/example/index.html @@ -4,5 +4,7 @@ - + +

Use the console to view logs

+ diff --git a/hammer.mjs b/hammer.mjs index 21752e8..ad75099 100644 --- a/hammer.mjs +++ b/hammer.mjs @@ -23,10 +23,13 @@ export async function start(target = 'target/example') { // ------------------------------------------------------------------------------- // Test // ------------------------------------------------------------------------------- +export async function test_serve(target = 'target/test') { + await shell(`hammer serve test/index.html --dist ${target}`) +} export async function test(filter = '', target = 'target/test') { await shell(`hammer build test/index.mts --dist ${target} --platform node`) const server = require('http').createServer((_, res) => res.end('')).listen(5010) - await shell(`drift url http://localhost:5010 wait 1000 run ./${target}/index.mjs args ${filter}`) + await shell(`drift url http://localhost:5010 wait 1000 run ./${target}/index.mjs args "${filter}"`) server.close() } // ------------------------------------------------------------------------------- diff --git a/package-lock.json b/package-lock.json index 7531d39..f11641e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sinclair/smoke", - "version": "0.8.5", + "version": "0.8.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sinclair/smoke", - "version": "0.8.5", + "version": "0.8.6", "license": "MIT", "devDependencies": { "@sinclair/drift": "^0.9.1", diff --git a/package.json b/package.json index 7597d2e..87c58b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sinclair/smoke", - "version": "0.8.5", + "version": "0.8.6", "description": "Run Web Servers in Web Browsers over WebRTC", "type": "module", "main": "./index.mjs", @@ -11,6 +11,7 @@ "format": "hammer task format", "clean": "hammer task clean", "test": "hammer task test", + "test:serve": "hammer task test_serve", "build": "hammer task build", "publish": "hammer task publish" }, diff --git a/src/net/net.mts b/src/net/net.mts index d7c2053..5a430df 100644 --- a/src/net/net.mts +++ b/src/net/net.mts @@ -49,7 +49,7 @@ export class NetModule { /** Establishes a connection to a remote Net listener */ public async connect(options: NetConnectOptions): Promise { const [hostname, port] = [options.hostname ?? 'localhost', options.port] - const [peer, datachannel] = await this.#webrtc.connect(hostname, port) + const [peer, datachannel] = await this.#webrtc.connect(hostname, port, { ordered: true, maxRetransmits: 16 }) return new NetSocket(peer, datachannel) } } diff --git a/src/webrtc/webrtc.mts b/src/webrtc/webrtc.mts index 279fde1..5d15e21 100644 --- a/src/webrtc/webrtc.mts +++ b/src/webrtc/webrtc.mts @@ -95,21 +95,14 @@ export class WebRtcModule implements Dispose.Dispose { return listener } /** Connects to a remote peer */ - public async connect(remoteAddress: string, port: number): Promise<[WebRtcPeer, RTCDataChannel]> { - const open = new Async.Deferred<[WebRtcPeer, RTCDataChannel]>() + public async connect(remoteAddress: string, port: number, options: RTCDataChannelInit): Promise<[WebRtcPeer, RTCDataChannel]> { const peer = await this.#resolvePeer(await this.#resolveAddress(remoteAddress)) - const datachannel = peer.connection.createDataChannel(port.toString(), { ordered: true, maxRetransmits: 16 }) - datachannel.addEventListener('open', () => { - peer.datachannels.add(datachannel) - open.resolve([peer, datachannel]) - }) - datachannel.addEventListener('close', () => { - peer.datachannels.delete(datachannel) - }) - setTimeout(() => { - open.reject(new Error(`Connection to '${remoteAddress}:${port}' timed out`)) - }, 4000) - return await open.promise() + const datachannel = peer.connection.createDataChannel(port.toString(), options) + const awaiter = new Async.Deferred<[WebRtcPeer, RTCDataChannel]>() + datachannel.addEventListener('close', () => peer.datachannels.delete(datachannel)) + datachannel.addEventListener('open', () => peer.datachannels.add(datachannel)) + datachannel.addEventListener('open', () => awaiter.resolve([peer, datachannel])) + return Async.timeout(awaiter.promise(), { timeout: 4000, error: new Error(`Connection to '${remoteAddress}:${port}' timed out`) }) } /** Terminates the RTCPeerConnection associated with this remoteAddress and asks the remote peer to do the same */ public async terminate(remoteAddress: string) { diff --git a/test/index.css b/test/index.css new file mode 100644 index 0000000..97c4d4d --- /dev/null +++ b/test/index.css @@ -0,0 +1,5 @@ +html, +body { + font-family: monospace; + background-color: #222; +} diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..e3b7a2b --- /dev/null +++ b/test/index.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/index.mts b/test/index.mts index f8e8e2e..824bf67 100644 --- a/test/index.mts +++ b/test/index.mts @@ -17,15 +17,20 @@ import './net/index.mjs' import './os/index.mjs' // ------------------------------------------------------------------ -// Runner +// Drift // ------------------------------------------------------------------ declare const Drift: any -if ('Drift' in globalThis) { - Test.run({ filter: Drift.args[0] }).then((result) => { - return result.success ? Drift.close(0) : Drift.close(1) - }) -} else { - Test.run({ filter: '' }).then((result) => { - console.log('done', result) - }) + +// ------------------------------------------------------------------ +// Runner +// ------------------------------------------------------------------ +function resolve_filter() { + if ('Drift' in globalThis) return Drift.args[0] + const searchParams = new URLSearchParams(window.location.search) + return searchParams.get('filter') ?? '' } + +Test.run({ filter: resolve_filter() }).then((result) => { + if ('Drift' in globalThis) return result.success ? Drift.close(0) : Drift.close(1) + console.log(result) +}) diff --git a/test/test/reporter.mts b/test/test/reporter.mts index 8d66426..4186397 100644 --- a/test/test/reporter.mts +++ b/test/test/reporter.mts @@ -34,6 +34,42 @@ import type { DescribeContext } from './describe.mjs' import type { ItContext } from './it.mjs' import { ValueFormatter } from './formatter.mjs' +// Mapping of ANSI codes to their respective hex color values +const ansiToHex: Record = { + '\x1b[30m': '#000000', // black + '\x1b[31m': '#FF0000', // red + '\x1b[32m': '#00FF00', // green + '\x1b[33m': '#FFFF00', // yellow + '\x1b[34m': '#0000FF', // blue + '\x1b[35m': '#FF00FF', // magenta + '\x1b[36m': '#00FFFF', // cyan + '\x1b[37m': '#FFFFFF', // white + '\x1b[90m': '#808080', // gray + '\x1b[91m': '#FF8080', // lightRed + '\x1b[92m': '#80FF80', // lightGreen + '\x1b[93m': '#FFFF80', // lightYellow + '\x1b[94m': '#8080FF', // lightBlue + '\x1b[95m': '#FF80FF', // lightMagenta + '\x1b[96m': '#80FFFF', // lightCyan + '\x1b[97m': '#FFFFFF', // lightWhite + '\x1b[40m': '#000000', // black (background) + '\x1b[41m': '#FF0000', // red (background) + '\x1b[42m': '#00FF00', // green (background) + '\x1b[43m': '#FFFF00', // yellow (background) + '\x1b[44m': '#0000FF', // blue (background) + '\x1b[45m': '#FF00FF', // magenta (background) + '\x1b[46m': '#00FFFF', // cyan (background) + '\x1b[47m': '#FFFFFF', // white (background) + '\x1b[100m': '#808080', // gray (background) + '\x1b[101m': '#FF8080', // lightRed (background) + '\x1b[102m': '#80FF80', // lightGreen (background) + '\x1b[103m': '#FFFF80', // lightYellow (background) + '\x1b[104m': '#8080FF', // lightBlue (background) + '\x1b[105m': '#FF80FF', // lightMagenta (background) + '\x1b[106m': '#80FFFF', // lightCyan (background) + '\x1b[107m': '#FFFFFF', // lightWhite (background) +} + // ------------------------------------------------------------------ // ConsoleBuffer // ------------------------------------------------------------------ @@ -92,8 +128,7 @@ export interface Reporter { onUnitEnd(unit: ItContext): any onSummary(context: DescribeContext): any } - -export class StdoutReporter implements Reporter { +export class DocumentReporter implements Reporter { readonly #writer: WritableStreamDefaultWriter constructor() { this.#writer = stdout.getWriter() @@ -101,23 +136,34 @@ export class StdoutReporter implements Reporter { // ---------------------------------------------------------------- // Stdout // ---------------------------------------------------------------- + #currentColor: string | undefined + #color(code: string, callback: Function) { + this.#writer.write(Buffer.encode(code)) + this.#currentColor = code + callback() + this.#currentColor = undefined + this.#writer.write(Buffer.encode(Ansi.reset)) + } #newline() { this.#writer.write(Buffer.encode(`\n`)) + if (!('document' in globalThis)) return + const br = document.createElement('br') + document.body.appendChild(br) } #write(message: string) { this.#writer.write(Buffer.encode(message)) - } - #ansi(message: string) { - this.#writer.write(Buffer.encode(message)) + if (!('document' in globalThis)) return + const span = document.createElement('span') + span.style.color = ansiToHex[this.#currentColor as never] as never + span.innerHTML = message + document.body.appendChild(span) } // ---------------------------------------------------------------- // Handlers // ---------------------------------------------------------------- public onContextBegin(context: DescribeContext) { if (context.name === 'root') return - this.#ansi(Ansi.color.lightBlue) - this.#write(context.name) - this.#ansi(Ansi.reset) + this.#color(Ansi.color.lightBlue, () => this.#write(context.name)) this.#newline() } public onContextEnd(context: DescribeContext) { @@ -125,51 +171,31 @@ export class StdoutReporter implements Reporter { this.#newline() } public onUnitBegin(unit: ItContext) { - this.#ansi(Ansi.color.gray) - this.#write(` - ${unit.name}`) - this.#ansi(Ansi.reset) + this.#color(Ansi.color.gray, () => this.#write(` - ${unit.name}`)) } public onUnitEnd(unit: ItContext) { if (unit.error === null) { - this.#ansi(Ansi.color.green) - this.#write(` pass`) - this.#ansi(Ansi.reset) - this.#ansi(Ansi.color.lightBlue) - this.#write(` ${unit.elapsed.toFixed()} ms`) - this.#ansi(Ansi.reset) + this.#color(Ansi.color.green, () => this.#write(` pass`)) + this.#color(Ansi.color.lightBlue, () => this.#write(` ${unit.elapsed.toFixed()} ms`)) } else { - this.#ansi(Ansi.color.lightRed) - this.#write(' fail') - this.#ansi(Ansi.reset) + this.#color(Ansi.color.lightRed, () => this.#write(' fail')) } this.#newline() } #printFailureSummary(context: DescribeContext) { for (const error of context.failures()) { - this.#ansi(Ansi.color.lightBlue) - this.#write(`${error.context} `) - this.#ansi(Ansi.reset) - this.#ansi(Ansi.color.gray) - this.#write(`${error.unit}`) - this.#ansi(Ansi.reset) + this.#color(Ansi.color.lightBlue, () => this.#write(`${error.context} `)) + this.#color(Ansi.color.gray, () => this.#write(`${error.unit}`)) this.#newline() this.#newline() - - this.#write(Ansi.color.lightRed) - this.#write(` error`) - this.#ansi(Ansi.reset) - this.#write(`: ${error.error.message}`) + this.#color(Ansi.color.lightRed, () => this.#write(` error`)) + this.#color(Ansi.color.gray, () => this.#write(`: ${error.error.message}`)) this.#newline() if (error.error instanceof Assert.AssertError) { - this.#ansi(Ansi.color.lightGreen) - this.#write(` expect`) - this.#ansi(Ansi.reset) + this.#color(Ansi.color.lightGreen, () => this.#write(` expect`)) this.#write(`: ${ValueFormatter.format(error.error.expect)}`) this.#newline() - - this.#ansi(Ansi.color.lightRed) - this.#write(` actual`) - this.#ansi(Ansi.reset) + this.#color(Ansi.color.lightRed, () => this.#write(` actual`)) this.#write(`: ${ValueFormatter.format(error.error.actual)}`) this.#newline() } @@ -178,22 +204,16 @@ export class StdoutReporter implements Reporter { this.#newline() } #printCompletionSummary(context: DescribeContext) { - this.#ansi(Ansi.color.lightBlue) - this.#write('elapsed') - this.#ansi(Ansi.reset) - this.#write(`: ${context.elapsed.toFixed(0)} ms`) + this.#color(Ansi.color.lightBlue, () => this.#write('elapsed')) + this.#color(Ansi.color.gray, () => this.#write(`: ${context.elapsed.toFixed(0)} ms`)) this.#newline() - this.#ansi(Ansi.color.lightBlue) - this.#write('passed') - this.#ansi(Ansi.reset) - this.#write(`: ${context.passCount}`) + this.#color(Ansi.color.lightBlue, () => this.#write('passed')) + this.#color(Ansi.color.gray, () => this.#write(`: ${context.passCount}`)) this.#newline() - this.#ansi(Ansi.color.lightBlue) - this.#write('failed') - this.#ansi(Ansi.reset) - this.#write(`: ${context.failCount}`) + this.#color(Ansi.color.lightBlue, () => this.#write('failed')) + this.#color(Ansi.color.gray, () => this.#write(`: ${context.failCount}`)) this.#newline() } public onSummary(context: DescribeContext) { diff --git a/test/test/test.mts b/test/test/test.mts index 489fee4..4f67380 100644 --- a/test/test/test.mts +++ b/test/test/test.mts @@ -29,7 +29,7 @@ THE SOFTWARE. import * as Async from './async/index.mjs' import { DescribeContext } from './describe.mjs' import { Options } from './options.mjs' -import { StdoutReporter } from './reporter.mjs' +import { DocumentReporter } from './reporter.mjs' import { Result } from './result.mjs' import { ItContext } from './it.mjs' @@ -64,7 +64,7 @@ export function after(callback: Function) { function resolveOptions>(options: T): Options { return { filter: options.filter ?? '', - reporter: options.reporter ?? new StdoutReporter(), + reporter: options.reporter ?? new DocumentReporter(), } } export async function run(options: Partial = {}): Promise {