feat: getting something going (#1)

* feat: basics

* chore: lint

* wip: slightly better expect logic

* test: use self for test cases

* ci: enable test, build steps

* chore: lint

* docs: readme stub

* feat: toBe

* chore: lockfile
This commit is contained in:
Marc 2023-03-27 01:01:50 -04:00
parent c1d6171542
commit 329103c470
Signed by: marc
GPG key ID: 048E042F22B5DC79
16 changed files with 1574 additions and 2 deletions

97
.github/workflows/nodejs.yml vendored Normal file
View file

@ -0,0 +1,97 @@
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
dependencies:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14, 16, 18]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- uses: actions/cache@v3
id: dependencies-cache
env:
cache-name: dependencies-cache
with:
path: .yarn
key: ${{ runner.os }}-build-${{env.cache-name}}-${{ hashFiles('**/yarn.lock') }}-node-${{ matrix.node-version }}
- if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
run: yarn
lint:
runs-on: ubuntu-latest
needs: dependencies
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- uses: actions/cache@v3
id: dependencies-cache
env:
cache-name: dependencies-cache
with:
path: .yarn
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}-node-18
- run: |
yarn
yarn lint
test:
runs-on: ubuntu-latest
needs: dependencies
strategy:
matrix:
node-version: [14, 16, 18]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- uses: actions/cache@v3
id: dependencies-cache
env:
cache-name: dependencies-cache
with:
path: .yarn
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}-node-${{ matrix.node-version }}
- run: |
yarn
yarn test
build:
runs-on: ubuntu-latest
needs: dependencies
strategy:
matrix:
node-version: [14, 16, 18]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- uses: actions/cache@v3
id: dependencies-cache
env:
cache-name: dependencies-cache
with:
path: .yarn
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}-node-${{ matrix.node-version }}
- run: |
yarn
yarn build

7
.gitignore vendored
View file

