From 2fcfe4328c230d2e9d64ae254b4b757e2bf251ab Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Fri, 3 Nov 2023 11:59:58 -0400 Subject: [PATCH] Cross-runtime testing --- deno.jsonc | 3 +- justfile | 37 ++++++++++++++++++--- src/bun/{index.ts => mod.ts} | 0 src/bun/test_base.ts | 38 ++++++++++++++++++++++ src/common/mod.ts | 3 ++ src/common/{index.ts => runtime.ts} | 17 +++++++++- src/common/strings_test.ts | 14 +++++--- src/common/termios.ts | 4 +-- src/common/test_base.ts | 13 ++++++++ src/deno/{index.ts => mod.ts} | 0 src/deno/test_base.ts | 50 +++++++++++++++++++++++++++-- src/scroll.ts | 4 +-- 12 files changed, 164 insertions(+), 19 deletions(-) rename src/bun/{index.ts => mod.ts} (100%) create mode 100644 src/bun/test_base.ts create mode 100644 src/common/mod.ts rename src/common/{index.ts => runtime.ts} (72%) create mode 100644 src/common/test_base.ts rename src/deno/{index.ts => mod.ts} (100%) diff --git a/deno.jsonc b/deno.jsonc index c82e5a1..65ccb9d 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -3,7 +3,8 @@ "lint": { "include": ["src/"], "rules": { - "tags": ["recommended"] + "tags": ["recommended"], + "exclude": ["no-explicit-any"] } }, "fmt": { diff --git a/justfile b/justfile index e5ae9cd..48b8680 100644 --- a/justfile +++ b/justfile @@ -3,24 +3,51 @@ default: @just --list # Typescript checking -check: +check: lint deno check --unstable --all -c deno.jsonc ./src/deno/*.ts ./src/common/*.ts # Code linting -deno-lint: +lint: deno lint # Reformat the code fmt: deno fmt +# Run tests with all the runtimes +test: deno-test bun-test + +# Clean up any generated files +clean: + rm -rf .deno-cover + rm -rf cover + rm -rf docs + +######################################################################################################################## +# Bun-specific commands +######################################################################################################################## + +# Test with bun +bun-test: + bun test --coverage + # Run with bun bun-run: bun run ./src/scroll.ts +######################################################################################################################## +# Deno-specific commands +######################################################################################################################## + +# Test with deno +deno-test: + deno test --allow-all + +# Create test coverage report with deno +deno-coverage: + deno test --allow-all --coverage=.deno-cover + deno coverage --lcov .deno-cover + # Run with deno deno-run: deno run --allow-all --allow-ffi --deny-net --deny-hrtime --unstable ./src/scroll.ts - -deno-test: - deno test --allow-all \ No newline at end of file diff --git a/src/bun/index.ts b/src/bun/mod.ts similarity index 100% rename from src/bun/index.ts rename to src/bun/mod.ts diff --git a/src/bun/test_base.ts b/src/bun/test_base.ts new file mode 100644 index 0000000..c40de78 --- /dev/null +++ b/src/bun/test_base.ts @@ -0,0 +1,38 @@ +/** + * Adapt the bun test interface to the shared testing interface + */ +import {test as btest, expect } from 'bun:test'; +import {ITestBase} from "../common/mod"; + +class TestBase implements ITestBase { + test(name: string, fn: () => void) { + return btest(name, fn); + } + + assertEquals(actual: unknown, expected: unknown): void { + return expect(actual).toEqual(expected); + } + + assertExists(actual: unknown): void { + return expect(actual).toBeDefined(); + } + + assertInstanceOf(actual: unknown, expectedType: any): void { + return expect(actual).toBeInstanceOf(expectedType); + } + + assertNotEquals(actual: unknown, expected: unknown): void { + return expect(actual).not.toBe(expected); + } + + assertFalse(actual: boolean): void { + return expect(actual).toBe(false); + } + + assertTrue(actual: boolean): void { + return expect(actual).toBe(true); + } +} + +const testBase = new TestBase(); +export default testBase; \ No newline at end of file diff --git a/src/common/mod.ts b/src/common/mod.ts new file mode 100644 index 0000000..9d77c93 --- /dev/null +++ b/src/common/mod.ts @@ -0,0 +1,3 @@ +export * from './runtime.ts'; +export * from './strings.ts'; +export type { ITestBase } from './test_base.ts'; diff --git a/src/common/index.ts b/src/common/runtime.ts similarity index 72% rename from src/common/index.ts rename to src/common/runtime.ts index 889b459..a853207 100644 --- a/src/common/index.ts +++ b/src/common/runtime.ts @@ -30,7 +30,7 @@ export const getRuntime = (): RunTime => { /** * Import a runtime-specific module * - * eg. to load "src/bun/index.ts", if the runtime is bun, + * eg. to load "src/bun/mod.ts", if the runtime is bun, * you can use like so `await importForRuntime('index')`; * * @param path - the path within the runtime module @@ -49,3 +49,18 @@ export const importForRuntime = async (path: string) => { return await import(importPath); }; + +/** + * Import the default export for a runtime-specific module + * (this is just a simple wrapper of `importForRuntime`) + * + * @param path - the path within the runtime module + */ +export const importDefaultForRuntime = async (path: string) => { + const pkg = await importForRuntime(path); + if ('default' in pkg) { + return pkg.default; + } + + return null; +}; diff --git a/src/common/strings_test.ts b/src/common/strings_test.ts index df92376..f4d6b1b 100644 --- a/src/common/strings_test.ts +++ b/src/common/strings_test.ts @@ -1,8 +1,12 @@ -import { importForRuntime } from './index.ts'; -import { chars } from './strings.ts'; +import { chars, importDefaultForRuntime, is_ascii, ITestBase } from './mod.ts'; -const { test, assertEquals } = await importForRuntime('test_base'); +const t: ITestBase = await importDefaultForRuntime('test_base'); -test('chars fn properly splits strings into unicode characters', () => { - assertEquals(chars('๐Ÿ˜บ๐Ÿ˜ธ๐Ÿ˜น'), ['๐Ÿ˜บ', '๐Ÿ˜ธ', '๐Ÿ˜น']); +t.test('chars fn properly splits strings into unicode characters', () => { + t.assertEquals(chars('๐Ÿ˜บ๐Ÿ˜ธ๐Ÿ˜น'), ['๐Ÿ˜บ', '๐Ÿ˜ธ', '๐Ÿ˜น']); +}); + +t.test('is_ascii properly descerns ascii chars', () => { + t.assertTrue(is_ascii('asjyverkjhsdf1928374')); + t.assertFalse(is_ascii('๐Ÿ˜บacalskjsdf')); }); diff --git a/src/common/termios.ts b/src/common/termios.ts index 66ab955..f71d173 100644 --- a/src/common/termios.ts +++ b/src/common/termios.ts @@ -1,4 +1,4 @@ -import { die, importForRuntime } from './index.ts'; +import { die, importForRuntime } from './mod.ts'; export const STDIN_FILENO = 0; export const STOUT_FILENO = 1; @@ -122,7 +122,7 @@ export const getTermios = async () => { } const oldTermiosPtr = getPointer(this.#cookedTermios); - let res = tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr); + const res = tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr); if (res === -1) { die('Failed to restore canonical mode.'); } diff --git a/src/common/test_base.ts b/src/common/test_base.ts new file mode 100644 index 0000000..b01e050 --- /dev/null +++ b/src/common/test_base.ts @@ -0,0 +1,13 @@ +/** + * The shared test interface, so tests can be run by both runtimes + */ +export interface ITestBase { + test(name: string, fn: () => void): void; + assertEquals(actual: unknown, expected: unknown): void; + assertNotEquals(actual: unknown, expected: unknown): void; + assertStrictEquals(actual: unknown, expected: unknown): void; + assertExists(actual: unknown): void; + assertInstanceOf(actual: unknown, expectedType: any): void; + assertTrue(actual: boolean): void; + assertFalse(actual: boolean): void; +} diff --git a/src/deno/index.ts b/src/deno/mod.ts similarity index 100% rename from src/deno/index.ts rename to src/deno/mod.ts diff --git a/src/deno/test_base.ts b/src/deno/test_base.ts index c55b2aa..f34d188 100644 --- a/src/deno/test_base.ts +++ b/src/deno/test_base.ts @@ -1,7 +1,51 @@ -export const test = Deno.test; -export { - assert, +import { ITestBase } from '../common/mod.ts'; + +import { assertEquals, + assertExists, + assertInstanceOf, + AssertionError, assertNotEquals, assertStrictEquals, } from 'https://deno.land/std/assert/mod.ts'; + +class TestBase implements ITestBase { + test(name: string, fn: () => void): void { + return Deno.test(name, fn); + } + + assertEquals(actual: unknown, expected: unknown): void { + return assertEquals(actual, expected); + } + + assertStrictEquals(actual: unknown, expected: unknown) { + return assertStrictEquals(actual, expected); + } + + assertNotEquals(actual: unknown, expected: unknown): void { + return assertNotEquals(actual, expected); + } + + assertExists(actual: unknown, msg?: string): void { + return assertExists(actual, msg); + } + + assertInstanceOf(actual: unknown, expectedType: any): void { + return assertInstanceOf(actual, expectedType); + } + + assertTrue(actual: boolean): void { + if (actual !== true) { + throw new AssertionError(`actual: "${actual}" expected to be true"`); + } + } + + assertFalse(actual: boolean): void { + if (actual !== false) { + throw new AssertionError(`actual: "${actual}" expected to be false"`); + } + } +} + +export const testBase = new TestBase(); +export default testBase; diff --git a/src/scroll.ts b/src/scroll.ts index 3cda157..30067f1 100644 --- a/src/scroll.ts +++ b/src/scroll.ts @@ -8,12 +8,12 @@ export enum RunTime { Unknown = 'common', } -import { importForRuntime } from './common/index.ts'; +import { importForRuntime } from './common/mod.ts'; /** * Determine the runtime strategy, and go! */ (async () => { - const { main } = await importForRuntime('./index.ts'); + const { main } = await importForRuntime('./mod.ts'); await main(); })();