Skip to content

Commit

Permalink
Support Interactive Test Runner
Browse files Browse the repository at this point in the history
  • Loading branch information
sinclairzx81 committed Aug 12, 2024
1 parent 5c50ee8 commit 4b3027e
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 87 deletions.
9 changes: 0 additions & 9 deletions example/index.css
Original file line number Diff line number Diff line change
@@ -1,9 +0,0 @@
html,
body {
background: #111;
}
canvas {
width: 320px;
height: 200px;
border: 1px dashed;
}
4 changes: 3 additions & 1 deletion example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
<link rel="stylesheet" href="index.css" />
<script type="module" src="index.mts"></script>
</head>
<body></body>
<body>
<p>Use the console to view logs</p>
</body>
</html>
5 changes: 4 additions & 1 deletion hammer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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('<html><head></head></html>')).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()
}
// -------------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
2 changes: 1 addition & 1 deletion src/net/net.mts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class NetModule {
/** Establishes a connection to a remote Net listener */
public async connect(options: NetConnectOptions): Promise<NetSocket> {
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)
}
}
21 changes: 7 additions & 14 deletions src/webrtc/webrtc.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions test/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
html,
body {
font-family: monospace;
background-color: #222;
}
8 changes: 8 additions & 0 deletions test/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="index.css" />
<script type="module" src="index.mts"></script>
</head>
<body></body>
</html>
23 changes: 14 additions & 9 deletions test/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
120 changes: 70 additions & 50 deletions test/test/reporter.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> = {
'\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
// ------------------------------------------------------------------
Expand Down Expand Up @@ -92,84 +128,74 @@ export interface Reporter {
onUnitEnd(unit: ItContext): any
onSummary(context: DescribeContext): any
}

export class StdoutReporter implements Reporter {
export class DocumentReporter implements Reporter {
readonly #writer: WritableStreamDefaultWriter<Uint8Array>
constructor() {
this.#writer = stdout.getWriter()
}
// ----------------------------------------------------------------
// 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) {
if (context.name === 'root') return
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()
}
Expand All @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions test/test/test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -64,7 +64,7 @@ export function after(callback: Function) {
function resolveOptions<T extends Partial<Options>>(options: T): Options {
return {
filter: options.filter ?? '',
reporter: options.reporter ?? new StdoutReporter(),
reporter: options.reporter ?? new DocumentReporter(),
}
}
export async function run(options: Partial<Options> = {}): Promise<Result> {
Expand Down

0 comments on commit 4b3027e

Please sign in to comment.