modestbench 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/LICENSE.md +55 -0
- package/README.md +699 -0
- package/dist/bootstrap.cjs +37 -0
- package/dist/bootstrap.cjs.map +1 -0
- package/dist/bootstrap.d.cts +17 -0
- package/dist/bootstrap.d.cts.map +1 -0
- package/dist/bootstrap.d.ts +17 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +33 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/cli/commands/history.cjs +459 -0
- package/dist/cli/commands/history.cjs.map +1 -0
- package/dist/cli/commands/history.d.cts +34 -0
- package/dist/cli/commands/history.d.cts.map +1 -0
- package/dist/cli/commands/history.d.ts +34 -0
- package/dist/cli/commands/history.d.ts.map +1 -0
- package/dist/cli/commands/history.js +422 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/init.cjs +566 -0
- package/dist/cli/commands/init.cjs.map +1 -0
- package/dist/cli/commands/init.d.cts +26 -0
- package/dist/cli/commands/init.d.cts.map +1 -0
- package/dist/cli/commands/init.d.ts +26 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +562 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/run.cjs +285 -0
- package/dist/cli/commands/run.cjs.map +1 -0
- package/dist/cli/commands/run.d.cts +37 -0
- package/dist/cli/commands/run.d.cts.map +1 -0
- package/dist/cli/commands/run.d.ts +37 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +248 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/index.cjs +523 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +58 -0
- package/dist/cli/index.d.cts.map +1 -0
- package/dist/cli/index.d.ts +58 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +515 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/manager.cjs +370 -0
- package/dist/config/manager.cjs.map +1 -0
- package/dist/config/manager.d.cts +46 -0
- package/dist/config/manager.d.cts.map +1 -0
- package/dist/config/manager.d.ts +46 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +333 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/schema.cjs +182 -0
- package/dist/config/schema.cjs.map +1 -0
- package/dist/config/schema.d.cts +51 -0
- package/dist/config/schema.d.cts.map +1 -0
- package/dist/config/schema.d.ts +51 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +145 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/constants.cjs +22 -0
- package/dist/constants.cjs.map +1 -0
- package/dist/constants.d.cts +10 -0
- package/dist/constants.d.cts.map +1 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +19 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/benchmark-schema.cjs +135 -0
- package/dist/core/benchmark-schema.cjs.map +1 -0
- package/dist/core/benchmark-schema.d.cts +139 -0
- package/dist/core/benchmark-schema.d.cts.map +1 -0
- package/dist/core/benchmark-schema.d.ts +139 -0
- package/dist/core/benchmark-schema.d.ts.map +1 -0
- package/dist/core/benchmark-schema.js +132 -0
- package/dist/core/benchmark-schema.js.map +1 -0
- package/dist/core/engine.cjs +669 -0
- package/dist/core/engine.cjs.map +1 -0
- package/dist/core/engine.d.cts +128 -0
- package/dist/core/engine.d.cts.map +1 -0
- package/dist/core/engine.d.ts +128 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +632 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/engines/accurate-engine.cjs +292 -0
- package/dist/core/engines/accurate-engine.cjs.map +1 -0
- package/dist/core/engines/accurate-engine.d.cts +63 -0
- package/dist/core/engines/accurate-engine.d.cts.map +1 -0
- package/dist/core/engines/accurate-engine.d.ts +63 -0
- package/dist/core/engines/accurate-engine.d.ts.map +1 -0
- package/dist/core/engines/accurate-engine.js +288 -0
- package/dist/core/engines/accurate-engine.js.map +1 -0
- package/dist/core/engines/index.cjs +21 -0
- package/dist/core/engines/index.cjs.map +1 -0
- package/dist/core/engines/index.d.cts +16 -0
- package/dist/core/engines/index.d.cts.map +1 -0
- package/dist/core/engines/index.d.ts +16 -0
- package/dist/core/engines/index.d.ts.map +1 -0
- package/dist/core/engines/index.js +16 -0
- package/dist/core/engines/index.js.map +1 -0
- package/dist/core/engines/tinybench-engine.cjs +286 -0
- package/dist/core/engines/tinybench-engine.cjs.map +1 -0
- package/dist/core/engines/tinybench-engine.d.cts +18 -0
- package/dist/core/engines/tinybench-engine.d.cts.map +1 -0
- package/dist/core/engines/tinybench-engine.d.ts +18 -0
- package/dist/core/engines/tinybench-engine.d.ts.map +1 -0
- package/dist/core/engines/tinybench-engine.js +282 -0
- package/dist/core/engines/tinybench-engine.js.map +1 -0
- package/dist/core/error-manager.cjs +303 -0
- package/dist/core/error-manager.cjs.map +1 -0
- package/dist/core/error-manager.d.cts +77 -0
- package/dist/core/error-manager.d.cts.map +1 -0
- package/dist/core/error-manager.d.ts +77 -0
- package/dist/core/error-manager.d.ts.map +1 -0
- package/dist/core/error-manager.js +299 -0
- package/dist/core/error-manager.js.map +1 -0
- package/dist/core/loader.cjs +287 -0
- package/dist/core/loader.cjs.map +1 -0
- package/dist/core/loader.d.cts +55 -0
- package/dist/core/loader.d.cts.map +1 -0
- package/dist/core/loader.d.ts +55 -0
- package/dist/core/loader.d.ts.map +1 -0
- package/dist/core/loader.js +250 -0
- package/dist/core/loader.js.map +1 -0
- package/dist/core/stats-utils.cjs +99 -0
- package/dist/core/stats-utils.cjs.map +1 -0
- package/dist/core/stats-utils.d.cts +50 -0
- package/dist/core/stats-utils.d.cts.map +1 -0
- package/dist/core/stats-utils.d.ts +50 -0
- package/dist/core/stats-utils.d.ts.map +1 -0
- package/dist/core/stats-utils.js +94 -0
- package/dist/core/stats-utils.js.map +1 -0
- package/dist/index.cjs +64 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/progress/manager.cjs +325 -0
- package/dist/progress/manager.cjs.map +1 -0
- package/dist/progress/manager.d.cts +125 -0
- package/dist/progress/manager.d.cts.map +1 -0
- package/dist/progress/manager.d.ts +125 -0
- package/dist/progress/manager.d.ts.map +1 -0
- package/dist/progress/manager.js +321 -0
- package/dist/progress/manager.js.map +1 -0
- package/dist/reporters/csv.cjs +250 -0
- package/dist/reporters/csv.cjs.map +1 -0
- package/dist/reporters/csv.d.cts +92 -0
- package/dist/reporters/csv.d.cts.map +1 -0
- package/dist/reporters/csv.d.ts +92 -0
- package/dist/reporters/csv.d.ts.map +1 -0
- package/dist/reporters/csv.js +246 -0
- package/dist/reporters/csv.js.map +1 -0
- package/dist/reporters/human.cjs +516 -0
- package/dist/reporters/human.cjs.map +1 -0
- package/dist/reporters/human.d.cts +86 -0
- package/dist/reporters/human.d.cts.map +1 -0
- package/dist/reporters/human.d.ts +86 -0
- package/dist/reporters/human.d.ts.map +1 -0
- package/dist/reporters/human.js +509 -0
- package/dist/reporters/human.js.map +1 -0
- package/dist/reporters/index.cjs +17 -0
- package/dist/reporters/index.cjs.map +1 -0
- package/dist/reporters/index.d.cts +10 -0
- package/dist/reporters/index.d.cts.map +1 -0
- package/dist/reporters/index.d.ts +10 -0
- package/dist/reporters/index.d.ts.map +1 -0
- package/dist/reporters/index.js +10 -0
- package/dist/reporters/index.js.map +1 -0
- package/dist/reporters/json.cjs +215 -0
- package/dist/reporters/json.cjs.map +1 -0
- package/dist/reporters/json.d.cts +79 -0
- package/dist/reporters/json.d.cts.map +1 -0
- package/dist/reporters/json.d.ts +79 -0
- package/dist/reporters/json.d.ts.map +1 -0
- package/dist/reporters/json.js +211 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/reporters/registry.cjs +255 -0
- package/dist/reporters/registry.cjs.map +1 -0
- package/dist/reporters/registry.d.cts +155 -0
- package/dist/reporters/registry.d.cts.map +1 -0
- package/dist/reporters/registry.d.ts +155 -0
- package/dist/reporters/registry.d.ts.map +1 -0
- package/dist/reporters/registry.js +249 -0
- package/dist/reporters/registry.js.map +1 -0
- package/dist/reporters/simple.cjs +328 -0
- package/dist/reporters/simple.cjs.map +1 -0
- package/dist/reporters/simple.d.cts +51 -0
- package/dist/reporters/simple.d.cts.map +1 -0
- package/dist/reporters/simple.d.ts +51 -0
- package/dist/reporters/simple.d.ts.map +1 -0
- package/dist/reporters/simple.js +321 -0
- package/dist/reporters/simple.js.map +1 -0
- package/dist/schema/modestbench-config.schema.json +162 -0
- package/dist/storage/history.cjs +456 -0
- package/dist/storage/history.cjs.map +1 -0
- package/dist/storage/history.d.cts +99 -0
- package/dist/storage/history.d.cts.map +1 -0
- package/dist/storage/history.d.ts +99 -0
- package/dist/storage/history.d.ts.map +1 -0
- package/dist/storage/history.js +452 -0
- package/dist/storage/history.js.map +1 -0
- package/dist/types/cli.cjs +21 -0
- package/dist/types/cli.cjs.map +1 -0
- package/dist/types/cli.d.cts +296 -0
- package/dist/types/cli.d.cts.map +1 -0
- package/dist/types/cli.d.ts +296 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +18 -0
- package/dist/types/cli.js.map +1 -0
- package/dist/types/core.cjs +14 -0
- package/dist/types/core.cjs.map +1 -0
- package/dist/types/core.d.cts +380 -0
- package/dist/types/core.d.cts.map +1 -0
- package/dist/types/core.d.ts +380 -0
- package/dist/types/core.d.ts.map +1 -0
- package/dist/types/core.js +13 -0
- package/dist/types/core.js.map +1 -0
- package/dist/types/index.cjs +27 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +11 -0
- package/dist/types/index.d.cts.map +1 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/interfaces.cjs +10 -0
- package/dist/types/interfaces.cjs.map +1 -0
- package/dist/types/interfaces.d.cts +381 -0
- package/dist/types/interfaces.d.cts.map +1 -0
- package/dist/types/interfaces.d.ts +381 -0
- package/dist/types/interfaces.d.ts.map +1 -0
- package/dist/types/interfaces.js +9 -0
- package/dist/types/interfaces.js.map +1 -0
- package/dist/types/utility.cjs +92 -0
- package/dist/types/utility.cjs.map +1 -0
- package/dist/types/utility.d.cts +330 -0
- package/dist/types/utility.d.cts.map +1 -0
- package/dist/types/utility.d.ts +330 -0
- package/dist/types/utility.d.ts.map +1 -0
- package/dist/types/utility.js +78 -0
- package/dist/types/utility.js.map +1 -0
- package/package.json +211 -0
- package/src/bootstrap.ts +35 -0
- package/src/cli/commands/history.ts +569 -0
- package/src/cli/commands/init.ts +658 -0
- package/src/cli/commands/run.ts +346 -0
- package/src/cli/index.ts +642 -0
- package/src/config/manager.ts +387 -0
- package/src/config/schema.ts +188 -0
- package/src/constants.ts +21 -0
- package/src/core/benchmark-schema.ts +185 -0
- package/src/core/engine.ts +888 -0
- package/src/core/engines/accurate-engine.ts +408 -0
- package/src/core/engines/index.ts +16 -0
- package/src/core/engines/tinybench-engine.ts +335 -0
- package/src/core/error-manager.ts +372 -0
- package/src/core/loader.ts +324 -0
- package/src/core/stats-utils.ts +135 -0
- package/src/index.ts +46 -0
- package/src/progress/manager.ts +415 -0
- package/src/reporters/csv.ts +368 -0
- package/src/reporters/human.ts +707 -0
- package/src/reporters/index.ts +10 -0
- package/src/reporters/json.ts +302 -0
- package/src/reporters/registry.ts +349 -0
- package/src/reporters/simple.ts +459 -0
- package/src/storage/history.ts +600 -0
- package/src/types/cli.ts +312 -0
- package/src/types/core.ts +414 -0
- package/src/types/index.ts +18 -0
- package/src/types/interfaces.ts +451 -0
- package/src/types/utility.ts +446 -0
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModestBench Human-Readable Console Reporter
|
|
3
|
+
*
|
|
4
|
+
* Provides colorized, progressive output for terminal environments. Displays
|
|
5
|
+
* real-time progress, results, and formatted statistics.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
BenchmarkRun,
|
|
12
|
+
FileResult,
|
|
13
|
+
ProgressState,
|
|
14
|
+
SuiteResult,
|
|
15
|
+
TaskResult,
|
|
16
|
+
} from '../types/index.js';
|
|
17
|
+
|
|
18
|
+
import { BaseReporter } from './registry.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* ANSI color codes for terminal output
|
|
22
|
+
*/
|
|
23
|
+
const colors = {
|
|
24
|
+
bold: '\x1b[1m',
|
|
25
|
+
brightBlue: '\x1b[94m',
|
|
26
|
+
brightCyan: '\x1b[96m',
|
|
27
|
+
brightMagenta: '\x1b[95m',
|
|
28
|
+
brightRed: '\x1b[91m',
|
|
29
|
+
brightWhite: '\x1b[97m',
|
|
30
|
+
cyan: '\x1b[36m',
|
|
31
|
+
dim: '\x1b[2m',
|
|
32
|
+
gray: '\x1b[90m',
|
|
33
|
+
green: '\x1b[32m',
|
|
34
|
+
magenta: '\x1b[35m',
|
|
35
|
+
red: '\x1b[31m',
|
|
36
|
+
reset: '\x1b[0m',
|
|
37
|
+
underline: '\x1b[4m',
|
|
38
|
+
white: '\x1b[37m',
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* CP437-inspired ANSI art characters
|
|
43
|
+
*/
|
|
44
|
+
const ansiChars = {
|
|
45
|
+
approx: '≈',
|
|
46
|
+
// Block elements for gradients
|
|
47
|
+
block: {
|
|
48
|
+
dark: '▓',
|
|
49
|
+
full: '█',
|
|
50
|
+
light: '░',
|
|
51
|
+
medium: '▒',
|
|
52
|
+
},
|
|
53
|
+
bullet: '•',
|
|
54
|
+
// Symbols
|
|
55
|
+
checkmark: '√',
|
|
56
|
+
cross: '×',
|
|
57
|
+
plusMinus: '±',
|
|
58
|
+
smallSquare: '▪',
|
|
59
|
+
} as const;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Human-readable console reporter with colorized output
|
|
63
|
+
*/
|
|
64
|
+
export class HumanReporter extends BaseReporter {
|
|
65
|
+
private currentFile = '';
|
|
66
|
+
|
|
67
|
+
private currentSuite = '';
|
|
68
|
+
|
|
69
|
+
private failures: Array<{
|
|
70
|
+
error: string;
|
|
71
|
+
file: string;
|
|
72
|
+
suite: string;
|
|
73
|
+
task: string;
|
|
74
|
+
}> = [];
|
|
75
|
+
|
|
76
|
+
private lastProgressLine = '';
|
|
77
|
+
|
|
78
|
+
private progressWindowActive = false; // Track if progress window is rendered
|
|
79
|
+
|
|
80
|
+
private readonly quiet: boolean;
|
|
81
|
+
|
|
82
|
+
private readonly showProgress: boolean;
|
|
83
|
+
|
|
84
|
+
private startTime = 0;
|
|
85
|
+
|
|
86
|
+
private suiteResults: TaskResult[] = [];
|
|
87
|
+
|
|
88
|
+
private readonly useColor: boolean;
|
|
89
|
+
|
|
90
|
+
private readonly verbose: boolean;
|
|
91
|
+
|
|
92
|
+
constructor(
|
|
93
|
+
options: {
|
|
94
|
+
color?: boolean;
|
|
95
|
+
progress?: boolean;
|
|
96
|
+
quiet?: boolean;
|
|
97
|
+
verbose?: boolean;
|
|
98
|
+
} = {},
|
|
99
|
+
) {
|
|
100
|
+
super('human', options);
|
|
101
|
+
|
|
102
|
+
// Auto-detect color support if not explicitly set
|
|
103
|
+
this.useColor =
|
|
104
|
+
options.color ??
|
|
105
|
+
(process.stdout.isTTY &&
|
|
106
|
+
process.env.FORCE_COLOR !== '0' &&
|
|
107
|
+
process.env.NO_COLOR == null);
|
|
108
|
+
|
|
109
|
+
this.verbose = options.verbose ?? false;
|
|
110
|
+
this.quiet = options.quiet ?? false;
|
|
111
|
+
this.showProgress = options.progress ?? true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
onEnd(run: BenchmarkRun): void {
|
|
115
|
+
if (this.quiet) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.clearProgress();
|
|
120
|
+
|
|
121
|
+
const duration = Date.now() - this.startTime;
|
|
122
|
+
const totalFiles = run.files.length;
|
|
123
|
+
|
|
124
|
+
// Calculate totals across all files
|
|
125
|
+
let totalSuites = 0;
|
|
126
|
+
let totalPassed = 0;
|
|
127
|
+
let totalFailed = 0;
|
|
128
|
+
|
|
129
|
+
for (const file of run.files) {
|
|
130
|
+
totalSuites += file.suites.length;
|
|
131
|
+
for (const suite of file.suites) {
|
|
132
|
+
totalPassed += suite.tasks.filter((t: TaskResult) => !t.error).length;
|
|
133
|
+
totalFailed += suite.tasks.filter((t: TaskResult) => t.error).length;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Results header
|
|
138
|
+
const resultsHeader = `${this.colorize('magenta', ansiChars.block.full.repeat(2))} ${this.colorize('brightWhite', this.colorize('bold', 'Results'))}`;
|
|
139
|
+
this.printLine(resultsHeader);
|
|
140
|
+
this.printLine();
|
|
141
|
+
|
|
142
|
+
this.printLine(
|
|
143
|
+
`${this.colorize('brightBlue', ' Files:')} ${this.colorize('brightWhite', String(totalFiles))}`,
|
|
144
|
+
);
|
|
145
|
+
this.printLine(
|
|
146
|
+
`${this.colorize('brightBlue', ' Suites:')} ${this.colorize('brightWhite', String(totalSuites))}`,
|
|
147
|
+
);
|
|
148
|
+
this.printLine(
|
|
149
|
+
`${this.colorize('brightBlue', ' Tasks:')} ${this.colorize('brightWhite', String(totalPassed + totalFailed))}`,
|
|
150
|
+
);
|
|
151
|
+
if (totalFailed > 0) {
|
|
152
|
+
this.printLine(
|
|
153
|
+
`${this.colorize('brightRed', ansiChars.cross + ' Failed:')} ${this.colorize('brightWhite', String(totalFailed))}`,
|
|
154
|
+
);
|
|
155
|
+
this.printLine(
|
|
156
|
+
`${this.colorize('brightCyan', ansiChars.checkmark + ' Passed:')} ${this.colorize('brightWhite', String(totalPassed))}`,
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
this.printLine(
|
|
160
|
+
`${this.colorize('brightCyan', ansiChars.checkmark + ' All tests passed:')} ${this.colorize('brightWhite', String(totalPassed))}`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
this.printLine(
|
|
164
|
+
`${this.colorize('cyan', ansiChars.approx + ' Duration:')} ${this.colorize('brightWhite', this.formatDuration(duration * 1000000))}`,
|
|
165
|
+
);
|
|
166
|
+
this.printLine();
|
|
167
|
+
|
|
168
|
+
if (totalFailed > 0) {
|
|
169
|
+
// Display failed tasks with details
|
|
170
|
+
if (this.failures.length > 0) {
|
|
171
|
+
this.printLine();
|
|
172
|
+
this.printLine(
|
|
173
|
+
this.colorize('brightRed', this.colorize('bold', 'Failed Tasks:')),
|
|
174
|
+
);
|
|
175
|
+
this.printLine();
|
|
176
|
+
|
|
177
|
+
for (const failure of this.failures) {
|
|
178
|
+
const displayPath = this.formatPath(failure.file);
|
|
179
|
+
this.printLine(
|
|
180
|
+
` ${this.colorize('dim', displayPath)} ${this.colorize('dim', '›')} ${this.colorize('white', failure.suite)} ${this.colorize('dim', '›')} ${this.colorize('brightWhite', failure.task)}`,
|
|
181
|
+
);
|
|
182
|
+
this.printLine(` ${this.colorize('brightRed', failure.error)}`);
|
|
183
|
+
this.printLine();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
const successMessage = `${this.colorize('brightMagenta', 'Rad. ☮')}`;
|
|
188
|
+
this.printLine(successMessage);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
onError(error: Error): void {
|
|
193
|
+
if (this.quiet) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this.clearProgress();
|
|
198
|
+
console.error(
|
|
199
|
+
this.colorize('red', ansiChars.cross.repeat(3) + ' Error:'),
|
|
200
|
+
error.message,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
if (this.verbose && error.stack) {
|
|
204
|
+
console.error(this.colorize('dim', error.stack));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
onFileEnd(result: FileResult): void {
|
|
209
|
+
if (this.quiet) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const totalTasks = result.suites.reduce(
|
|
214
|
+
(sum, suite) => sum + suite.tasks.length,
|
|
215
|
+
0,
|
|
216
|
+
);
|
|
217
|
+
const totalPassed = result.suites.reduce(
|
|
218
|
+
(sum, suite) => sum + suite.tasks.filter((t) => !t.error).length,
|
|
219
|
+
0,
|
|
220
|
+
);
|
|
221
|
+
const totalFailed = totalTasks - totalPassed;
|
|
222
|
+
|
|
223
|
+
if (totalFailed > 0) {
|
|
224
|
+
this.printLine(
|
|
225
|
+
this.colorize(
|
|
226
|
+
'red',
|
|
227
|
+
` ${ansiChars.cross} ${totalFailed} failed, ${totalPassed} passed`,
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
} else {
|
|
231
|
+
this.printLine(
|
|
232
|
+
` ${this.colorize('magenta', ansiChars.checkmark)} ${totalPassed > 1 ? this.colorize('brightMagenta', 'All ') : ''}${this.colorize('bold', this.colorize('brightMagenta', `${totalPassed}`))} ${this.colorize('brightMagenta', `${this.pluralize('task', totalPassed)} passed`)}`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.printLine();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
onFileStart(file: string): void {
|
|
240
|
+
this.currentFile = file;
|
|
241
|
+
|
|
242
|
+
if (this.quiet) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const displayPath = this.formatPath(file);
|
|
247
|
+
const fileMarker = `${colors.magenta}${ansiChars.block.dark}${ansiChars.block.dark}${colors.reset}`;
|
|
248
|
+
this.printLine(
|
|
249
|
+
`${fileMarker} ${colors.underline}${this.colorize('brightMagenta', this.colorize('bold', displayPath))}${colors.reset}`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
onProgress(state: ProgressState): void {
|
|
254
|
+
// Only show progress bar in non-verbose, non-quiet mode with progress enabled
|
|
255
|
+
if (this.quiet || this.verbose || !this.showProgress) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Only show in TTY mode (progress bar updates in place)
|
|
260
|
+
if (!process.stdout.isTTY) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const { elapsed, percentage, tasksCompleted, totalTasks } = state;
|
|
265
|
+
|
|
266
|
+
// Pad task counts for alignment
|
|
267
|
+
const totalTasksWidth = String(totalTasks).length;
|
|
268
|
+
const paddedTasksCompleted = String(tasksCompleted).padStart(
|
|
269
|
+
totalTasksWidth,
|
|
270
|
+
' ',
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// Format elapsed time
|
|
274
|
+
const elapsedSeconds = Math.round(elapsed / 1000);
|
|
275
|
+
const elapsedStrRaw = this.formatTimeRemaining(elapsedSeconds);
|
|
276
|
+
|
|
277
|
+
// Calculate ETA if we have completed tasks and determine padding width
|
|
278
|
+
let etaStr = '';
|
|
279
|
+
let padWidth = elapsedStrRaw.length;
|
|
280
|
+
if (tasksCompleted > 0) {
|
|
281
|
+
const avgTimePerTask = elapsed / tasksCompleted;
|
|
282
|
+
const remainingTasks = totalTasks - tasksCompleted;
|
|
283
|
+
const etaMs = avgTimePerTask * remainingTasks;
|
|
284
|
+
const etaSeconds = Math.round(etaMs / 1000);
|
|
285
|
+
const etaTimeStr = this.formatTimeRemaining(etaSeconds);
|
|
286
|
+
padWidth = Math.max(padWidth, etaTimeStr.length);
|
|
287
|
+
etaStr = ` ${this.colorize('dim', '|')} ${this.colorize('dim', 'ETA:')} ${this.colorize('brightBlue', etaTimeStr)}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Pad elapsed time to match the longest time string
|
|
291
|
+
const elapsedStr = elapsedStrRaw.padStart(padWidth, ' ');
|
|
292
|
+
|
|
293
|
+
const roundedPercentage = percentage.toFixed(2);
|
|
294
|
+
const line = `${this.colorize('brightCyan', ansiChars.approx)} ${this.colorize('white', paddedTasksCompleted)}${this.colorize('dim', '/')}${this.colorize('white', String(totalTasks))} ${this.colorize('dim', 'tasks')} ${this.colorize('dim', '(')}${this.colorize('brightBlue', roundedPercentage + '%')}${this.colorize('dim', ')')} ${this.colorize('dim', '|')} ${this.colorize('dim', 'Elapsed:')} ${this.colorize('cyan', elapsedStr)}${etaStr}`;
|
|
295
|
+
|
|
296
|
+
this.lastProgressLine = line;
|
|
297
|
+
this.renderProgressWindow();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
onStart(run: BenchmarkRun): void {
|
|
301
|
+
this.startTime = Date.now();
|
|
302
|
+
this.failures = []; // Reset failures for new run
|
|
303
|
+
this.lastProgressLine = ''; // Reset for new run
|
|
304
|
+
|
|
305
|
+
if (this.quiet) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let header: string;
|
|
310
|
+
if (run.environment) {
|
|
311
|
+
header = `
|
|
312
|
+
\x1b[49m \x1b[38;5;0;49m▄▄\x1b[38;5;37;48;5;0m▄\x1b[38;5;14;48;5;0m▄\x1b[38;5;6;48;5;0m▄\x1b[38;5;0;49m▄▄\x1b[49m \x1b[m
|
|
313
|
+
\x1b[49m \x1b[38;5;0;49m▄\x1b[38;5;235;48;5;0m▄\x1b[38;5;45;48;5;0m▄\x1b[38;5;14;48;5;23m▄\x1b[48;5;14m \x1b[38;5;14;48;5;14m▄\x1b[38;5;14;48;5;236m▄\x1b[38;5;44;48;5;0m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;0;49m▄\x1b[49m \x1b[m
|
|
314
|
+
\x1b[38;5;0;49m▄▄\x1b[38;5;30;48;5;0m▄\x1b[38;5;14;48;5;233m▄\x1b[38;5;14;48;5;37m▄\x1b[48;5;14m \x1b[38;5;14;48;5;37m▄\x1b[38;5;14;48;5;0m▄\x1b[38;5;23;48;5;0m▄\x1b[38;5;0;49m▄▄\x1b[m
|
|
315
|
+
\x1b[48;5;0m \x1b[38;5;14;48;5;45m▄\x1b[48;5;14m \x1b[38;5;44;48;5;14m▄\x1b[38;5;24;48;5;14m▄\x1b[38;5;242;48;5;14m▄\x1b[38;5;5;48;5;14m▄\x1b[38;5;60;48;5;14m▄\x1b[38;5;24;48;5;14m▄\x1b[38;5;44;48;5;14m▄\x1b[48;5;14m \x1b[38;5;14;48;5;44m▄\x1b[48;5;0m \x1b[m
|
|
316
|
+
\x1b[48;5;0m \x1b[48;5;14m \x1b[38;5;44;48;5;14m▄\x1b[38;5;53;48;5;45m▄\x1b[38;5;44;48;5;53m▄\x1b[38;5;14;48;5;162m▄\x1b[38;5;14;48;5;89m▄▄\x1b[38;5;14;48;5;162m▄\x1b[38;5;44;48;5;198m▄\x1b[38;5;235;48;5;198m▄\x1b[48;5;198m \x1b[38;5;30;48;5;237m▄\x1b[38;5;38;48;5;14m▄\x1b[48;5;14m \x1b[48;5;0m \x1b[m \x1b[97m\x1b[4;1mmodest\x1b[0m\x1b[4;97mbench\x1b[0m \x1b[4;97m \x1b[0m \x1b[4;97m \x1b[0m
|
|
317
|
+
\x1b[48;5;0m \x1b[48;5;14m \x1b[38;5;237;48;5;45m▄\x1b[38;5;14;48;5;23m▄\x1b[48;5;14m \x1b[38;5;14;48;5;14m▄\x1b[38;5;53;48;5;38m▄\x1b[38;5;44;48;5;23m▄\x1b[38;5;198;48;5;238m▄\x1b[38;5;198;48;5;125m▄\x1b[38;5;23;48;5;14m▄\x1b[48;5;14m \x1b[48;5;0m \x1b[m
|
|
318
|
+
\x1b[48;5;0m \x1b[48;5;14m \x1b[38;5;30;48;5;38m▄\x1b[38;5;14;48;5;14m▄\x1b[48;5;14m \x1b[38;5;45;48;5;14m▄\x1b[38;5;89;48;5;14m▄\x1b[38;5;89;48;5;89m▄\x1b[38;5;14;48;5;31m▄\x1b[48;5;14m \x1b[38;5;37;48;5;89m▄\x1b[48;5;198m \x1b[38;5;198;48;5;198m▄\x1b[38;5;31;48;5;14m▄\x1b[48;5;14m \x1b[48;5;0m \x1b[m \x1b[2mnode.js:\x1b[m \x1b[36m${run.environment.nodeVersion} \x1b[m
|
|
319
|
+
\x1b[48;5;0m \x1b[48;5;14m \x1b[38;5;44;48;5;31m▄\x1b[48;5;14m \x1b[38;5;126;48;5;38m▄\x1b[38;5;198;48;5;237m▄\x1b[38;5;237;48;5;37m▄\x1b[48;5;14m \x1b[38;5;14;48;5;14m▄\x1b[38;5;162;48;5;198m▄▄\x1b[38;5;53;48;5;240m▄\x1b[48;5;14m \x1b[48;5;0m \x1b[m \x1b[2mplatform:\x1b[m \x1b[36m${run.environment.platform} ${run.environment.arch} \x1b[m
|
|
320
|
+
\x1b[48;5;0m \x1b[38;5;45;48;5;14m▄\x1b[48;5;14m \x1b[38;5;14;48;5;37m▄\x1b[38;5;14;48;5;5m▄\x1b[38;5;14;48;5;44m▄\x1b[48;5;14m \x1b[38;5;45;48;5;14m▄\x1b[48;5;0m \x1b[m \x1b[2mcpu:\x1b[m \x1b[36m${run.environment.cpu.model} \x1b[2m(\x1b[m\x1b[36m${run.environment.cpu.cores} cores) \x1b[m
|
|
321
|
+
\x1b[49;38;5;0m▀▀\x1b[38;5;0;48;5;6m▄\x1b[38;5;232;48;5;14m▄\x1b[38;5;38;48;5;14m▄\x1b[48;5;14m \x1b[38;5;30;48;5;14m▄\x1b[38;5;0;48;5;14m▄\x1b[38;5;0;48;5;23m▄\x1b[49;38;5;0m▀▀\x1b[m \x1b[2mmem:\x1b[m \x1b[36m${this.formatBytes(run.environment.memory.total)} \x1b[m
|
|
322
|
+
\x1b[49m \x1b[49;38;5;0m▀\x1b[38;5;0;48;5;236m▄\x1b[38;5;0;48;5;45m▄\x1b[38;5;23;48;5;14m▄\x1b[48;5;14m \x1b[38;5;236;48;5;14m▄\x1b[38;5;0;48;5;44m▄\x1b[38;5;0;48;5;232m▄\x1b[49;38;5;0m▀\x1b[49m \x1b[m
|
|
323
|
+
\x1b[49m \x1b[49;38;5;0m▀▀\x1b[38;5;0;48;5;37m▄\x1b[38;5;0;48;5;14m▄\x1b[38;5;0;48;5;30m▄\x1b[49;38;5;0m▀▀\x1b[49m \x1b[m
|
|
324
|
+
`;
|
|
325
|
+
} else {
|
|
326
|
+
header = `
|
|
327
|
+
\x1b[49m \x1b[38;5;0;49m▄▄\x1b[38;5;37;48;5;0m▄\x1b[38;5;14;48;5;0m▄\x1b[38;5;6;48;5;0m▄\x1b[38;5;0;49m▄▄\x1b[49m \x1b[m
|
|
328
|
+
\x1b[49m \x1b[38;5;0;49m▄\x1b[38;5;235;48;5;0m▄\x1b[38;5;45;48;5;0m▄\x1b[38;5;14;48;5;23m▄\x1b[48;5;14m \x1b[38;5;14;48;5;14m▄\x1b[38;5;14;48;5;236m▄\x1b[38;5;44;48;5;0m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;0;49m▄\x1b[49m \x1b[m
|
|
329
|
+
\x1b[38;5;0;49m▄▄\x1b[38;5;30;48;5;0m▄\x1b[38;5;14;48;5;233m▄\x1b[38;5;14;48;5;37m▄\x1b[48;5;14m \x1b[38;5;14;48;5;37m▄\x1b[38;5;14;48;5;0m▄\x1b[38;5;23;48;5;0m▄\x1b[38;5;0;49m▄▄\x1b[m
|
|
330
|
+
\x1b[48;5;0m \x1b[38;5;14;48;5;45m▄\x1b[48;5;14m \x1b[38;5;44;48;5;14m▄\x1b[38;5;24;48;5;14m▄\x1b[38;5;242;48;5;14m▄\x1b[38;5;5;48;5;14m▄\x1b[38;5;60;48;5;14m▄\x1b[38;5;24;48;5;14m▄\x1b[38;5;44;48;5;14m▄\x1b[48;5;14m \x1b[38;5;14;48;5;44m▄\x1b[48;5;0m \x1b[m
|
|
331
|
+
\x1b[48;5;0m \x1b[48;5;14m \x1b[38;5;44;48;5;14m▄\x1b[38;5;53;48;5;45m▄\x1b[38;5;44;48;5;53m▄\x1b[38;5;14;48;5;162m▄\x1b[38;5;14;48;5;89m▄▄\x1b[38;5;14;48;5;162m▄\x1b[38;5;44;48;5;198m▄\x1b[38;5;235;48;5;198m▄\x1b[48;5;198m \x1b[38;5;30;48;5;237m▄\x1b[38;5;38;48;5;14m▄\x1b[48;5;14m \x1b[48;5;0m \x1b[m \x1b[97m\x1b[4;1mmodest\x1b[0m\x1b[4;97mbench\x1b[0m \x1b[4;97m \x1b[0m
|
|
332
|
+
\x1b[48;5;0m \x1b[48;5;14m \x1b[38;5;237;48;5;45m▄\x1b[38;5;14;48;5;23m▄\x1b[48;5;14m \x1b[38;5;14;48;5;14m▄\x1b[38;5;53;48;5;38m▄\x1b[38;5;44;48;5;23m▄\x1b[38;5;198;48;5;238m▄\x1b[38;5;198;48;5;125m▄\x1b[38;5;23;48;5;14m▄\x1b[48;5;14m \x1b[48;5;0m \x1b[m
|
|
333
|
+
\x1b[48;5;0m \x1b[48;5;14m \x1b[38;5;30;48;5;38m▄\x1b[38;5;14;48;5;14m▄\x1b[48;5;14m \x1b[38;5;45;48;5;14m▄\x1b[38;5;89;48;5;14m▄\x1b[38;5;89;48;5;89m▄\x1b[38;5;14;48;5;31m▄\x1b[48;5;14m \x1b[38;5;37;48;5;89m▄\x1b[48;5;198m \x1b[38;5;198;48;5;198m▄\x1b[38;5;31;48;5;14m▄\x1b[48;5;14m \x1b[48;5;0m \x1b[m
|
|
334
|
+
\x1b[48;5;0m \x1b[48;5;14m \x1b[38;5;44;48;5;31m▄\x1b[48;5;14m \x1b[38;5;126;48;5;38m▄\x1b[38;5;198;48;5;237m▄\x1b[38;5;237;48;5;37m▄\x1b[48;5;14m \x1b[38;5;14;48;5;14m▄\x1b[38;5;162;48;5;198m▄▄\x1b[38;5;53;48;5;240m▄\x1b[48;5;14m \x1b[48;5;0m \x1b[m
|
|
335
|
+
\x1b[48;5;0m \x1b[38;5;45;48;5;14m▄\x1b[48;5;14m \x1b[38;5;14;48;5;37m▄\x1b[38;5;14;48;5;5m▄\x1b[38;5;14;48;5;44m▄\x1b[48;5;14m \x1b[38;5;45;48;5;14m▄\x1b[48;5;0m \x1b[m
|
|
336
|
+
\x1b[49;38;5;0m▀▀\x1b[38;5;0;48;5;6m▄\x1b[38;5;232;48;5;14m▄\x1b[38;5;38;48;5;14m▄\x1b[48;5;14m \x1b[38;5;30;48;5;14m▄\x1b[38;5;0;48;5;14m▄\x1b[38;5;0;48;5;23m▄\x1b[49;38;5;0m▀▀\x1b[m
|
|
337
|
+
\x1b[49m \x1b[49;38;5;0m▀\x1b[38;5;0;48;5;236m▄\x1b[38;5;0;48;5;45m▄\x1b[38;5;23;48;5;14m▄\x1b[48;5;14m \x1b[38;5;236;48;5;14m▄\x1b[38;5;0;48;5;44m▄\x1b[38;5;0;48;5;232m▄\x1b[49;38;5;0m▀\x1b[49m \x1b[m
|
|
338
|
+
\x1b[49m \x1b[49;38;5;0m▀▀\x1b[38;5;0;48;5;37m▄\x1b[38;5;0;48;5;14m▄\x1b[38;5;0;48;5;30m▄\x1b[49;38;5;0m▀▀\x1b[49m \x1b[m
|
|
339
|
+
`;
|
|
340
|
+
}
|
|
341
|
+
this.printLine(header);
|
|
342
|
+
this.printLine();
|
|
343
|
+
|
|
344
|
+
if (run.git) {
|
|
345
|
+
this.printLine(` Git: ${this.colorize('cyan', run.git.commit)}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (run.ci) {
|
|
349
|
+
this.printLine(` CI: ${this.colorize('cyan', run.ci.provider)}`);
|
|
350
|
+
this.printLine();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
onSuiteEnd(result: SuiteResult): void {
|
|
355
|
+
if (this.quiet) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Print all buffered task results with aligned columns
|
|
360
|
+
this.printAlignedSuiteResults();
|
|
361
|
+
|
|
362
|
+
// Skip displaying summary for the implicit "default" suite
|
|
363
|
+
if (result.name === 'default') {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const passed = result.tasks.filter((t) => !t.error).length;
|
|
368
|
+
const failed = result.tasks.filter((t) => t.error).length;
|
|
369
|
+
|
|
370
|
+
if (failed > 0) {
|
|
371
|
+
this.printLine(
|
|
372
|
+
` ${this.colorize('red', `${ansiChars.cross} ${failed} failed`)}, ${this.colorize('green', `${passed} passed`)}`,
|
|
373
|
+
);
|
|
374
|
+
} else {
|
|
375
|
+
this.printLine(
|
|
376
|
+
` ${this.colorize('magenta', ansiChars.checkmark)} ${this.colorize('bold', this.colorize('brightWhite', `${passed}`))} ${this.colorize('brightWhite', `${this.pluralize('task', passed)} passed`)}`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
this.printLine();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
onSuiteStart(suite: string): void {
|
|
383
|
+
this.currentSuite = suite;
|
|
384
|
+
|
|
385
|
+
if (this.quiet) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
this.suiteResults = []; // Reset buffer for new suite
|
|
390
|
+
|
|
391
|
+
// Skip displaying the implicit "default" suite header
|
|
392
|
+
if (suite === 'default') {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
this.printLine();
|
|
397
|
+
const suiteMarker = `${colors.magenta}${ansiChars.block.light}${ansiChars.block.light}${colors.reset}`;
|
|
398
|
+
this.printLine(
|
|
399
|
+
` ${suiteMarker} ${this.colorize('bold', this.colorize('brightWhite', suite))}`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
onTaskResult(result: TaskResult): void {
|
|
404
|
+
if (this.quiet) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Buffer the result for later printing with proper alignment
|
|
409
|
+
this.suiteResults.push(result);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
onTaskStart(task: string): void {
|
|
413
|
+
if (this.quiet) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Only show static markers in verbose mode
|
|
418
|
+
if (this.verbose) {
|
|
419
|
+
this.printLine(
|
|
420
|
+
` ${this.colorize('gray', ansiChars.smallSquare)} ${task}`,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Clear current progress display
|
|
427
|
+
*/
|
|
428
|
+
private clearProgress(): void {
|
|
429
|
+
this.clearProgressWindow();
|
|
430
|
+
this.lastProgressLine = '';
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Clear the progress window at the bottom (Vitest-style)
|
|
435
|
+
*/
|
|
436
|
+
private clearProgressWindow(): void {
|
|
437
|
+
if (!process.stdout.isTTY || !this.progressWindowActive) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
// Move up and clear two lines (blank line + progress line)
|
|
441
|
+
// '\x1b[1A' moves cursor up one line, '\x1b[K' clears the current line
|
|
442
|
+
// This sequence moves up and clears two lines in total
|
|
443
|
+
process.stdout.write('\x1b[1A\x1b[K\x1b[1A\x1b[K');
|
|
444
|
+
this.progressWindowActive = false;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Apply color to text if colors are enabled
|
|
449
|
+
*/
|
|
450
|
+
private colorize(color: keyof typeof colors, text: string): string {
|
|
451
|
+
if (!this.useColor) {
|
|
452
|
+
return text;
|
|
453
|
+
}
|
|
454
|
+
return `${colors[color]}${text}${colors.reset}`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Format bytes in human-readable format
|
|
459
|
+
*/
|
|
460
|
+
private formatBytes(bytes: number): string {
|
|
461
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
462
|
+
let size = bytes;
|
|
463
|
+
let unitIndex = 0;
|
|
464
|
+
|
|
465
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
466
|
+
size /= 1024;
|
|
467
|
+
unitIndex++;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Format file path - show relative path if within CWD, otherwise absolute
|
|
475
|
+
*/
|
|
476
|
+
private formatPath(filePath: string): string {
|
|
477
|
+
const cwd = process.cwd();
|
|
478
|
+
const absolutePath = path.resolve(filePath);
|
|
479
|
+
|
|
480
|
+
// Check if the file is within the current working directory
|
|
481
|
+
if (absolutePath.startsWith(cwd + path.sep) || absolutePath === cwd) {
|
|
482
|
+
return path.relative(cwd, absolutePath);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return absolutePath;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Format duration in human-readable format for progress display
|
|
490
|
+
*/
|
|
491
|
+
private formatTimeRemaining(seconds: number): string {
|
|
492
|
+
if (seconds < 60) {
|
|
493
|
+
return `${seconds}s`;
|
|
494
|
+
} else if (seconds < 3600) {
|
|
495
|
+
const minutes = Math.floor(seconds / 60);
|
|
496
|
+
const remainingSeconds = seconds % 60;
|
|
497
|
+
return `${minutes}m${remainingSeconds}s`;
|
|
498
|
+
} else {
|
|
499
|
+
const hours = Math.floor(seconds / 3600);
|
|
500
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
501
|
+
return `${hours}h${minutes}m`;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Get visible length of string (excluding ANSI escape codes)
|
|
507
|
+
*/
|
|
508
|
+
private getVisibleLength(str: string): number {
|
|
509
|
+
// Remove ANSI escape codes to get actual visible length
|
|
510
|
+
// eslint-disable-next-line no-control-regex
|
|
511
|
+
return str.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Simple pluralization helper
|
|
516
|
+
*/
|
|
517
|
+
private pluralize(str: string, count: number): string {
|
|
518
|
+
return count === 1 ? str : `${str}s`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Print all task results in a suite with aligned columns
|
|
523
|
+
*/
|
|
524
|
+
private printAlignedSuiteResults(): void {
|
|
525
|
+
if (this.suiteResults.length === 0) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const MAX_NAME_WIDTH = 60;
|
|
530
|
+
const BASE_INDENT = ' '; // 4 spaces
|
|
531
|
+
const bullet = this.colorize(
|
|
532
|
+
'dim',
|
|
533
|
+
this.colorize('gray', ansiChars.bullet),
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
// Prepare formatted data for each task
|
|
537
|
+
interface FormattedTask {
|
|
538
|
+
durationLen: number;
|
|
539
|
+
durationStr: string;
|
|
540
|
+
error: boolean;
|
|
541
|
+
errorMessage?: string;
|
|
542
|
+
iterations: number;
|
|
543
|
+
name: string;
|
|
544
|
+
nameLength: number;
|
|
545
|
+
opsPerSecLen: number;
|
|
546
|
+
opsPerSecStr: string;
|
|
547
|
+
rmeLen: number;
|
|
548
|
+
rmeStr: string;
|
|
549
|
+
status: string;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const formatted: FormattedTask[] = this.suiteResults.map((result) => {
|
|
553
|
+
const status = result.error
|
|
554
|
+
? this.colorize('red', ansiChars.cross)
|
|
555
|
+
: this.colorize('brightCyan', ansiChars.checkmark);
|
|
556
|
+
|
|
557
|
+
const name = result.name.trim();
|
|
558
|
+
const nameLength = this.getVisibleLength(name);
|
|
559
|
+
|
|
560
|
+
if (result.error) {
|
|
561
|
+
return {
|
|
562
|
+
durationLen: 0,
|
|
563
|
+
durationStr: '',
|
|
564
|
+
error: true,
|
|
565
|
+
errorMessage: result.error?.message || String(result.error),
|
|
566
|
+
iterations: 0,
|
|
567
|
+
name,
|
|
568
|
+
nameLength,
|
|
569
|
+
opsPerSecLen: 0,
|
|
570
|
+
opsPerSecStr: '',
|
|
571
|
+
rmeLen: 0,
|
|
572
|
+
rmeStr: '',
|
|
573
|
+
status,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const duration = this.formatDuration(result.mean); // already in nanoseconds
|
|
578
|
+
const opsPerSec = this.formatOpsPerSecond(result.opsPerSecond);
|
|
579
|
+
const rme = this.formatPercentage(result.marginOfError * 100);
|
|
580
|
+
|
|
581
|
+
return {
|
|
582
|
+
durationLen: this.getVisibleLength(duration),
|
|
583
|
+
durationStr: duration,
|
|
584
|
+
error: false,
|
|
585
|
+
iterations: result.iterations,
|
|
586
|
+
name,
|
|
587
|
+
nameLength,
|
|
588
|
+
opsPerSecLen: this.getVisibleLength(opsPerSec),
|
|
589
|
+
opsPerSecStr: opsPerSec,
|
|
590
|
+
rmeLen: this.getVisibleLength(rme),
|
|
591
|
+
rmeStr: rme,
|
|
592
|
+
status,
|
|
593
|
+
};
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// Find max widths
|
|
597
|
+
const nonWrappingTasks = formatted.filter(
|
|
598
|
+
(t) => t.nameLength <= MAX_NAME_WIDTH,
|
|
599
|
+
);
|
|
600
|
+
const maxNameLen =
|
|
601
|
+
nonWrappingTasks.length > 0
|
|
602
|
+
? Math.max(...nonWrappingTasks.map((t) => t.nameLength))
|
|
603
|
+
: 40; // Default if all tasks wrap
|
|
604
|
+
|
|
605
|
+
const maxDurationLen = Math.max(
|
|
606
|
+
...formatted.filter((t) => !t.error).map((t) => t.durationLen),
|
|
607
|
+
0,
|
|
608
|
+
);
|
|
609
|
+
const maxRmeLen = Math.max(
|
|
610
|
+
...formatted.filter((t) => !t.error).map((t) => t.rmeLen),
|
|
611
|
+
0,
|
|
612
|
+
);
|
|
613
|
+
const maxOpsLen = Math.max(
|
|
614
|
+
...formatted.filter((t) => !t.error).map((t) => t.opsPerSecLen),
|
|
615
|
+
0,
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
// Calculate the position where numbers start for unwrapped lines
|
|
619
|
+
// BASE_INDENT (4) + status (1 char) + space (1) + maxNameLen + ": " (2) = 8 + maxNameLen
|
|
620
|
+
const numbersStartPos = BASE_INDENT.length + 2 + maxNameLen + 2;
|
|
621
|
+
|
|
622
|
+
// Print each task with aligned columns
|
|
623
|
+
for (const task of formatted) {
|
|
624
|
+
if (task.error) {
|
|
625
|
+
// Track failure for end summary
|
|
626
|
+
this.failures.push({
|
|
627
|
+
error: task.errorMessage || 'Unknown error',
|
|
628
|
+
file: this.currentFile,
|
|
629
|
+
suite: this.currentSuite,
|
|
630
|
+
task: task.name,
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
this.printLine(
|
|
634
|
+
`${BASE_INDENT}${task.status} ${this.colorize('white', task.name)} ${this.colorize('red', 'FAILED')}`,
|
|
635
|
+
);
|
|
636
|
+
} else if (task.nameLength > MAX_NAME_WIDTH) {
|
|
637
|
+
// Long name - wrap to next line, but align numbers with unwrapped lines
|
|
638
|
+
this.printLine(
|
|
639
|
+
`${BASE_INDENT}${task.status} ${this.colorize('white', task.name)}:`,
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
// Calculate padding to align with unwrapped lines
|
|
643
|
+
// We need to get to numbersStartPos from the beginning of the line
|
|
644
|
+
const leadingPad = ' '.repeat(numbersStartPos);
|
|
645
|
+
const durationPad = ' '.repeat(maxDurationLen - task.durationLen);
|
|
646
|
+
const rmePad = ' '.repeat(maxRmeLen - task.rmeLen);
|
|
647
|
+
const opsPad = ' '.repeat(maxOpsLen - task.opsPerSecLen);
|
|
648
|
+
|
|
649
|
+
this.printLine(
|
|
650
|
+
`${leadingPad}${durationPad}${this.colorize('cyan', task.durationStr)} ${bullet} ${ansiChars.plusMinus}${rmePad}${this.colorize('brightBlue', task.rmeStr)} ${bullet} ${opsPad}${this.colorize('magenta', task.opsPerSecStr)}`,
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
if (this.verbose && task.iterations > 0) {
|
|
654
|
+
this.printLine(
|
|
655
|
+
` ${this.colorize('dim', `${task.iterations} iterations`)}`,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
} else {
|
|
659
|
+
// Normal length - align on same line
|
|
660
|
+
const namePad = ' '.repeat(maxNameLen - task.nameLength);
|
|
661
|
+
const durationPad = ' '.repeat(maxDurationLen - task.durationLen);
|
|
662
|
+
const rmePad = ' '.repeat(maxRmeLen - task.rmeLen);
|
|
663
|
+
const opsPad = ' '.repeat(maxOpsLen - task.opsPerSecLen);
|
|
664
|
+
|
|
665
|
+
this.printLine(
|
|
666
|
+
`${BASE_INDENT}${task.status} ${this.colorize('white', task.name)}${namePad}: ${durationPad}${this.colorize('cyan', task.durationStr)} ${bullet} ${ansiChars.plusMinus}${rmePad}${this.colorize('brightBlue', task.rmeStr)} ${bullet} ${opsPad}${this.colorize('magenta', task.opsPerSecStr)}`,
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
if (this.verbose && task.iterations > 0) {
|
|
670
|
+
this.printLine(
|
|
671
|
+
` ${this.colorize('dim', `${task.iterations} iterations`)}`,
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Print a line while maintaining the progress window at the bottom
|
|
680
|
+
* (Vitest-style)
|
|
681
|
+
*/
|
|
682
|
+
private printLine(message: string = ''): void {
|
|
683
|
+
// Clear progress window, print content, re-render progress window
|
|
684
|
+
this.clearProgressWindow();
|
|
685
|
+
console.log(message);
|
|
686
|
+
this.renderProgressWindow();
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Render the progress window at the bottom
|
|
691
|
+
*/
|
|
692
|
+
private renderProgressWindow(): void {
|
|
693
|
+
if (!process.stdout.isTTY || !this.lastProgressLine) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Clear existing window if present
|
|
698
|
+
if (this.progressWindowActive) {
|
|
699
|
+
this.clearProgressWindow();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Write blank line for spacing, then progress line
|
|
703
|
+
console.log('');
|
|
704
|
+
console.log(this.lastProgressLine);
|
|
705
|
+
this.progressWindowActive = true;
|
|
706
|
+
}
|
|
707
|
+
}
|