@ -6,6 +6,13 @@ yarn-debug.log*
yarn-error.log* yarn-error.log*
lerna-debug.log* lerna-debug.log*
# Yarntifacts
.yarn/*
!.yarn/plugins
!.yarn/versions
!.yarn/sdks
.pnp.*
# Diagnostic reports (https://nodejs.org/api/report.html) # Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

1
.nvmrc Normal file
View file

@ -0,0 +1 @@
18.15.0

873
.yarn/releases/yarn-3.4.1.cjs vendored Executable file

File diff suppressed because one or more lines are too long

1
.yarnrc.yml Normal file
View file

@ -0,0 +1 @@
yarnPath: .yarn/releases/yarn-3.4.1.cjs

View file

@ -1,2 +1,16 @@
# works-on-my-machine # works-on-my-machine (womm)
A full-nonsense pet test runner
> ✨ A full-nonsense pet test runner ✨
## So, what is this?
Software in the NodeJS ecosystem tends to depend _a lot_ on external dependencies. What would a test runner without any
look like? This is the question that started all of this.
`womm` is a pet test runner that follows the general direction of `jest` and `playwright` with a few additional
constraints:
- It must not have any production dependencies (some development dependencies are permissible, like `typescript` and
`rome`, but keeping it to a minimum);
- It must be compatible with the general API exposed by Jest and the like, for familiarity;
- It must use itself for testing.

22
package.json Normal file
View file

@ -0,0 +1,22 @@
{
"name": "testrunner",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"packageManager": "yarn@3.4.1",
"bin": {
"womm": "./dist/runner.js"
},
"scripts": {
"lint": "rome check src tests && rome format src tests",
"lint:fix": "rome check src tests --apply && rome format src tests --write",
"test": "ts-node ./src/runner.ts ./tests",
"build": "tsc --project ."
},
"devDependencies": {
"@types/node": "^18.15.10",
"rome": "^11.0.0",
"ts-node": "^10.9.1",
"typescript": "^4.0.0"
}
}

21
rome.json Normal file
View file

@ -0,0 +1,21 @@
{
"linter": {
"enabled": true,
"rules": {
"suspicious": {
"noExplicitAny": "warn"
}
}
},
"formatter": {
"enabled": true,
"lineWidth": 120
},
"javascript": {
"formatter": {
"semicolons": "asNeeded",
"quoteStyle": "single"
}
}
}

16
src/context.ts Normal file
View file

@ -0,0 +1,16 @@
import { type TestCaseLabel, type TestFilePath, type IContext } from './types'
let runnerContext: IContext | null
function getContext(): IContext {
if (!runnerContext) {
runnerContext = {
collectedTests: new Map<TestFilePath, any>(),
collecting: true,
}
}
return runnerContext
}
export default getContext()

37
src/expect.ts Normal file
View file

@ -0,0 +1,37 @@
class TestAssertionFailed extends Error {
constructor(message: string) {
super(message)
this.name = 'TestAssertionFailed'
}
}
class Expectation<ValueType> {
value: ValueType
constructor(value: ValueType) {
this.value = value
}
toEqual(value: ValueType) {
const isPrimitive = ['boolean', 'number'].includes(typeof value)
const isString = !isPrimitive && typeof value === 'string'
if ((isPrimitive || isString) && this.value === value) {
return
}
throw new TestAssertionFailed(`NotEqual! ${this.value} != ${value}`)
}
toBe(value: ValueType) {
if (Object.is(this.value, value)) return
throw new TestAssertionFailed(`NotEqual! ${this.value} !== ${value}`)
}
}
function expect<ValueType>(value: ValueType) {
return new Expectation(value)
}
export default expect

88
src/runner.ts Normal file
View file

@ -0,0 +1,88 @@
import Context from './context'
import util from 'util'
import childProcess from 'child_process'
import { promises as fs, type Dirent, type PathLike } from 'fs'
import path from 'path'
function greenText(text: string): string {
return `\x1b[32m${text}\x1b[0m`
}
function redText(text: string): string {
return `\x1b[31m${text}\x1b[0m`
}
/*
* Collects test files recursively starting from the provided root
* path.
*/
async function collectTests(root: string): Promise<Array<string>> {
const collectedHere = []
const rootStats = await fs.stat(root)
if (rootStats.isFile() && path.basename(root).endsWith('.test.ts')) {
collectedHere.push(root)
} else if (rootStats.isDirectory()) {
const content = await fs.readdir(root, { encoding: 'utf8' })
const segmentedCollectedPaths = await Promise.all(
content.map((item: string) => collectTests(path.join(root, item))),
)
const collectedPaths = segmentedCollectedPaths.reduce((acc: Array<string>, collectedSegment: Array<string>) => {
acc.push(...collectedSegment)
return acc
}, [] as Array<string>)
collectedHere.push(...collectedPaths)
}
return collectedHere
}
/*
* Collects then executes test cases based on provided test files.
*/
async function runTests(collectedPaths: Array<string>) {
/*
* Test files are imported dynamically so the `test` functions
* defined in them are run. Running the functions doesn't actually
* run the test, but instead builds the catalog of
* known cases, which are executed in the next step.
*/
await Promise.all(
collectedPaths.map(async (collectedPath) => {
await import(path.resolve(collectedPath))
}),
)
console.log(greenText(`Collected ${Context.collectedTests.size} cases.`))
/*
* Each case collected is executed and can fail independently
* of its peers.
*/
for (let entry of Context.collectedTests.entries()) {
const [testLabel, testCase] = entry
console.group(greenText(`Test: ${testLabel}`))
try {
testCase.call()
} catch (e) {
console.error(redText(`FAIL ${testLabel}`))
console.error(e)
}
console.groupEnd()
}
} /*
* Logic executed when running the test runner CLI.
*/
;(async () => {
console.group('Test run')
const collectionRoot = process.argv[2]
const collectedTests = await collectTests(collectionRoot)
runTests(collectedTests)
console.groupEnd()
})().catch((e) => {
throw e
})

18
src/testCaseUtils.ts Normal file
View file

@ -0,0 +1,18 @@
import Context from './context'
import expect from './expect'
function test(label: string, testCase: any) {
// Hack to get the file that contains the test definition.
const _prepareStackTrace = Error.prepareStackTrace
Error.prepareStackTrace = (_, stack) => stack
const stack = new Error().stack?.slice(1)
Error.prepareStackTrace = _prepareStackTrace
const testCaseLocation = stack?.[0] ?? 'unknown'
Context.collectedTests.set(`${testCaseLocation}:${label}`, testCase)
}
const it = test
export { it, test, expect }

8
src/types.ts Normal file
View file

