From ab80f159830c93bc7880613b4b52cd3ca9bca589 Mon Sep 17 00:00:00 2001 From: dblock Date: Thu, 30 May 2024 15:14:07 -0400 Subject: [PATCH] Adding command line tests. Signed-off-by: dblock --- eslint.config.mjs | 1 + tools/src/tester/ResultsDisplayer.ts | 46 ++++++++++++++------------ tools/src/tester/start.ts | 12 +++---- tools/tests/tester/fixtures/empty.yaml | 3 ++ tools/tests/tester/start.test.ts | 30 +++++++++++++++++ 5 files changed, 64 insertions(+), 28 deletions(-) create mode 100644 tools/tests/tester/fixtures/empty.yaml create mode 100644 tools/tests/tester/start.test.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index 9e13e3c1a..554130adf 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -48,6 +48,7 @@ export default [ allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false } ], + '@typescript-eslint/no-extraneous-class': 'off', 'array-callback-return': 'off', 'new-cap': 'off', 'no-return-assign': 'error', diff --git a/tools/src/tester/ResultsDisplayer.ts b/tools/src/tester/ResultsDisplayer.ts index feb3a6eb1..e0fbaec01 100644 --- a/tools/src/tester/ResultsDisplayer.ts +++ b/tools/src/tester/ResultsDisplayer.ts @@ -1,20 +1,22 @@ import { type ChapterEvaluation, type Evaluation, Result, type StoryEvaluation } from './types/eval.types' import { overall_result } from './helpers' -function b (text: string): string { return `\x1b[1m${text}\x1b[0m` } -function i (text: string): string { return `\x1b[3m${text}\x1b[0m` } +export class Ansi { + static b (text: string): string { return `\x1b[1m${text}\x1b[0m` } + static i (text: string): string { return `\x1b[3m${text}\x1b[0m` } -function padding (text: string, length: number, prefix: number = 0): string { - const spaces = length - text.length > 0 ? ' '.repeat(length - text.length) : '' - return `${' '.repeat(prefix)}${text}${spaces}` -} + static padding (text: string, length: number, prefix: number = 0): string { + const spaces = length - text.length > 0 ? ' '.repeat(length - text.length) : '' + return `${' '.repeat(prefix)}${text}${spaces}` + } -function green (text: string): string { return `\x1b[32m${text}\x1b[0m` } -function red (text: string): string { return `\x1b[31m${text}\x1b[0m` } -function yellow (text: string): string { return `\x1b[33m${text}\x1b[0m` } -function cyan (text: string): string { return `\x1b[36m${text}\x1b[0m` } -function gray (text: string): string { return `\x1b[90m${text}\x1b[0m` } -function magenta (text: string): string { return `\x1b[35m${text}\x1b[0m` } + static green (text: string): string { return `\x1b[32m${text}\x1b[0m` } + static red (text: string): string { return `\x1b[31m${text}\x1b[0m` } + static yellow (text: string): string { return `\x1b[33m${text}\x1b[0m` } + static cyan (text: string): string { return `\x1b[36m${text}\x1b[0m` } + static gray (text: string): string { return `\x1b[90m${text}\x1b[0m` } + static magenta (text: string): string { return `\x1b[35m${text}\x1b[0m` } +} export interface DisplayOptions { tab_size?: number @@ -45,7 +47,7 @@ export default class ResultsDisplayer { #display_story (): void { const result = this.evaluation.result const message = this.evaluation.full_path - const title = cyan(b(this.evaluation.display_path)) + const title = Ansi.cyan(Ansi.b(this.evaluation.display_path)) this.#display_evaluation({ result, message }, title) } @@ -58,7 +60,7 @@ export default class ResultsDisplayer { } #display_chapter (chapter: ChapterEvaluation): void { - this.#display_evaluation(chapter.overall, i(chapter.title), this.tab_size * 2) + this.#display_evaluation(chapter.overall, Ansi.i(chapter.title), this.tab_size * 2) if (chapter.overall.result === Result.PASSED || chapter.overall.result === Result.SKIPPED) return this.#display_parameters(chapter.request?.parameters ?? {}) @@ -93,8 +95,8 @@ export default class ResultsDisplayer { } #display_evaluation (evaluation: Evaluation, title: string, prefix: number = 0): void { - const result = padding(this.#result(evaluation.result), 0, prefix) - const message = evaluation.message != null ? `${gray('(' + evaluation.message + ')')}` : '' + const result = Ansi.padding(this.#result(evaluation.result), 0, prefix) + const message = evaluation.message != null ? `${Ansi.gray('(' + evaluation.message + ')')}` : '' console.log(`${result} ${title} ${message}`) if (evaluation.error && this.verbose) { console.log('-'.repeat(100)) @@ -104,13 +106,13 @@ export default class ResultsDisplayer { } #result (r: Result): string { - const text = padding(r, 7) + const text = Ansi.padding(r, 7) switch (r) { - case Result.PASSED: return green(text) - case Result.SKIPPED: return yellow(text) - case Result.FAILED: return magenta(text) - case Result.ERROR: return red(text) - default: return gray(text) + case Result.PASSED: return Ansi.green(text) + case Result.SKIPPED: return Ansi.yellow(text) + case Result.FAILED: return Ansi.magenta(text) + case Result.ERROR: return Ansi.red(text) + default: return Ansi.gray(text) } } } diff --git a/tools/src/tester/start.ts b/tools/src/tester/start.ts index c366a8926..c976575a6 100644 --- a/tools/src/tester/start.ts +++ b/tools/src/tester/start.ts @@ -6,9 +6,9 @@ import _ from 'lodash' const command = new Command() .description('Run test stories against the OpenSearch spec.') - .addOption(new Option('--spec, --spec_path ', 'path to the root folder of the multi-file spec').default('./spec')) - .addOption(new Option('--tests, --tests_path ', 'path to the root folder of the tests').default('./tests')) - .addOption(new Option('--tab_size ', 'tab size for displayed results').default('4')) + .addOption(new Option('--spec, --spec-path ', 'path to the root folder of the multi-file spec').default('./spec')) + .addOption(new Option('--tests, --tests-path ', 'path to the root folder of the tests').default('./tests')) + .addOption(new Option('--tab-size ', 'tab size for displayed results').default('4')) .addOption(new Option('--verbose', 'whether to print the full stack trace of errors')) .allowExcessArguments(false) .parse() @@ -16,8 +16,8 @@ const command = new Command() const opts = command.opts() const display_options = { verbose: opts.verbose ?? false, - tab_size: Number.parseInt(opts.tab_size) + tab_size: Number.parseInt(opts.tabSize) } -const spec = (new OpenApiMerger(opts.spec_path, LogLevel.error)).merge() -const runner = new TestsRunner(spec, opts.tests_path, display_options) +const spec = (new OpenApiMerger(opts.specPath, LogLevel.error)).merge() +const runner = new TestsRunner(spec, opts.testsPath, display_options) void runner.run().then(() => { _.noop() }) diff --git a/tools/tests/tester/fixtures/empty.yaml b/tools/tests/tester/fixtures/empty.yaml new file mode 100644 index 000000000..0b63cb9b6 --- /dev/null +++ b/tools/tests/tester/fixtures/empty.yaml @@ -0,0 +1,3 @@ +$schema: ../json_schemas/test_story.schema.yaml + +chapters: [] \ No newline at end of file diff --git a/tools/tests/tester/start.test.ts b/tools/tests/tester/start.test.ts new file mode 100644 index 000000000..e973e441a --- /dev/null +++ b/tools/tests/tester/start.test.ts @@ -0,0 +1,30 @@ +import { spawnSync } from 'child_process' +import { Ansi } from '../../src/tester/ResultsDisplayer' + +const spec = (args: string[]): any => { + const start = spawnSync('ts-node', ['tools/src/tester/start.ts'].concat(args), { + env: { ...process.env, OPENSEARCH_PASSWORD: 'password' } + }) + return { + stdout: start.stdout?.toString(), + stderr: start.stderr?.toString() + } +} + +test('--help', async () => { + expect(spec(['--help']).stdout).toContain('Usage: start [options]') +}) + +test('--invalid', async () => { + expect(spec(['--invalid']).stderr).toContain("error: unknown option '--invalid'") +}) + +test('--tests', async () => { + expect(spec(['--tests', 'tools/tests/tester/fixtures']).stdout).toContain( + `${Ansi.green('PASSED ')} ${Ansi.cyan(Ansi.b('empty.yaml'))}` + ) +}) + +test.todo('--tab-size') +test.todo('--verbose') +test.todo('--spec')