feat: set worker count from cli (#14)

* feat: basic worker setting support

* ci: enable in CI

* refactor: split off argument parser

* feat: generate helptext from flag configuration
This commit is contained in:
Marc 2023-04-08 01:27:57 -04:00
parent 2bc6d94507
commit 4f14f20ec2
Signed by: marc
GPG key ID: 048E042F22B5DC79
5 changed files with 107 additions and 38 deletions

View file

@ -70,7 +70,7 @@ jobs:
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}-node-${{ matrix.node-version }}
- run: |
yarn
yarn test
yarn test --workers=2
build:
runs-on: ubuntu-latest

69
src/argumentParser.ts Normal file
View file

@ -0,0 +1,69 @@
import { type Args, type FlagConfigurationMap } from './types'
export const FLAG_CONFIGURATION: Readonly<FlagConfigurationMap> = {
workers: {
requiresValue: true,
default: 1,
description: 'Defines up to how many parallel processes should consume tests.',
},
help: {
requiresValue: false,
default: false,
description: 'Displays the help text.',
},
}
class MalformedArgumentError extends Error {}
function parseFlags(flags: Array<string>): Map<string, string | number | boolean> {
const flagMap = new Map<string, string | number | boolean>()
for (const flag of flags) {
const [flagName, flagValue] = flag.split('=') as Array<string>
const flagNameWithoutPrefix = flagName.replace(/^--/, '')
const flagConfiguration = FLAG_CONFIGURATION[flagNameWithoutPrefix]
if (!flagConfiguration) throw new MalformedArgumentError(`"${flagName}" is not a valid flag.`)
if (flagConfiguration.requiresValue && !flagValue)
throw new MalformedArgumentError(`"${flagName}" requires a value.`)
flagMap.set(flagNameWithoutPrefix, flagValue ?? true)
}
return flagMap
}
function parseArgs(args: Array<string>): Args {
const [, runtimePath, ...userArgs] = args
const {
argsWithoutFlags,
flags,
}: {
argsWithoutFlags: Array<string>
flags: Array<string>
} = (userArgs as Array<string>).reduce(
(acc, arg: string) => {
if (arg.startsWith('--')) acc.flags.push(arg)
else acc.argsWithoutFlags.push(arg)
return acc
},
{ argsWithoutFlags: [], flags: [] } as {
argsWithoutFlags: Array<string>
flags: Array<string>
},
)
const parsedFlags = parseFlags(flags)
return {
runtimePath,
targets: argsWithoutFlags ?? [],
help: Boolean(parsedFlags.get('help') ?? FLAG_CONFIGURATION['help'].default),
workers: Number(parsedFlags.get('workers') ?? FLAG_CONFIGURATION['workers'].default),
}
}
export default parseArgs

View file

@ -1,12 +1,31 @@
import { boldText } from './utils'
import { FLAG_CONFIGURATION } from './argumentParser'
function getFlagHelp(): string {
const lines: Array<string> = []
for (const flag of Object.keys(FLAG_CONFIGURATION)) {
const requiresValue = FLAG_CONFIGURATION[flag].requiresValue
lines.push(
[
`--${flag}${requiresValue ? '=<x>' : ''}`,
`${FLAG_CONFIGURATION[flag].description} (default=${FLAG_CONFIGURATION[flag].default})`,
]
.map((segment) => segment.padEnd(15))
.join(''),
)
}
return lines.join('\n')
}
export default `
${boldText('Works on my machine v0.0.0')}
A no-dependency test runner
---
womm [--help] [-h] ...<test-files-or-directories>
womm <flags> ...<test-files-or-directories>
Flags:
--help, -h: Prints this message
${boldText('Flags:')}
${getFlagHelp()}
`

View file

@ -3,12 +3,15 @@
import { getContext, greenText, redText, exec, splitIntoBatches } from './utils'
import helpText from './help'
import { type Args, type IContext, type TestServer } from './types'
import parseArgs from './argumentParser'
import { type Buffer } from 'buffer'
import { promises as fs } from 'fs'
import path from 'path'
import net from 'net'
class UnknownArgumentError extends Error {}
/*
* Collects test files recursively starting from the provided root
* path.
@ -90,39 +93,6 @@ function setUpSocket(socketPath: string): TestServer {
})
return server
}
function parseArgs(args: Array<string>): Args {
const [, runtimePath, ...userArgs] = args
const {
argsWithoutFlags,
shortFlags,
longFlags,
}: {
argsWithoutFlags: Array<string>
longFlags: Array<string>
shortFlags: Array<string>
} = (userArgs as Array<string>).reduce(
(acc, arg: string) => {
if (arg.startsWith('--')) acc.longFlags.push(arg)
else if (arg.startsWith('-')) acc.shortFlags.push(arg)
else acc.argsWithoutFlags.push(arg)
return acc
},
{ argsWithoutFlags: [], longFlags: [], shortFlags: [] } as {
argsWithoutFlags: Array<string>
longFlags: Array<string>
shortFlags: Array<string>
},
)
return {
runtimePath,
targets: argsWithoutFlags,
help: longFlags.includes('--help') || shortFlags.includes('-h'),
}
} /*
* Logic executed when running the test runner CLI.
*/
@ -142,7 +112,7 @@ function parseArgs(args: Array<string>): Args {
const collectedTests = await collectTests(args.targets)
await collectCases(context, collectedTests)
await assignTestsToWorkers(context, collectedTests)
await assignTestsToWorkers(context, collectedTests, args.workers)
if (server.failure) throw new Error()
} catch (e) {

View file

@ -22,6 +22,7 @@ export interface Args {
targets: Array<string>
runtimePath: string
help: boolean
workers: number
}
export interface MatcherReport {
@ -45,3 +46,13 @@ export interface RawMatchersMap {
comparisonMatchers: Array<RawComparisonMatcher>
noArgMatchers: Array<RawNoArgMatcher>
}
interface FlagConfiguration {
requiresValue: boolean
default: string | boolean | number
description: string
}
export interface FlagConfigurationMap {
[key: string]: FlagConfiguration
}