@ -0,0 +1,8 @@
export type TestCaseLabel = string
export type TestFilePath = string
export type TestCaseFunction = () => void
export interface IContext {
collectedTests: Map<TestFilePath, any>
collecting: boolean
}

73
tests/expect.test.ts Normal file
View file

@ -0,0 +1,73 @@
import { test, expect } from '../src/testCaseUtils'
test('Equality (number)', () => {
expect(1).toEqual(1)
})
test('Equality (string)', () => {
expect('expectations').toEqual('expectations')
})
test('Equality (boolean)', () => {
expect(true).toEqual(true)
})
test('Equality (failed - number)', () => {
try {
expect(1).toEqual(2)
} catch (e) {
expect(1).toEqual(1)
}
})
test('Equality (failed - string)', () => {
try {
expect('expectation').toEqual('something else')
} catch (e) {
expect(1).toEqual(1)
}
})
test('Equality (failed - boolean)', () => {
try {
expect(true).toEqual(false)
} catch (e) {
expect(1).toEqual(1)
}
})
test('Identity comparison (number)', () => {
expect(1).toBe(1)
})
test('Identity comparison (boolean)', () => {
expect(true).toBe(true)
})
test('Identity comparison (string)', () => {
expect('identity').toBe('identity')
})
test('Identity comparison (failed - number)', () => {
try {
expect(1).toEqual(2)
} catch (e) {
expect(1).toEqual(1)
}
})
test('Identity comparison (failed - boolean)', () => {
try {
expect(false).toBe(true)
} catch (e) {
expect(1).toEqual(1)
}
})
test('Identity comparison (failed - string)', () => {
try {
expect('yes').toBe('no')
} catch (e) {
expect(1).toEqual(1)
}
})

22
tsconfig.json Normal file
View file

@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"allowJs": false,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./",
"removeComments": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src"],
"exclude": [
"dist/**/*",
"tests/**/*"
]
}

274
yarn.lock Normal file
View file

