Skip to content

Commit

Permalink
feat!: Replaced ts-node with @swc-node/register.
Browse files Browse the repository at this point in the history
  • Loading branch information
ShogunPanda committed Feb 7, 2024
1 parent 11dd020 commit 053a10c
Show file tree
Hide file tree
Showing 19 changed files with 632 additions and 566 deletions.
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,9 @@ If you need to configure the script at runtime, use environment variables and op

### TypeScript

cronometro uses [ts-node](https://www.npmjs.com/package/ts-node) to compile TypeScript files on the fly.
cronometro can run on TypeScript files. Type deletion happens on the fly with no additional configuration required.

ts-node and TypeScript are not installed automatically by cronometro (as they are listed in `peerDependencies`) so you need to do it manually.

To pass the `tsconfig.json` project file to use, use the `TS_NODE_PROJECT` environment variable.
Use the `TS_NODE_PROJECT` environment variable to provide a TypeScript configuration.

### API use

Expand Down Expand Up @@ -102,10 +100,10 @@ Each property value is a object with the following properties:
import cronometro from 'cronometro'

const results = cronometro({
test1: function () {
await test1: function () {
// Do something
},
test2: function () {
await test2: function () {
// Do something else
}
})
Expand Down
10 changes: 3 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@
"format": "prettier -w src test",
"lint": "eslint --cache --ext .js,.jsx,.ts,.tsx src test",
"typecheck": "tsc -p . --emitDeclarationOnly",
"test": "c8 -c test/config/c8-local.json tap test/*.test.ts",
"test:ci": "c8 -c test/config/c8-ci.json tap --no-color test/*.test.ts",
"test": "TS_NODE_PROJECT=tsconfig.test.json c8 -c test/config/c8-local.json node --import @swc-node/register/esm-register --test test/*.test.ts",
"test:ci": "TS_NODE_PROJECT=tsconfig.test.json c8 -c test/config/c8-ci.json node --import @swc-node/register/esm-register --test-reporter=tap --test test/*.test.ts",
"ci": "npm run build && npm run test:ci",
"prepublishOnly": "npm run ci",
"postpublish": "git push origin && git push origin -f --tags"
},
"dependencies": {
"@swc-node/register": "^1.8.0",
"acquerello": "^2.0.6",
"hdr-histogram-js": "^3.0.0",
"table": "^6.8.1"
Expand All @@ -55,14 +56,9 @@
"concurrently": "^8.2.2",
"prettier": "^3.2.4",
"proxyquire": "^2.1.3",
"tap": "^18.7.0",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"engines": {
"node": ">= 18.18.0"
},
"tap": {
"extends": "./test/config/tap.yml"
}
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { isMainThread, Worker, workerData } from 'node:worker_threads'
import {
defaultOptions,
type Result,
runnerPath,
type Callback,
type Context,
type Options,
type PrintOptions,
type Result,
type Results,
type Tests
} from './models.js'
Expand Down
16 changes: 12 additions & 4 deletions src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ export interface PrintOptions {
compareMode?: 'base' | 'previous'
}

export type SetupFunctionCallback = (err?: Error | null) => void

// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
export type SetupFunction = (cb: (err?: Error | null) => void) => Promise<any> | void
export type SetupFunction = (cb: SetupFunctionCallback) => Promise<any> | void

export interface Options {
iterations: number
Expand All @@ -33,8 +35,6 @@ export interface Test {
after?: SetupFunction
}

export type Callback = (err: Error | null, results: Results) => any

export type Percentiles = Record<string, number>

export interface Result {
Expand All @@ -49,6 +49,8 @@ export interface Result {
percentiles: Percentiles
}

export type Callback = (err: Error | null, results: Results) => any

export type Tests = Record<string, TestFunction | Test>

export type Results = Record<string, Result>
Expand Down Expand Up @@ -98,4 +100,10 @@ export const defaultOptions = {

export const percentiles = [0.001, 0.01, 0.1, 1, 2.5, 10, 25, 50, 75, 90, 97.5, 99, 99.9, 99.99, 99.999]

export const runnerPath = resolve(import.meta.url.replace('file://', '').replace(/models.(js|ts)/, ''), './runner.js')
export const runnerPath = resolve(
import.meta.url
.replace('file://', '')
.replace('/src/', '/dist/')
.replace(/models.(js|ts)/, ''),
'./runner.js'
)
59 changes: 23 additions & 36 deletions src/runner.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* c8 ignore start */
import { isMainThread, parentPort, workerData } from 'node:worker_threads'
import { type WorkerContext } from './models.js'
import { runWorker } from './worker.js'
Expand All @@ -6,45 +7,31 @@ if (isMainThread) {
throw new Error('Do not run this file as main script.')
}

// Register ts-node for TypeScript inclusion
let chain: Promise<void> = Promise.resolve()

/* c8 ignore start */
if (workerData.path.endsWith('.ts')) {
const instance = Symbol.for('ts-node.register.instance')

if (!(instance in process)) {
chain = import('ts-node').then(({ register }) => {
register({ project: process.env.TS_NODE_PROJECT })
})
}
await import('@swc-node/register/esm-register')
}

// Require the script to set tests
chain
.then(() => {
return import(workerData.path)
})
.then(module => {
if (typeof module === 'function') {
return module()
} else if (typeof module.default === 'function') {
return module.default()
}
})
.then(() => {
// Run the worker
runWorker(
workerData as WorkerContext,
value => {
parentPort!.postMessage({ type: 'cronometro.result', payload: value })
},
(code: number) => process.exit(code)
)
})
.catch(error => {
process.nextTick(() => {
throw error
})
try {
const module = await import(workerData.path)

if (typeof module === 'function') {
await module()
} else if (typeof module.default === 'function') {
await module.default()
}

// Run the worker
runWorker(
workerData as WorkerContext,
value => {
parentPort!.postMessage({ type: 'cronometro.result', payload: value })
},
(code: number) => process.exit(code)
)
} catch (error) {
process.nextTick(() => {
throw error
})
}
/* c8 ignore stop */
2 changes: 2 additions & 0 deletions src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,13 @@ function runTestIteration(context: TestContext): void {
context.handler(null)
}
} catch (error) {
/* c8 ignore start */
// If a error was thrown, only handle if the original function length is 0, which means it's a sync error, otherwise propagate
if (context.test.length === 0) {
context.handler(error as Error)
return
}
/* c8 ignore end */

throw error
}
Expand Down
75 changes: 34 additions & 41 deletions test/asyncImport.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
/* eslint-disable @typescript-eslint/no-floating-promises */

import { deepStrictEqual, ifError, ok } from 'node:assert'
import { test } from 'node:test'
import { isMainThread } from 'node:worker_threads'
import t from 'tap'
import { cronometro, percentiles } from '../src/index.js'

async function main(): Promise<void> {
await new Promise(resolve => setTimeout(resolve, 100))
await new Promise(resolve => setTimeout(resolve, 100))

if (!isMainThread) {
cronometro(
if (!isMainThread) {
cronometro(
{
single() {
Buffer.alloc(10)
},
multiple() {
Buffer.alloc(10)
Buffer.alloc(20)
}
},
() => false
)
} else {
await test('Collecting results', async () => {
const results = await cronometro(
{
single() {
Buffer.alloc(10)
Expand All @@ -18,43 +30,24 @@ async function main(): Promise<void> {
Buffer.alloc(20)
}
},
() => false
{ iterations: 10, print: false }
)
} else {
t.test('Collecting results', async t => {
const results = await cronometro(
{
single() {
Buffer.alloc(10)
},
multiple() {
Buffer.alloc(10)
Buffer.alloc(20)
}
},
{ iterations: 10, print: false }
)

t.strictSame(Object.keys(results), ['single', 'multiple'])
deepStrictEqual(Object.keys(results), ['single', 'multiple'])

for (const entry of Object.values(results)) {
t.ok(entry.success)
t.type(entry.error, 'undefined')
t.equal(entry.size, 10)
t.type(entry.min, 'number')
t.type(entry.max, 'number')
t.type(entry.mean, 'number')
t.type(entry.stddev, 'number')
t.type(entry.standardError, 'number')
for (const entry of Object.values(results)) {
ok(entry.success)
ifError(entry.error)
deepStrictEqual(entry.size, 10)
deepStrictEqual(typeof entry.min, 'number')
deepStrictEqual(typeof entry.max, 'number')
deepStrictEqual(typeof entry.mean, 'number')
deepStrictEqual(typeof entry.stddev, 'number')
deepStrictEqual(typeof entry.standardError, 'number')

for (const percentile of percentiles) {
t.type(entry.percentiles[percentile.toString()], 'number')
}
for (const percentile of percentiles) {
ok(typeof entry.percentiles[percentile.toString()], 'number')
}
})
}
}
})
}

main()

export default main
31 changes: 17 additions & 14 deletions test/callbacks.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
/* eslint-disable @typescript-eslint/no-floating-promises */

import { isMainThread, Worker } from 'node:worker_threads'
import t from 'tap'
import { deepStrictEqual, match, ok } from 'node:assert'
import { test } from 'node:test'
import { Worker, isMainThread, parentPort } from 'node:worker_threads'
import { cronometro, type Result, type TestFunction } from '../src/index.js'

if (!isMainThread) {
parentPort!.postMessage('another')

cronometro(
{
single() {
Expand All @@ -18,7 +21,7 @@ if (!isMainThread) {
() => false
)
} else {
t.test('Callbacks', async t => {
test('Callbacks', async () => {
await cronometro(
{
single() {
Expand All @@ -35,24 +38,24 @@ if (!isMainThread) {
iterations: 10,
print: false,
onTestStart(name: string, data: any, worker: Worker) {
t.match(name, /single|multiple|missing/)
t.ok(data.index < 3)
t.ok(worker instanceof Worker)
match(name, /single|multiple|missing/)
ok(data.index < 3)
ok(worker instanceof Worker)
},
onTestEnd(name: string, result: Result, worker: Worker) {
if (result.success) {
t.same(name, 'single')
t.ok(result.size > 0)
deepStrictEqual(name, 'single')
ok(result.size > 0)
} else {
t.same(name, 'multiple')
t.same(result.error!.message, 'INVALID')
deepStrictEqual(name, 'multiple')
deepStrictEqual(result.error!.message, 'INVALID')
}
t.ok(worker instanceof Worker)
ok(worker instanceof Worker)
},
onTestError(name: string, error: Error, worker: Worker) {
t.same(name, 'missing')
t.same(error.message, "Cannot read properties of undefined (reading 'test')")
t.ok(worker instanceof Worker)
deepStrictEqual(name, 'missing')
deepStrictEqual(error.message, "Cannot read properties of undefined (reading 'test')")
ok(worker instanceof Worker)
}
}
)
Expand Down
1 change: 1 addition & 0 deletions test/config/c8-ci.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"check-coverage": true,
"reporter": ["text", "json"],
"exclude": ["dist", "test"],
"branches": 90,
"functions": 90,
"lines": 90,
Expand Down
1 change: 1 addition & 0 deletions test/config/c8-local.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"exclude": ["dist", "test"],
"reporter": ["text", "html"]
}
7 changes: 0 additions & 7 deletions test/config/tap.yml

This file was deleted.

0 comments on commit 053a10c

Please sign in to comment.