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:
parent
2bc6d94507
commit
4f14f20ec2
5 changed files with 107 additions and 38 deletions
2
.github/workflows/nodejs.yml
vendored
2
.github/workflows/nodejs.yml
vendored
|
@ -70,7 +70,7 @@ jobs:
|
||||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}-node-${{ matrix.node-version }}
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}-node-${{ matrix.node-version }}
|
||||||
- run: |
|
- run: |
|
||||||
yarn
|
yarn
|
||||||
yarn test
|
yarn test --workers=2
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
69
src/argumentParser.ts
Normal file
69
src/argumentParser.ts
Normal 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
|
25
src/help.ts
25
src/help.ts
|
@ -1,12 +1,31 @@
|
||||||
import { boldText } from './utils'
|
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 `
|
export default `
|
||||||
${boldText('Works on my machine v0.0.0')}
|
${boldText('Works on my machine v0.0.0')}
|
||||||
|
|
||||||
A no-dependency test runner
|
A no-dependency test runner
|
||||||
---
|
---
|
||||||
womm [--help] [-h] ...<test-files-or-directories>
|
womm <flags> ...<test-files-or-directories>
|
||||||
|
|
||||||
Flags:
|
${boldText('Flags:')}
|
||||||
--help, -h: Prints this message
|
${getFlagHelp()}
|
||||||
`
|
`
|
||||||
|
|
|
@ -3,12 +3,15 @@
|
||||||
import { getContext, greenText, redText, exec, splitIntoBatches } from './utils'
|
import { getContext, greenText, redText, exec, splitIntoBatches } from './utils'
|
||||||
import helpText from './help'
|
import helpText from './help'
|
||||||
import { type Args, type IContext, type TestServer } from './types'
|
import { type Args, type IContext, type TestServer } from './types'
|
||||||
|
import parseArgs from './argumentParser'
|
||||||
import { type Buffer } from 'buffer'
|
import { type Buffer } from 'buffer'
|
||||||
|
|
||||||
import { promises as fs } from 'fs'
|
import { promises as fs } from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import net from 'net'
|
import net from 'net'
|
||||||
|
|
||||||
|
class UnknownArgumentError extends Error {}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Collects test files recursively starting from the provided root
|
* Collects test files recursively starting from the provided root
|
||||||
* path.
|
* path.
|
||||||
|
@ -90,39 +93,6 @@ function setUpSocket(socketPath: string): TestServer {
|
||||||
})
|
})
|
||||||
|
|
||||||
return server
|
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.
|
* Logic executed when running the test runner CLI.
|
||||||
*/
|
*/
|
||||||
|
@ -142,7 +112,7 @@ function parseArgs(args: Array<string>): Args {
|
||||||
const collectedTests = await collectTests(args.targets)
|
const collectedTests = await collectTests(args.targets)
|
||||||
await collectCases(context, collectedTests)
|
await collectCases(context, collectedTests)
|
||||||
|
|
||||||
await assignTestsToWorkers(context, collectedTests)
|
await assignTestsToWorkers(context, collectedTests, args.workers)
|
||||||
|
|
||||||
if (server.failure) throw new Error()
|
if (server.failure) throw new Error()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
11
src/types.ts
11
src/types.ts
|
@ -22,6 +22,7 @@ export interface Args {
|
||||||
targets: Array<string>
|
targets: Array<string>
|
||||||
runtimePath: string
|
runtimePath: string
|
||||||
help: boolean
|
help: boolean
|
||||||
|
workers: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MatcherReport {
|
export interface MatcherReport {
|
||||||
|
@ -45,3 +46,13 @@ export interface RawMatchersMap {
|
||||||
comparisonMatchers: Array<RawComparisonMatcher>
|
comparisonMatchers: Array<RawComparisonMatcher>
|
||||||
noArgMatchers: Array<RawNoArgMatcher>
|
noArgMatchers: Array<RawNoArgMatcher>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FlagConfiguration {
|
||||||
|
requiresValue: boolean
|
||||||
|
default: string | boolean | number
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FlagConfigurationMap {
|
||||||
|
[key: string]: FlagConfiguration
|
||||||
|
}
|
||||||
|
|
Reference in a new issue