@ -0,0 +1,274 @@
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!
__metadata:
version: 6
cacheKey: 8
"@cspotcode/source-map-support@npm:^0.8.0":
version: 0.8.1
resolution: "@cspotcode/source-map-support@npm:0.8.1"
dependencies:
"@jridgewell/trace-mapping": 0.3.9
checksum: 5718f267085ed8edb3e7ef210137241775e607ee18b77d95aa5bd7514f47f5019aa2d82d96b3bf342ef7aa890a346fa1044532ff7cc3009e7d24fce3ce6200fa
languageName: node
linkType: hard
"@jridgewell/resolve-uri@npm:^3.0.3":
version: 3.1.0
resolution: "@jridgewell/resolve-uri@npm:3.1.0"
checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267
languageName: node
linkType: hard
"@jridgewell/sourcemap-codec@npm:^1.4.10":
version: 1.4.14
resolution: "@jridgewell/sourcemap-codec@npm:1.4.14"
checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97
languageName: node
linkType: hard
"@jridgewell/trace-mapping@npm:0.3.9":
version: 0.3.9
resolution: "@jridgewell/trace-mapping@npm:0.3.9"
dependencies:
"@jridgewell/resolve-uri": ^3.0.3
"@jridgewell/sourcemap-codec": ^1.4.10
checksum: d89597752fd88d3f3480845691a05a44bd21faac18e2185b6f436c3b0fd0c5a859fbbd9aaa92050c4052caf325ad3e10e2e1d1b64327517471b7d51babc0ddef
languageName: node
linkType: hard
"@rometools/cli-darwin-arm64@npm:11.0.0":
version: 11.0.0
resolution: "@rometools/cli-darwin-arm64@npm:11.0.0"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@rometools/cli-darwin-x64@npm:11.0.0":
version: 11.0.0
resolution: "@rometools/cli-darwin-x64@npm:11.0.0"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@rometools/cli-linux-arm64@npm:11.0.0":
version: 11.0.0
resolution: "@rometools/cli-linux-arm64@npm:11.0.0"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
"@rometools/cli-linux-x64@npm:11.0.0":
version: 11.0.0
resolution: "@rometools/cli-linux-x64@npm:11.0.0"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
"@rometools/cli-win32-arm64@npm:11.0.0":
version: 11.0.0
resolution: "@rometools/cli-win32-arm64@npm:11.0.0"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@rometools/cli-win32-x64@npm:11.0.0":
version: 11.0.0
resolution: "@rometools/cli-win32-x64@npm:11.0.0"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@tsconfig/node10@npm:^1.0.7":
version: 1.0.9
resolution: "@tsconfig/node10@npm:1.0.9"
checksum: a33ae4dc2a621c0678ac8ac4bceb8e512ae75dac65417a2ad9b022d9b5411e863c4c198b6ba9ef659e14b9fb609bbec680841a2e84c1172df7a5ffcf076539df
languageName: node
linkType: hard
"@tsconfig/node12@npm:^1.0.7":
version: 1.0.11
resolution: "@tsconfig/node12@npm:1.0.11"
checksum: 5ce29a41b13e7897a58b8e2df11269c5395999e588b9a467386f99d1d26f6c77d1af2719e407621412520ea30517d718d5192a32403b8dfcc163bf33e40a338a
languageName: node
linkType: hard
"@tsconfig/node14@npm:^1.0.0":
version: 1.0.3
resolution: "@tsconfig/node14@npm:1.0.3"
checksum: 19275fe80c4c8d0ad0abed6a96dbf00642e88b220b090418609c4376e1cef81bf16237bf170ad1b341452feddb8115d8dd2e5acdfdea1b27422071163dc9ba9d
languageName: node
linkType: hard
"@tsconfig/node16@npm:^1.0.2":
version: 1.0.3
resolution: "@tsconfig/node16@npm:1.0.3"
checksum: 3a8b657dd047495b7ad23437d6afd20297ce90380ff0bdee93fc7d39a900dbd8d9e26e53ff6b465e7967ce2adf0b218782590ce9013285121e6a5928fbd6819f
languageName: node
linkType: hard
"@types/node@npm:^18.15.10":
version: 18.15.10
resolution: "@types/node@npm:18.15.10"
checksum: 9aeae0b683eda82892def5315812bdee3f1a28c4898b7e70f8e2514564538b16c4dccbe8339c1266f8fc1d707a48f152689264a854f5ebc2eba5011e793612d9
languageName: node
linkType: hard
"acorn-walk@npm:^8.1.1":
version: 8.2.0
resolution: "acorn-walk@npm:8.2.0"
checksum: 1715e76c01dd7b2d4ca472f9c58968516a4899378a63ad5b6c2d668bba8da21a71976c14ec5f5b75f887b6317c4ae0b897ab141c831d741dc76024d8745f1ad1
languageName: node
linkType: hard
"acorn@npm:^8.4.1":
version: 8.8.2
resolution: "acorn@npm:8.8.2"
bin:
acorn: bin/acorn
checksum: f790b99a1bf63ef160c967e23c46feea7787e531292bb827126334612c234ed489a0dc2c7ba33156416f0ffa8d25bf2b0fdb7f35c2ba60eb3e960572bece4001
languageName: node
linkType: hard
"arg@npm:^4.1.0":
version: 4.1.3
resolution: "arg@npm:4.1.3"
checksum: 544af8dd3f60546d3e4aff084d451b96961d2267d668670199692f8d054f0415d86fc5497d0e641e91546f0aa920e7c29e5250e99fc89f5552a34b5d93b77f43
languageName: node
linkType: hard
"create-require@npm:^1.1.0":
version: 1.1.1
resolution: "create-require@npm:1.1.1"
checksum: a9a1503d4390d8b59ad86f4607de7870b39cad43d929813599a23714831e81c520bddf61bcdd1f8e30f05fd3a2b71ae8538e946eb2786dc65c2bbc520f692eff
languageName: node
linkType: hard
"diff@npm:^4.0.1":
version: 4.0.2
resolution: "diff@npm:4.0.2"
checksum: f2c09b0ce4e6b301c221addd83bf3f454c0bc00caa3dd837cf6c127d6edf7223aa2bbe3b688feea110b7f262adbfc845b757c44c8a9f8c0c5b15d8fa9ce9d20d
languageName: node
linkType: hard
"make-error@npm:^1.1.1":
version: 1.3.6
resolution: "make-error@npm:1.3.6"
checksum: b86e5e0e25f7f777b77fabd8e2cbf15737972869d852a22b7e73c17623928fccb826d8e46b9951501d3f20e51ad74ba8c59ed584f610526a48f8ccf88aaec402
languageName: node
linkType: hard
"rome@npm:^11.0.0":
version: 11.0.0
resolution: "rome@npm:11.0.0"
dependencies:
"@rometools/cli-darwin-arm64": 11.0.0
"@rometools/cli-darwin-x64": 11.0.0
"@rometools/cli-linux-arm64": 11.0.0
"@rometools/cli-linux-x64": 11.0.0
"@rometools/cli-win32-arm64": 11.0.0
"@rometools/cli-win32-x64": 11.0.0
dependenciesMeta:
"@rometools/cli-darwin-arm64":
optional: true
"@rometools/cli-darwin-x64":
optional: true
"@rometools/cli-linux-arm64":
optional: true
"@rometools/cli-linux-x64":
optional: true
"@rometools/cli-win32-arm64":
optional: true
"@rometools/cli-win32-x64":
optional: true
bin:
rome: bin/rome
checksum: 3c92a47c78a66c62f94b6a3a72ead92a20c23326d2d73785ce88c03897b60b8114c285a636acc24bd11039381ded48f8c58c6cc5c4cc46ce7eed092c90d9a054
languageName: node
linkType: hard
"testrunner@workspace:.":
version: 0.0.0-use.local
resolution: "testrunner@workspace:."
dependencies:
"@types/node": ^18.15.10
rome: ^11.0.0
ts-node: ^10.9.1
typescript: ^4.0.0
bin:
womm: ./dist/runner.js
languageName: unknown
linkType: soft
"ts-node@npm:^10.9.1":
version: 10.9.1
resolution: "ts-node@npm:10.9.1"
dependencies:
"@cspotcode/source-map-support": ^0.8.0
"@tsconfig/node10": ^1.0.7
"@tsconfig/node12": ^1.0.7
"@tsconfig/node14": ^1.0.0
"@tsconfig/node16": ^1.0.2
acorn: ^8.4.1
acorn-walk: ^8.1.1
arg: ^4.1.0
create-require: ^1.1.0
diff: ^4.0.1
make-error: ^1.1.1
v8-compile-cache-lib: ^3.0.1
yn: 3.1.1
peerDependencies:
"@swc/core": ">=1.2.50"
"@swc/wasm": ">=1.2.50"
"@types/node": "*"
typescript: ">=2.7"
peerDependenciesMeta:
"@swc/core":
optional: true
"@swc/wasm":
optional: true
bin:
ts-node: dist/bin.js
ts-node-cwd: dist/bin-cwd.js
ts-node-esm: dist/bin-esm.js
ts-node-script: dist/bin-script.js
ts-node-transpile-only: dist/bin-transpile.js
ts-script: dist/bin-script-deprecated.js
checksum: 090adff1302ab20bd3486e6b4799e90f97726ed39e02b39e566f8ab674fd5bd5f727f43615debbfc580d33c6d9d1c6b1b3ce7d8e3cca3e20530a145ffa232c35
languageName: node
linkType: hard
"typescript@npm:^4.0.0":
version: 4.9.5
resolution: "typescript@npm:4.9.5"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db
languageName: node
linkType: hard
"typescript@patch:typescript@^4.0.0#~builtin<compat/typescript>":
version: 4.9.5
resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin<compat/typescript>::version=4.9.5&hash=23ec76"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: ab417a2f398380c90a6cf5a5f74badd17866adf57f1165617d6a551f059c3ba0a3e4da0d147b3ac5681db9ac76a303c5876394b13b3de75fdd5b1eaa06181c9d
languageName: node
linkType: hard
"v8-compile-cache-lib@npm:^3.0.1":
version: 3.0.1
resolution: "v8-compile-cache-lib@npm:3.0.1"
checksum: 78089ad549e21bcdbfca10c08850022b22024cdcc2da9b168bcf5a73a6ed7bf01a9cebb9eac28e03cd23a684d81e0502797e88f3ccd27a32aeab1cfc44c39da0
languageName: node
linkType: hard
"yn@npm:3.1.1":
version: 3.1.1
resolution: "yn@npm:3.1.1"
checksum: 2c487b0e149e746ef48cda9f8bad10fc83693cd69d7f9dcd8be4214e985de33a29c9e24f3c0d6bcf2288427040a8947406ab27f7af67ee9456e6b84854f02dd6
languageName: node
linkType: hard