modestbench 0.2.0 → 0.3.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 +27 -0
- package/README.md +131 -34
- package/dist/cli/commands/analyze.cjs +60 -0
- package/dist/cli/commands/analyze.cjs.map +1 -0
- package/dist/cli/commands/analyze.d.cts +35 -0
- package/dist/cli/commands/analyze.d.cts.map +1 -0
- package/dist/cli/commands/analyze.d.ts +35 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +56 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/baseline.cjs +404 -0
- package/dist/cli/commands/baseline.cjs.map +1 -0
- package/dist/cli/commands/baseline.d.cts +72 -0
- package/dist/cli/commands/baseline.d.cts.map +1 -0
- package/dist/cli/commands/baseline.d.ts +72 -0
- package/dist/cli/commands/baseline.d.ts.map +1 -0
- package/dist/cli/commands/baseline.js +396 -0
- package/dist/cli/commands/baseline.js.map +1 -0
- package/dist/cli/commands/history.d.cts +1 -1
- package/dist/cli/commands/history.d.cts.map +1 -1
- package/dist/cli/commands/history.d.ts +1 -1
- package/dist/cli/commands/history.d.ts.map +1 -1
- package/dist/cli/commands/init.cjs +99 -166
- package/dist/cli/commands/init.cjs.map +1 -1
- package/dist/cli/commands/init.d.cts +4 -4
- package/dist/cli/commands/init.d.cts.map +1 -1
- package/dist/cli/commands/init.d.ts +4 -4
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +99 -166
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/run.cjs +146 -127
- package/dist/cli/commands/run.cjs.map +1 -1
- package/dist/cli/commands/run.d.cts +16 -3
- package/dist/cli/commands/run.d.cts.map +1 -1
- package/dist/cli/commands/run.d.ts +16 -3
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +145 -93
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/index.cjs +583 -394
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +4 -16
- package/dist/cli/index.d.cts.map +1 -1
- package/dist/cli/index.d.ts +4 -16
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +575 -386
- package/dist/cli/index.js.map +1 -1
- package/dist/config/budget-schema.cjs +172 -0
- package/dist/config/budget-schema.cjs.map +1 -0
- package/dist/config/budget-schema.d.cts +59 -0
- package/dist/config/budget-schema.d.cts.map +1 -0
- package/dist/config/budget-schema.d.ts +59 -0
- package/dist/config/budget-schema.d.ts.map +1 -0
- package/dist/config/budget-schema.js +166 -0
- package/dist/config/budget-schema.js.map +1 -0
- package/dist/config/schema.cjs +182 -2
- package/dist/config/schema.cjs.map +1 -1
- package/dist/config/schema.d.cts +122 -3
- package/dist/config/schema.d.cts.map +1 -1
- package/dist/config/schema.d.ts +122 -3
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +180 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/constants.cjs +45 -2
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +41 -0
- package/dist/constants.d.cts.map +1 -1
- package/dist/constants.d.ts +41 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +44 -1
- package/dist/constants.js.map +1 -1
- package/dist/core/engine.cjs +114 -23
- package/dist/core/engine.cjs.map +1 -1
- package/dist/core/engine.d.cts +7 -7
- package/dist/core/engine.d.cts.map +1 -1
- package/dist/core/engine.d.ts +7 -7
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +115 -24
- package/dist/core/engine.js.map +1 -1
- package/dist/core/engines/accurate-engine.cjs +171 -36
- package/dist/core/engines/accurate-engine.cjs.map +1 -1
- package/dist/core/engines/accurate-engine.d.cts +5 -0
- package/dist/core/engines/accurate-engine.d.cts.map +1 -1
- package/dist/core/engines/accurate-engine.d.ts +5 -0
- package/dist/core/engines/accurate-engine.d.ts.map +1 -1
- package/dist/core/engines/accurate-engine.js +171 -36
- package/dist/core/engines/accurate-engine.js.map +1 -1
- package/dist/core/engines/tinybench-engine.cjs +3 -2
- package/dist/core/engines/tinybench-engine.cjs.map +1 -1
- package/dist/core/engines/tinybench-engine.d.cts.map +1 -1
- package/dist/core/engines/tinybench-engine.d.ts.map +1 -1
- package/dist/core/engines/tinybench-engine.js +3 -2
- package/dist/core/engines/tinybench-engine.js.map +1 -1
- package/dist/core/output-path-resolver.cjs +8 -1
- package/dist/core/output-path-resolver.cjs.map +1 -1
- package/dist/core/output-path-resolver.d.cts.map +1 -1
- package/dist/core/output-path-resolver.d.ts.map +1 -1
- package/dist/core/output-path-resolver.js +9 -2
- package/dist/core/output-path-resolver.js.map +1 -1
- package/dist/errors/base.cjs +12 -3
- package/dist/errors/base.cjs.map +1 -1
- package/dist/errors/base.d.cts +7 -0
- package/dist/errors/base.d.cts.map +1 -1
- package/dist/errors/base.d.ts +7 -0
- package/dist/errors/base.d.ts.map +1 -1
- package/dist/errors/base.js +10 -2
- package/dist/errors/base.js.map +1 -1
- package/dist/errors/budget.cjs +37 -0
- package/dist/errors/budget.cjs.map +1 -0
- package/dist/errors/budget.d.cts +31 -0
- package/dist/errors/budget.d.cts.map +1 -0
- package/dist/errors/budget.d.ts +31 -0
- package/dist/errors/budget.d.ts.map +1 -0
- package/dist/errors/budget.js +33 -0
- package/dist/errors/budget.js.map +1 -0
- package/dist/errors/index.cjs +4 -1
- package/dist/errors/index.cjs.map +1 -1
- package/dist/errors/index.d.cts +1 -0
- package/dist/errors/index.d.cts.map +1 -1
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +2 -0
- package/dist/errors/index.js.map +1 -1
- package/dist/index.cjs +13 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/reporters/csv.cjs +37 -17
- package/dist/reporters/csv.cjs.map +1 -1
- package/dist/reporters/csv.d.cts +3 -6
- package/dist/reporters/csv.d.cts.map +1 -1
- package/dist/reporters/csv.d.ts +3 -6
- package/dist/reporters/csv.d.ts.map +1 -1
- package/dist/reporters/csv.js +37 -17
- package/dist/reporters/csv.js.map +1 -1
- package/dist/reporters/human.cjs +290 -67
- package/dist/reporters/human.cjs.map +1 -1
- package/dist/reporters/human.d.cts +25 -13
- package/dist/reporters/human.d.cts.map +1 -1
- package/dist/reporters/human.d.ts +25 -13
- package/dist/reporters/human.d.ts.map +1 -1
- package/dist/reporters/human.js +290 -67
- package/dist/reporters/human.js.map +1 -1
- package/dist/reporters/json.cjs +23 -48
- package/dist/reporters/json.cjs.map +1 -1
- package/dist/reporters/json.d.cts +2 -28
- package/dist/reporters/json.d.cts.map +1 -1
- package/dist/reporters/json.d.ts +2 -28
- package/dist/reporters/json.d.ts.map +1 -1
- package/dist/reporters/json.js +25 -50
- package/dist/reporters/json.js.map +1 -1
- package/dist/reporters/profile-human.cjs +154 -0
- package/dist/reporters/profile-human.cjs.map +1 -0
- package/dist/reporters/profile-human.d.cts +44 -0
- package/dist/reporters/profile-human.d.cts.map +1 -0
- package/dist/reporters/profile-human.d.ts +44 -0
- package/dist/reporters/profile-human.d.ts.map +1 -0
- package/dist/reporters/profile-human.js +147 -0
- package/dist/reporters/profile-human.js.map +1 -0
- package/dist/reporters/simple.cjs +67 -45
- package/dist/reporters/simple.cjs.map +1 -1
- package/dist/reporters/simple.d.cts +14 -14
- package/dist/reporters/simple.d.cts.map +1 -1
- package/dist/reporters/simple.d.ts +14 -14
- package/dist/reporters/simple.d.ts.map +1 -1
- package/dist/reporters/simple.js +67 -45
- package/dist/reporters/simple.js.map +1 -1
- package/dist/schema/modestbench-config.schema.json +153 -0
- package/dist/services/baseline-storage.cjs +151 -0
- package/dist/services/baseline-storage.cjs.map +1 -0
- package/dist/services/baseline-storage.d.cts +55 -0
- package/dist/services/baseline-storage.d.cts.map +1 -0
- package/dist/services/baseline-storage.d.ts +55 -0
- package/dist/services/baseline-storage.d.ts.map +1 -0
- package/dist/services/baseline-storage.js +147 -0
- package/dist/services/baseline-storage.js.map +1 -0
- package/dist/services/budget-evaluator.cjs +146 -0
- package/dist/services/budget-evaluator.cjs.map +1 -0
- package/dist/services/budget-evaluator.d.cts +29 -0
- package/dist/services/budget-evaluator.d.cts.map +1 -0
- package/dist/services/budget-evaluator.d.ts +29 -0
- package/dist/services/budget-evaluator.d.ts.map +1 -0
- package/dist/services/budget-evaluator.js +142 -0
- package/dist/services/budget-evaluator.js.map +1 -0
- package/dist/services/config-manager.cjs +24 -10
- package/dist/services/config-manager.cjs.map +1 -1
- package/dist/services/config-manager.d.cts +6 -1
- package/dist/services/config-manager.d.cts.map +1 -1
- package/dist/services/config-manager.d.ts +6 -1
- package/dist/services/config-manager.d.ts.map +1 -1
- package/dist/services/config-manager.js +24 -10
- package/dist/services/config-manager.js.map +1 -1
- package/dist/services/file-loader.cjs +3 -6
- package/dist/services/file-loader.cjs.map +1 -1
- package/dist/services/file-loader.d.cts.map +1 -1
- package/dist/services/file-loader.d.ts.map +1 -1
- package/dist/services/file-loader.js +3 -6
- package/dist/services/file-loader.js.map +1 -1
- package/dist/services/profiler/profile-filter.cjs +116 -0
- package/dist/services/profiler/profile-filter.cjs.map +1 -0
- package/dist/services/profiler/profile-filter.d.cts +20 -0
- package/dist/services/profiler/profile-filter.d.cts.map +1 -0
- package/dist/services/profiler/profile-filter.d.ts +20 -0
- package/dist/services/profiler/profile-filter.d.ts.map +1 -0
- package/dist/services/profiler/profile-filter.js +112 -0
- package/dist/services/profiler/profile-filter.js.map +1 -0
- package/dist/services/profiler/profile-parser.cjs +139 -0
- package/dist/services/profiler/profile-parser.cjs.map +1 -0
- package/dist/services/profiler/profile-parser.d.cts +18 -0
- package/dist/services/profiler/profile-parser.d.cts.map +1 -0
- package/dist/services/profiler/profile-parser.d.ts +18 -0
- package/dist/services/profiler/profile-parser.d.ts.map +1 -0
- package/dist/services/profiler/profile-parser.js +132 -0
- package/dist/services/profiler/profile-parser.js.map +1 -0
- package/dist/services/profiler/profile-runner.cjs +90 -0
- package/dist/services/profiler/profile-runner.cjs.map +1 -0
- package/dist/services/profiler/profile-runner.d.cts +29 -0
- package/dist/services/profiler/profile-runner.d.cts.map +1 -0
- package/dist/services/profiler/profile-runner.d.ts +29 -0
- package/dist/services/profiler/profile-runner.d.ts.map +1 -0
- package/dist/services/profiler/profile-runner.js +86 -0
- package/dist/services/profiler/profile-runner.js.map +1 -0
- package/dist/services/progress-manager.cjs +10 -2
- package/dist/services/progress-manager.cjs.map +1 -1
- package/dist/services/progress-manager.d.cts +2 -0
- package/dist/services/progress-manager.d.cts.map +1 -1
- package/dist/services/progress-manager.d.ts +2 -0
- package/dist/services/progress-manager.d.ts.map +1 -1
- package/dist/services/progress-manager.js +10 -2
- package/dist/services/progress-manager.js.map +1 -1
- package/dist/services/reporter-registry.cjs +18 -24
- package/dist/services/reporter-registry.cjs.map +1 -1
- package/dist/services/reporter-registry.d.cts +18 -40
- package/dist/services/reporter-registry.d.cts.map +1 -1
- package/dist/services/reporter-registry.d.ts +18 -40
- package/dist/services/reporter-registry.d.ts.map +1 -1
- package/dist/services/reporter-registry.js +18 -24
- package/dist/services/reporter-registry.js.map +1 -1
- package/dist/types/budgets.cjs +8 -0
- package/dist/types/budgets.cjs.map +1 -0
- package/dist/types/budgets.d.cts +149 -0
- package/dist/types/budgets.d.cts.map +1 -0
- package/dist/types/budgets.d.ts +149 -0
- package/dist/types/budgets.d.ts.map +1 -0
- package/dist/types/budgets.js +7 -0
- package/dist/types/budgets.js.map +1 -0
- package/dist/types/cli.cjs +2 -11
- package/dist/types/cli.cjs.map +1 -1
- package/dist/types/cli.d.cts +3 -227
- package/dist/types/cli.d.cts.map +1 -1
- package/dist/types/cli.d.ts +3 -227
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.js +2 -11
- package/dist/types/cli.js.map +1 -1
- package/dist/types/core.cjs +6 -1
- package/dist/types/core.cjs.map +1 -1
- package/dist/types/core.d.cts +15 -2
- package/dist/types/core.d.cts.map +1 -1
- package/dist/types/core.d.ts +15 -2
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/core.js +2 -1
- package/dist/types/core.js.map +1 -1
- package/dist/types/index.cjs +5 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +2 -0
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/interfaces.d.cts +19 -8
- package/dist/types/interfaces.d.cts.map +1 -1
- package/dist/types/interfaces.d.ts +19 -8
- package/dist/types/interfaces.d.ts.map +1 -1
- package/dist/types/profiler.cjs +11 -0
- package/dist/types/profiler.cjs.map +1 -0
- package/dist/types/profiler.d.cts +102 -0
- package/dist/types/profiler.d.cts.map +1 -0
- package/dist/types/profiler.d.ts +102 -0
- package/dist/types/profiler.d.ts.map +1 -0
- package/dist/types/profiler.js +10 -0
- package/dist/types/profiler.js.map +1 -0
- package/dist/types/utility.cjs.map +1 -1
- package/dist/types/utility.d.cts +0 -8
- package/dist/types/utility.d.cts.map +1 -1
- package/dist/types/utility.d.ts +0 -8
- package/dist/types/utility.d.ts.map +1 -1
- package/dist/types/utility.js.map +1 -1
- package/dist/utils/identifiers.cjs +32 -0
- package/dist/utils/identifiers.cjs.map +1 -0
- package/dist/utils/identifiers.d.cts +32 -0
- package/dist/utils/identifiers.d.cts.map +1 -0
- package/dist/utils/identifiers.d.ts +32 -0
- package/dist/utils/identifiers.d.ts.map +1 -0
- package/dist/utils/identifiers.js +27 -0
- package/dist/utils/identifiers.js.map +1 -0
- package/dist/utils/package.cjs +40 -0
- package/dist/utils/package.cjs.map +1 -0
- package/dist/utils/package.d.cts +15 -0
- package/dist/utils/package.d.cts.map +1 -0
- package/dist/utils/package.d.ts +15 -0
- package/dist/utils/package.d.ts.map +1 -0
- package/dist/utils/package.js +33 -0
- package/dist/utils/package.js.map +1 -0
- package/dist/utils/type-guards.cjs +48 -0
- package/dist/utils/type-guards.cjs.map +1 -0
- package/dist/utils/type-guards.d.cts +22 -0
- package/dist/utils/type-guards.d.cts.map +1 -0
- package/dist/utils/type-guards.d.ts +22 -0
- package/dist/utils/type-guards.d.ts.map +1 -0
- package/dist/utils/type-guards.js +43 -0
- package/dist/utils/type-guards.js.map +1 -0
- package/package.json +18 -19
- package/src/cli/commands/analyze.ts +101 -0
- package/src/cli/commands/baseline.ts +577 -0
- package/src/cli/commands/history.ts +1 -1
- package/src/cli/commands/init.ts +116 -194
- package/src/cli/commands/run.ts +183 -113
- package/src/cli/index.ts +425 -183
- package/src/config/budget-schema.ts +189 -0
- package/src/config/schema.ts +260 -1
- package/src/constants.ts +53 -1
- package/src/core/engine.ts +169 -22
- package/src/core/engines/accurate-engine.ts +195 -44
- package/src/core/engines/tinybench-engine.ts +3 -2
- package/src/core/output-path-resolver.ts +10 -2
- package/src/errors/base.ts +11 -2
- package/src/errors/budget.ts +38 -0
- package/src/errors/index.ts +3 -0
- package/src/index.ts +9 -0
- package/src/reporters/csv.ts +54 -25
- package/src/reporters/human.ts +434 -115
- package/src/reporters/json.ts +26 -71
- package/src/reporters/profile-human.ts +210 -0
- package/src/reporters/simple.ts +88 -54
- package/src/services/baseline-storage.ts +199 -0
- package/src/services/budget-evaluator.ts +182 -0
- package/src/services/config-manager.ts +24 -9
- package/src/services/file-loader.ts +3 -6
- package/src/services/profiler/profile-filter.ts +147 -0
- package/src/services/profiler/profile-parser.ts +194 -0
- package/src/services/profiler/profile-runner.ts +121 -0
- package/src/services/progress-manager.ts +12 -2
- package/src/services/reporter-registry.ts +46 -81
- package/src/types/budgets.ts +180 -0
- package/src/types/cli.ts +5 -238
- package/src/types/core.ts +52 -10
- package/src/types/index.ts +5 -0
- package/src/types/interfaces.ts +24 -6
- package/src/types/profiler.ts +135 -0
- package/src/types/utility.ts +0 -10
- package/src/utils/identifiers.ts +58 -0
- package/src/utils/package.ts +35 -0
- package/src/utils/type-guards.ts +51 -0
package/src/reporters/human.ts
CHANGED
|
@@ -9,6 +9,7 @@ import path from 'node:path';
|
|
|
9
9
|
|
|
10
10
|
import type {
|
|
11
11
|
BenchmarkRun,
|
|
12
|
+
BudgetSummary,
|
|
12
13
|
FileResult,
|
|
13
14
|
ProgressState,
|
|
14
15
|
SuiteResult,
|
|
@@ -26,6 +27,8 @@ export class HumanReporter extends BaseReporter {
|
|
|
26
27
|
|
|
27
28
|
private currentSuite = '';
|
|
28
29
|
|
|
30
|
+
private currentSuiteMaxNameLen = 0; // Track max name length for current suite alignment
|
|
31
|
+
|
|
29
32
|
private failures: Array<{
|
|
30
33
|
error: string;
|
|
31
34
|
file: string;
|
|
@@ -35,6 +38,8 @@ export class HumanReporter extends BaseReporter {
|
|
|
35
38
|
|
|
36
39
|
private lastProgressLine = '';
|
|
37
40
|
|
|
41
|
+
private maxTimePadWidth = 0; // Track maximum time padding width to prevent jitter
|
|
42
|
+
|
|
38
43
|
private progressWindowActive = false; // Track if progress window is rendered
|
|
39
44
|
|
|
40
45
|
private readonly quiet: boolean;
|
|
@@ -71,6 +76,84 @@ export class HumanReporter extends BaseReporter {
|
|
|
71
76
|
this.showProgress = options.progress ?? true;
|
|
72
77
|
}
|
|
73
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Format bytes in human-readable format
|
|
81
|
+
*/
|
|
82
|
+
private static formatBytes(this: void, bytes: number): string {
|
|
83
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
84
|
+
let size = bytes;
|
|
85
|
+
let unitIndex = 0;
|
|
86
|
+
|
|
87
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
88
|
+
size /= 1024;
|
|
89
|
+
unitIndex++;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Format file path - show relative path if within CWD, otherwise absolute
|
|
97
|
+
*/
|
|
98
|
+
private static formatPath(this: void, filePath: string): string {
|
|
99
|
+
const cwd = process.cwd();
|
|
100
|
+
const absolutePath = path.resolve(filePath);
|
|
101
|
+
|
|
102
|
+
// Check if the file is within the current working directory
|
|
103
|
+
if (absolutePath.startsWith(cwd + path.sep) || absolutePath === cwd) {
|
|
104
|
+
return path.relative(cwd, absolutePath);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return absolutePath;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Simple pluralization helper
|
|
112
|
+
*/
|
|
113
|
+
private static pluralize(this: void, str: string, count: number): string {
|
|
114
|
+
return count === 1 ? str : `${str}s`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
onBudgetResult(summary: BudgetSummary): void {
|
|
118
|
+
if (summary.total === 0 || this.quiet) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.clearProgress();
|
|
123
|
+
|
|
124
|
+
this.printLine();
|
|
125
|
+
const budgetHeader = `${this.colorize('magenta', ansiChars.block.full.repeat(2))} ${this.colorize('brightWhite', this.colorize('bold', 'Performance Budgets'))}`;
|
|
126
|
+
this.printLine(budgetHeader);
|
|
127
|
+
this.printLine();
|
|
128
|
+
|
|
129
|
+
for (const result of summary.results) {
|
|
130
|
+
const icon = result.passed ? ansiChars.checkmark : ansiChars.cross;
|
|
131
|
+
const iconColor = result.passed ? 'brightCyan' : 'brightRed';
|
|
132
|
+
|
|
133
|
+
this.printLine(
|
|
134
|
+
` ${this.colorize(iconColor, icon)} ${this.colorize('white', result.taskId)}`,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (!result.passed && result.violations.length > 0) {
|
|
138
|
+
for (const violation of result.violations) {
|
|
139
|
+
this.printLine(
|
|
140
|
+
` ${this.colorize('brightRed', violation.message)}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.printLine();
|
|
147
|
+
|
|
148
|
+
const statusText =
|
|
149
|
+
summary.failed === 0
|
|
150
|
+
? `${this.colorize('brightCyan', ansiChars.checkmark)} All ${summary.total} budget(s) passed`
|
|
151
|
+
: `${this.colorize('brightRed', ansiChars.cross)} ${summary.failed} of ${summary.total} budget(s) failed`;
|
|
152
|
+
|
|
153
|
+
this.printLine(` ${statusText}`);
|
|
154
|
+
this.printLine();
|
|
155
|
+
}
|
|
156
|
+
|
|
74
157
|
onEnd(run: BenchmarkRun): void {
|
|
75
158
|
if (this.quiet) {
|
|
76
159
|
return;
|
|
@@ -85,12 +168,16 @@ export class HumanReporter extends BaseReporter {
|
|
|
85
168
|
let totalSuites = 0;
|
|
86
169
|
let totalPassed = 0;
|
|
87
170
|
let totalFailed = 0;
|
|
171
|
+
let totalAborted = 0;
|
|
88
172
|
|
|
89
173
|
for (const file of run.files) {
|
|
90
174
|
totalSuites += file.suites.length;
|
|
91
175
|
for (const suite of file.suites) {
|
|
92
|
-
totalPassed += suite.tasks.filter(
|
|
176
|
+
totalPassed += suite.tasks.filter(
|
|
177
|
+
(t: TaskResult) => !t.error && !t.aborted,
|
|
178
|
+
).length;
|
|
93
179
|
totalFailed += suite.tasks.filter((t: TaskResult) => t.error).length;
|
|
180
|
+
totalAborted += suite.tasks.filter((t: TaskResult) => t.aborted).length;
|
|
94
181
|
}
|
|
95
182
|
}
|
|
96
183
|
|
|
@@ -106,22 +193,31 @@ export class HumanReporter extends BaseReporter {
|
|
|
106
193
|
`${this.colorize('brightBlue', ' Suites:')} ${this.colorize('brightWhite', String(totalSuites))}`,
|
|
107
194
|
);
|
|
108
195
|
this.printLine(
|
|
109
|
-
`${this.colorize('brightBlue', ' Tasks:')} ${this.colorize('brightWhite', String(totalPassed + totalFailed))}`,
|
|
196
|
+
`${this.colorize('brightBlue', ' Tasks:')} ${this.colorize('brightWhite', String(totalPassed + totalFailed + totalAborted))}`,
|
|
110
197
|
);
|
|
111
|
-
if (totalFailed > 0) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
)
|
|
198
|
+
if (totalFailed > 0 || totalAborted > 0) {
|
|
199
|
+
if (totalFailed > 0) {
|
|
200
|
+
this.printLine(
|
|
201
|
+
`${this.colorize('brightRed', ansiChars.cross + ' Failed:')} ${this.colorize('brightWhite', String(totalFailed))}`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if (totalPassed > 0) {
|
|
205
|
+
this.printLine(
|
|
206
|
+
`${this.colorize('brightCyan', ansiChars.checkmark + ' Passed:')} ${this.colorize('brightWhite', String(totalPassed))}`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if (totalAborted > 0) {
|
|
210
|
+
this.printLine(
|
|
211
|
+
`${this.colorize('brightYellow', ansiChars.approx + ' Aborted:')} ${this.colorize('brightWhite', String(totalAborted))}`,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
118
214
|
} else {
|
|
119
215
|
this.printLine(
|
|
120
216
|
`${this.colorize('brightCyan', ansiChars.checkmark + ' All tasks passed:')} ${this.colorize('brightWhite', String(totalPassed))}`,
|
|
121
217
|
);
|
|
122
218
|
}
|
|
123
219
|
this.printLine(
|
|
124
|
-
`${this.colorize('cyan', ansiChars.approx + ' Duration:')} ${this.colorize('brightWhite',
|
|
220
|
+
`${this.colorize('cyan', ansiChars.approx + ' Duration:')} ${this.colorize('brightWhite', BaseReporter.formatDuration(duration * 1000000))}`,
|
|
125
221
|
);
|
|
126
222
|
this.printLine();
|
|
127
223
|
|
|
@@ -135,7 +231,7 @@ export class HumanReporter extends BaseReporter {
|
|
|
135
231
|
this.printLine();
|
|
136
232
|
|
|
137
233
|
for (const failure of this.failures) {
|
|
138
|
-
const displayPath =
|
|
234
|
+
const displayPath = HumanReporter.formatPath(failure.file);
|
|
139
235
|
this.printLine(
|
|
140
236
|
` ${this.colorize('dim', displayPath)} ${this.colorize('dim', '›')} ${this.colorize('white', failure.suite)} ${this.colorize('dim', '›')} ${this.colorize('brightWhite', failure.task)}`,
|
|
141
237
|
);
|
|
@@ -143,7 +239,8 @@ export class HumanReporter extends BaseReporter {
|
|
|
143
239
|
this.printLine();
|
|
144
240
|
}
|
|
145
241
|
}
|
|
146
|
-
} else {
|
|
242
|
+
} else if (totalAborted === 0) {
|
|
243
|
+
// Only show "Rad" if no failures AND no aborts
|
|
147
244
|
const successMessage = `${this.colorize('brightMagenta', 'Rad. ☮')}`;
|
|
148
245
|
this.printLine(successMessage);
|
|
149
246
|
}
|
|
@@ -189,7 +286,7 @@ export class HumanReporter extends BaseReporter {
|
|
|
189
286
|
);
|
|
190
287
|
} else {
|
|
191
288
|
this.printLine(
|
|
192
|
-
` ${this.colorize('magenta', ansiChars.checkmark)} ${totalPassed > 1 ? this.colorize('brightMagenta', 'All ') : ''}${this.colorize('bold', this.colorize('brightMagenta', `${totalPassed}`))} ${this.colorize('brightMagenta', `${
|
|
289
|
+
` ${this.colorize('magenta', ansiChars.checkmark)} ${totalPassed > 1 ? this.colorize('brightMagenta', 'All ') : ''}${this.colorize('bold', this.colorize('brightMagenta', `${totalPassed}`))} ${this.colorize('brightMagenta', `${HumanReporter.pluralize('task', totalPassed)} passed`)}`,
|
|
193
290
|
);
|
|
194
291
|
}
|
|
195
292
|
|
|
@@ -203,7 +300,7 @@ export class HumanReporter extends BaseReporter {
|
|
|
203
300
|
return;
|
|
204
301
|
}
|
|
205
302
|
|
|
206
|
-
const displayPath =
|
|
303
|
+
const displayPath = HumanReporter.formatPath(file);
|
|
207
304
|
const fileMarker = `${colors.magenta}${ansiChars.block.dark}${ansiChars.block.dark}${colors.reset}`;
|
|
208
305
|
this.printLine(
|
|
209
306
|
`${fileMarker} ${colors.underline}${this.colorize('brightMagenta', this.colorize('bold', displayPath))}${colors.reset}`,
|
|
@@ -221,7 +318,8 @@ export class HumanReporter extends BaseReporter {
|
|
|
221
318
|
return;
|
|
222
319
|
}
|
|
223
320
|
|
|
224
|
-
const { elapsed, percentage, tasksCompleted, totalTasks } =
|
|
321
|
+
const { currentTask, elapsed, percentage, tasksCompleted, totalTasks } =
|
|
322
|
+
state;
|
|
225
323
|
|
|
226
324
|
// Pad task counts for alignment
|
|
227
325
|
const totalTasksWidth = String(totalTasks).length;
|
|
@@ -236,7 +334,7 @@ export class HumanReporter extends BaseReporter {
|
|
|
236
334
|
|
|
237
335
|
// Calculate ETA if we have completed tasks and determine padding width
|
|
238
336
|
let etaStr = '';
|
|
239
|
-
let padWidth = elapsedStrRaw.length;
|
|
337
|
+
let padWidth = Math.max(this.maxTimePadWidth, elapsedStrRaw.length);
|
|
240
338
|
if (tasksCompleted > 0) {
|
|
241
339
|
const avgTimePerTask = elapsed / tasksCompleted;
|
|
242
340
|
const remainingTasks = totalTasks - tasksCompleted;
|
|
@@ -244,14 +342,27 @@ export class HumanReporter extends BaseReporter {
|
|
|
244
342
|
const etaSeconds = Math.round(etaMs / 1000);
|
|
245
343
|
const etaTimeStr = this.formatTimeRemaining(etaSeconds);
|
|
246
344
|
padWidth = Math.max(padWidth, etaTimeStr.length);
|
|
247
|
-
etaStr = ` ${this.colorize('
|
|
345
|
+
etaStr = ` ${this.colorize('gray', '|')} ${this.colorize('gray', 'ETA:')} ${this.colorize('brightBlue', etaTimeStr)}`;
|
|
248
346
|
}
|
|
249
347
|
|
|
348
|
+
// Remember the maximum width we've ever used to prevent jitter
|
|
349
|
+
this.maxTimePadWidth = Math.max(this.maxTimePadWidth, padWidth);
|
|
350
|
+
|
|
250
351
|
// Pad elapsed time to match the longest time string
|
|
251
|
-
const elapsedStr = elapsedStrRaw.padStart(
|
|
352
|
+
const elapsedStr = elapsedStrRaw.padStart(this.maxTimePadWidth, ' ');
|
|
252
353
|
|
|
253
354
|
const roundedPercentage = percentage.toFixed(2);
|
|
254
|
-
|
|
355
|
+
|
|
356
|
+
// Build progress line with current task if available
|
|
357
|
+
let line = `${this.colorize('brightCyan', ansiChars.approx)} ${this.colorize('white', paddedTasksCompleted)}${this.colorize('gray', '/')}${this.colorize('white', String(totalTasks))} ${this.colorize('gray', 'tasks')} ${this.colorize('gray', '(')}${this.colorize('brightBlue', roundedPercentage + '%')}${this.colorize('gray', ')')} ${this.colorize('gray', '|')} ${this.colorize('gray', 'Elapsed:')} ${this.colorize('cyan', elapsedStr)}${etaStr}`;
|
|
358
|
+
|
|
359
|
+
if (currentTask) {
|
|
360
|
+
const truncatedTask =
|
|
361
|
+
currentTask.length > 60
|
|
362
|
+
? currentTask.substring(0, 57) + '...'
|
|
363
|
+
: currentTask;
|
|
364
|
+
line += ` ${this.colorize('gray', '|')} ${this.colorize('white', truncatedTask)}`;
|
|
365
|
+
}
|
|
255
366
|
|
|
256
367
|
this.lastProgressLine = line;
|
|
257
368
|
this.renderProgressWindow();
|
|
@@ -261,6 +372,7 @@ export class HumanReporter extends BaseReporter {
|
|
|
261
372
|
this.startTime = Date.now();
|
|
262
373
|
this.failures = []; // Reset failures for new run
|
|
263
374
|
this.lastProgressLine = ''; // Reset for new run
|
|
375
|
+
this.maxTimePadWidth = 0; // Reset time padding width for new run
|
|
264
376
|
|
|
265
377
|
if (this.quiet) {
|
|
266
378
|
return;
|
|
@@ -278,7 +390,7 @@ export class HumanReporter extends BaseReporter {
|
|
|
278
390
|
\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
|
|
279
391
|
\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
|
|
280
392
|
\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[2m) \x1b[m
|
|
281
|
-
\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${
|
|
393
|
+
\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${HumanReporter.formatBytes(run.environment.memory.total)} \x1b[m
|
|
282
394
|
\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
|
|
283
395
|
\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
|
|
284
396
|
`;
|
|
@@ -316,29 +428,70 @@ export class HumanReporter extends BaseReporter {
|
|
|
316
428
|
return;
|
|
317
429
|
}
|
|
318
430
|
|
|
319
|
-
//
|
|
320
|
-
this.printAlignedSuiteResults();
|
|
431
|
+
// Tasks are printed immediately in onTaskResult, so just print suite summary
|
|
321
432
|
|
|
322
433
|
// Skip displaying summary for the implicit "default" suite
|
|
323
434
|
if (result.name === 'default') {
|
|
324
435
|
return;
|
|
325
436
|
}
|
|
326
437
|
|
|
327
|
-
const passed = result.tasks.filter((t) => !t.error).length;
|
|
438
|
+
const passed = result.tasks.filter((t) => !t.error && !t.aborted).length;
|
|
328
439
|
const failed = result.tasks.filter((t) => t.error).length;
|
|
440
|
+
const aborted = result.tasks.filter((t) => t.aborted).length;
|
|
441
|
+
const durationStr = BaseReporter.formatDuration(result.duration * 1000000); // ms to ns
|
|
442
|
+
|
|
443
|
+
// Build summary parts
|
|
444
|
+
const parts: string[] = [];
|
|
329
445
|
|
|
330
446
|
if (failed > 0) {
|
|
331
|
-
this.
|
|
332
|
-
|
|
333
|
-
|
|
447
|
+
parts.push(this.colorize('red', `${ansiChars.cross} ${failed} failed`));
|
|
448
|
+
}
|
|
449
|
+
if (passed > 0) {
|
|
450
|
+
parts.push(this.colorize('green', `${passed} passed`));
|
|
451
|
+
}
|
|
452
|
+
if (aborted > 0) {
|
|
453
|
+
parts.push(this.colorize('brightYellow', `${aborted} aborted`));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const summary = parts.join(', ');
|
|
457
|
+
const timeInfo = `${this.colorize('gray', 'in')} ${this.colorize('cyan', durationStr)}`;
|
|
458
|
+
|
|
459
|
+
if (failed > 0 || aborted > 0) {
|
|
460
|
+
this.printLine(` ${summary} ${timeInfo}`);
|
|
334
461
|
} else {
|
|
335
462
|
this.printLine(
|
|
336
|
-
` ${this.colorize('magenta', ansiChars.checkmark)} ${this.colorize('bold', this.colorize('brightWhite', `${passed}`))} ${this.colorize('brightWhite', `${
|
|
463
|
+
` ${this.colorize('magenta', ansiChars.checkmark)} ${this.colorize('bold', this.colorize('brightWhite', `${passed}`))} ${this.colorize('brightWhite', `${HumanReporter.pluralize('task', passed)} passed`)} ${timeInfo}`,
|
|
337
464
|
);
|
|
338
465
|
}
|
|
339
466
|
this.printLine();
|
|
340
467
|
}
|
|
341
468
|
|
|
469
|
+
onSuiteInit(suite: string, taskNames: readonly string[]): void {
|
|
470
|
+
// Pre-calculate max name length for optimal alignment
|
|
471
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
472
|
+
const STATS_RESERVED_WIDTH = 70;
|
|
473
|
+
const MAX_NAME_WIDTH = Math.max(
|
|
474
|
+
40,
|
|
475
|
+
Math.min(
|
|
476
|
+
60,
|
|
477
|
+
terminalWidth - 4 - 2 - 2 - STATS_RESERVED_WIDTH, // BASE_INDENT(4) + status(1) + space(1) + ": "(2)
|
|
478
|
+
),
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
// Calculate the actual max name length from non-wrapped names
|
|
482
|
+
let maxLen = 0;
|
|
483
|
+
for (const name of taskNames) {
|
|
484
|
+
const nameLen = this.getVisibleLength(name.trim());
|
|
485
|
+
// Only count names that won't wrap
|
|
486
|
+
if (nameLen <= MAX_NAME_WIDTH) {
|
|
487
|
+
maxLen = Math.max(maxLen, nameLen);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Use the max of actual names or MAX_NAME_WIDTH for consistency
|
|
492
|
+
this.currentSuiteMaxNameLen = Math.max(maxLen, MAX_NAME_WIDTH);
|
|
493
|
+
}
|
|
494
|
+
|
|
342
495
|
onSuiteStart(suite: string): void {
|
|
343
496
|
this.currentSuite = suite;
|
|
344
497
|
|
|
@@ -365,8 +518,16 @@ export class HumanReporter extends BaseReporter {
|
|
|
365
518
|
return;
|
|
366
519
|
}
|
|
367
520
|
|
|
368
|
-
//
|
|
521
|
+
// Always buffer the result for suite summary (including aborted tasks)
|
|
369
522
|
this.suiteResults.push(result);
|
|
523
|
+
|
|
524
|
+
// Skip printing aborted tasks (they're counted in summary but not shown individually)
|
|
525
|
+
if (result.aborted) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Print immediately with current alignment
|
|
530
|
+
this.printTaskResult(result);
|
|
370
531
|
}
|
|
371
532
|
|
|
372
533
|
onTaskStart(task: string): void {
|
|
@@ -414,37 +575,6 @@ export class HumanReporter extends BaseReporter {
|
|
|
414
575
|
return `${colors[color]}${text}${colors.reset}`;
|
|
415
576
|
}
|
|
416
577
|
|
|
417
|
-
/**
|
|
418
|
-
* Format bytes in human-readable format
|
|
419
|
-
*/
|
|
420
|
-
private formatBytes(bytes: number): string {
|
|
421
|
-
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
422
|
-
let size = bytes;
|
|
423
|
-
let unitIndex = 0;
|
|
424
|
-
|
|
425
|
-
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
426
|
-
size /= 1024;
|
|
427
|
-
unitIndex++;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Format file path - show relative path if within CWD, otherwise absolute
|
|
435
|
-
*/
|
|
436
|
-
private formatPath(filePath: string): string {
|
|
437
|
-
const cwd = process.cwd();
|
|
438
|
-
const absolutePath = path.resolve(filePath);
|
|
439
|
-
|
|
440
|
-
// Check if the file is within the current working directory
|
|
441
|
-
if (absolutePath.startsWith(cwd + path.sep) || absolutePath === cwd) {
|
|
442
|
-
return path.relative(cwd, absolutePath);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
return absolutePath;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
578
|
/**
|
|
449
579
|
* Format duration in human-readable format for progress display
|
|
450
580
|
*/
|
|
@@ -471,13 +601,6 @@ export class HumanReporter extends BaseReporter {
|
|
|
471
601
|
return str.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
472
602
|
}
|
|
473
603
|
|
|
474
|
-
/**
|
|
475
|
-
* Simple pluralization helper
|
|
476
|
-
*/
|
|
477
|
-
private pluralize(str: string, count: number): string {
|
|
478
|
-
return count === 1 ? str : `${str}s`;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
604
|
/**
|
|
482
605
|
* Print all task results in a suite with aligned columns
|
|
483
606
|
*/
|
|
@@ -486,13 +609,24 @@ export class HumanReporter extends BaseReporter {
|
|
|
486
609
|
return;
|
|
487
610
|
}
|
|
488
611
|
|
|
489
|
-
const MAX_NAME_WIDTH = 60;
|
|
490
612
|
const BASE_INDENT = ' '; // 4 spaces
|
|
491
613
|
const bullet = this.colorize(
|
|
492
614
|
'dim',
|
|
493
615
|
this.colorize('gray', ansiChars.bullet),
|
|
494
616
|
);
|
|
495
617
|
|
|
618
|
+
// Calculate maximum name width based on terminal width
|
|
619
|
+
// Reserve space for: indent (4) + status (1) + space (1) + name + ": " (2) + stats (~60 chars)
|
|
620
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
621
|
+
const STATS_RESERVED_WIDTH = 70; // Approx space for duration + rme + ops/sec with padding
|
|
622
|
+
const MAX_NAME_WIDTH = Math.max(
|
|
623
|
+
40,
|
|
624
|
+
Math.min(
|
|
625
|
+
60,
|
|
626
|
+
terminalWidth - BASE_INDENT.length - 2 - 2 - STATS_RESERVED_WIDTH,
|
|
627
|
+
),
|
|
628
|
+
);
|
|
629
|
+
|
|
496
630
|
// Prepare formatted data for each task
|
|
497
631
|
interface FormattedTask {
|
|
498
632
|
durationLen: number;
|
|
@@ -509,49 +643,52 @@ export class HumanReporter extends BaseReporter {
|
|
|
509
643
|
status: string;
|
|
510
644
|
}
|
|
511
645
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
646
|
+
// Filter out aborted tasks (they're counted in suite summary but not printed)
|
|
647
|
+
const formatted: FormattedTask[] = this.suiteResults
|
|
648
|
+
.filter((result) => !result.aborted)
|
|
649
|
+
.map((result) => {
|
|
650
|
+
const status = result.error
|
|
651
|
+
? this.colorize('red', ansiChars.cross)
|
|
652
|
+
: this.colorize('brightCyan', ansiChars.checkmark);
|
|
653
|
+
|
|
654
|
+
const name = result.name.trim();
|
|
655
|
+
const nameLength = this.getVisibleLength(name);
|
|
656
|
+
|
|
657
|
+
if (result.error) {
|
|
658
|
+
return {
|
|
659
|
+
durationLen: 0,
|
|
660
|
+
durationStr: '',
|
|
661
|
+
error: true,
|
|
662
|
+
errorMessage: result.error?.message || String(result.error),
|
|
663
|
+
iterations: 0,
|
|
664
|
+
name,
|
|
665
|
+
nameLength,
|
|
666
|
+
opsPerSecLen: 0,
|
|
667
|
+
opsPerSecStr: '',
|
|
668
|
+
rmeLen: 0,
|
|
669
|
+
rmeStr: '',
|
|
670
|
+
status,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
516
673
|
|
|
517
|
-
|
|
518
|
-
|
|
674
|
+
const duration = BaseReporter.formatDuration(result.mean); // already in nanoseconds
|
|
675
|
+
const opsPerSec = BaseReporter.formatOpsPerSecond(result.opsPerSecond);
|
|
676
|
+
const rme = BaseReporter.formatPercentage(result.marginOfError); // already a percentage
|
|
519
677
|
|
|
520
|
-
if (result.error) {
|
|
521
678
|
return {
|
|
522
|
-
durationLen:
|
|
523
|
-
durationStr:
|
|
524
|
-
error:
|
|
525
|
-
|
|
526
|
-
iterations: 0,
|
|
679
|
+
durationLen: this.getVisibleLength(duration),
|
|
680
|
+
durationStr: duration,
|
|
681
|
+
error: false,
|
|
682
|
+
iterations: result.iterations,
|
|
527
683
|
name,
|
|
528
684
|
nameLength,
|
|
529
|
-
opsPerSecLen:
|
|
530
|
-
opsPerSecStr:
|
|
531
|
-
rmeLen:
|
|
532
|
-
rmeStr:
|
|
685
|
+
opsPerSecLen: this.getVisibleLength(opsPerSec),
|
|
686
|
+
opsPerSecStr: opsPerSec,
|
|
687
|
+
rmeLen: this.getVisibleLength(rme),
|
|
688
|
+
rmeStr: rme,
|
|
533
689
|
status,
|
|
534
690
|
};
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const duration = this.formatDuration(result.mean); // already in nanoseconds
|
|
538
|
-
const opsPerSec = this.formatOpsPerSecond(result.opsPerSecond);
|
|
539
|
-
const rme = this.formatPercentage(result.marginOfError * 100);
|
|
540
|
-
|
|
541
|
-
return {
|
|
542
|
-
durationLen: this.getVisibleLength(duration),
|
|
543
|
-
durationStr: duration,
|
|
544
|
-
error: false,
|
|
545
|
-
iterations: result.iterations,
|
|
546
|
-
name,
|
|
547
|
-
nameLength,
|
|
548
|
-
opsPerSecLen: this.getVisibleLength(opsPerSec),
|
|
549
|
-
opsPerSecStr: opsPerSec,
|
|
550
|
-
rmeLen: this.getVisibleLength(rme),
|
|
551
|
-
rmeStr: rme,
|
|
552
|
-
status,
|
|
553
|
-
};
|
|
554
|
-
});
|
|
691
|
+
});
|
|
555
692
|
|
|
556
693
|
// Find max widths
|
|
557
694
|
const nonWrappingTasks = formatted.filter(
|
|
@@ -575,10 +712,6 @@ export class HumanReporter extends BaseReporter {
|
|
|
575
712
|
0,
|
|
576
713
|
);
|
|
577
714
|
|
|
578
|
-
// Calculate the position where numbers start for unwrapped lines
|
|
579
|
-
// BASE_INDENT (4) + status (1 char) + space (1) + maxNameLen + ": " (2) = 8 + maxNameLen
|
|
580
|
-
const numbersStartPos = BASE_INDENT.length + 2 + maxNameLen + 2;
|
|
581
|
-
|
|
582
715
|
// Print each task with aligned columns
|
|
583
716
|
for (const task of formatted) {
|
|
584
717
|
if (task.error) {
|
|
@@ -594,22 +727,45 @@ export class HumanReporter extends BaseReporter {
|
|
|
594
727
|
`${BASE_INDENT}${task.status} ${this.colorize('white', task.name)} ${this.colorize('red', 'FAILED')}`,
|
|
595
728
|
);
|
|
596
729
|
} else if (task.nameLength > MAX_NAME_WIDTH) {
|
|
597
|
-
// Long name - wrap to
|
|
598
|
-
this.
|
|
599
|
-
|
|
600
|
-
);
|
|
730
|
+
// Long name - wrap to multiple lines, align last line with short names
|
|
731
|
+
const wrappedLines = this.wrapText(task.name, MAX_NAME_WIDTH);
|
|
732
|
+
const continueIndent = BASE_INDENT + ' '; // 6 spaces for continuation lines
|
|
601
733
|
|
|
602
|
-
//
|
|
603
|
-
// We need to get to numbersStartPos from the beginning of the line
|
|
604
|
-
const leadingPad = ' '.repeat(numbersStartPos);
|
|
734
|
+
// Format stats string
|
|
605
735
|
const durationPad = ' '.repeat(maxDurationLen - task.durationLen);
|
|
606
736
|
const rmePad = ' '.repeat(maxRmeLen - task.rmeLen);
|
|
607
737
|
const opsPad = ' '.repeat(maxOpsLen - task.opsPerSecLen);
|
|
738
|
+
const statsStr = `${durationPad}${this.colorize('cyan', task.durationStr)} ${bullet} ${ansiChars.plusMinus}${rmePad}${this.colorize('brightBlue', task.rmeStr)} ${bullet} ${opsPad}${this.colorize('magenta', task.opsPerSecStr)}`;
|
|
608
739
|
|
|
740
|
+
// Print first line with status
|
|
609
741
|
this.printLine(
|
|
610
|
-
`${
|
|
742
|
+
`${BASE_INDENT}${task.status} ${this.colorize('white', wrappedLines[0]!)}`,
|
|
611
743
|
);
|
|
612
744
|
|
|
745
|
+
// Print middle continuation lines (all but first and last)
|
|
746
|
+
for (let i = 1; i < wrappedLines.length - 1; i++) {
|
|
747
|
+
this.printLine(
|
|
748
|
+
`${continueIndent}${this.colorize('white', wrappedLines[i]!)}`,
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Print last line with colon and stats aligned with short names
|
|
753
|
+
if (wrappedLines.length > 1) {
|
|
754
|
+
const lastLine = wrappedLines[wrappedLines.length - 1]!;
|
|
755
|
+
const lastLineLen = this.getVisibleLength(lastLine);
|
|
756
|
+
// Pad the last line to align the ':' with short names
|
|
757
|
+
const lastLinePad = ' '.repeat(Math.max(0, maxNameLen - lastLineLen));
|
|
758
|
+
this.printLine(
|
|
759
|
+
`${continueIndent}${this.colorize('white', lastLine)}${lastLinePad}: ${statsStr}`,
|
|
760
|
+
);
|
|
761
|
+
} else {
|
|
762
|
+
// Single wrapped line
|
|
763
|
+
const lastLinePad = ' '.repeat(maxNameLen - task.nameLength);
|
|
764
|
+
this.printLine(
|
|
765
|
+
`${BASE_INDENT}${task.status} ${this.colorize('white', task.name)}${lastLinePad}: ${statsStr}`,
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
|
|
613
769
|
if (this.verbose && task.iterations > 0) {
|
|
614
770
|
this.printLine(
|
|
615
771
|
` ${this.colorize('dim', `${task.iterations} iterations`)}`,
|
|
@@ -646,6 +802,128 @@ export class HumanReporter extends BaseReporter {
|
|
|
646
802
|
this.renderProgressWindow();
|
|
647
803
|
}
|
|
648
804
|
|
|
805
|
+
/**
|
|
806
|
+
* Print a single task result immediately with current alignment
|
|
807
|
+
*/
|
|
808
|
+
private printTaskResult(result: TaskResult): void {
|
|
809
|
+
// Clear progress bar temporarily
|
|
810
|
+
this.clearProgress();
|
|
811
|
+
|
|
812
|
+
const BASE_INDENT = ' '; // 4 spaces
|
|
813
|
+
const bullet = this.colorize(
|
|
814
|
+
'dim',
|
|
815
|
+
this.colorize('gray', ansiChars.bullet),
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
// Calculate terminal width constraints
|
|
819
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
820
|
+
const STATS_RESERVED_WIDTH = 70;
|
|
821
|
+
const MAX_NAME_WIDTH = Math.max(
|
|
822
|
+
40,
|
|
823
|
+
Math.min(
|
|
824
|
+
60,
|
|
825
|
+
terminalWidth - BASE_INDENT.length - 2 - 2 - STATS_RESERVED_WIDTH,
|
|
826
|
+
),
|
|
827
|
+
);
|
|
828
|
+
|
|
829
|
+
// Status marker
|
|
830
|
+
const status = result.error
|
|
831
|
+
? this.colorize('red', ansiChars.cross)
|
|
832
|
+
: this.colorize('brightCyan', ansiChars.checkmark);
|
|
833
|
+
|
|
834
|
+
const name = result.name.trim();
|
|
835
|
+
const nameLength = this.getVisibleLength(name);
|
|
836
|
+
|
|
837
|
+
// Handle errors
|
|
838
|
+
if (result.error) {
|
|
839
|
+
this.failures.push({
|
|
840
|
+
error: result.error?.message || String(result.error),
|
|
841
|
+
file: this.currentFile,
|
|
842
|
+
suite: this.currentSuite,
|
|
843
|
+
task: name,
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
this.printLine(
|
|
847
|
+
`${BASE_INDENT}${status} ${this.colorize('white', name)} ${this.colorize('red', 'FAILED')}`,
|
|
848
|
+
);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Format stats
|
|
853
|
+
const duration = BaseReporter.formatDuration(result.mean);
|
|
854
|
+
const opsPerSec = BaseReporter.formatOpsPerSecond(result.opsPerSecond);
|
|
855
|
+
const rme = BaseReporter.formatPercentage(result.marginOfError);
|
|
856
|
+
|
|
857
|
+
// Use fixed widths for stats columns (reasonable maximums)
|
|
858
|
+
const DURATION_WIDTH = 10; // "999.99ms" max
|
|
859
|
+
const RME_WIDTH = 8; // "±999.99%" max
|
|
860
|
+
const OPS_WIDTH = 15; // "999.99K ops/sec" max
|
|
861
|
+
|
|
862
|
+
const durationLen = this.getVisibleLength(duration);
|
|
863
|
+
const rmeLen = this.getVisibleLength(rme);
|
|
864
|
+
const opsLen = this.getVisibleLength(opsPerSec);
|
|
865
|
+
|
|
866
|
+
// Stats formatting with fixed widths
|
|
867
|
+
const durationPad = ' '.repeat(DURATION_WIDTH - durationLen);
|
|
868
|
+
const rmePad = ' '.repeat(RME_WIDTH - rmeLen);
|
|
869
|
+
const opsPad = ' '.repeat(OPS_WIDTH - opsLen);
|
|
870
|
+
const statsStr = `${durationPad}${this.colorize('cyan', duration)} ${bullet} ${ansiChars.plusMinus}${rmePad}${this.colorize('brightBlue', rme)} ${bullet} ${opsPad}${this.colorize('magenta', opsPerSec)}`;
|
|
871
|
+
|
|
872
|
+
// Handle long names (wrap)
|
|
873
|
+
if (nameLength > MAX_NAME_WIDTH) {
|
|
874
|
+
const wrappedLines = this.wrapText(name, MAX_NAME_WIDTH);
|
|
875
|
+
const continueIndent = BASE_INDENT + ' '; // 6 spaces for continuation lines
|
|
876
|
+
|
|
877
|
+
// Print first line with status
|
|
878
|
+
this.printLine(
|
|
879
|
+
`${BASE_INDENT}${status} ${this.colorize('white', wrappedLines[0]!)}`,
|
|
880
|
+
);
|
|
881
|
+
|
|
882
|
+
// Print middle lines (all but first and last)
|
|
883
|
+
for (let i = 1; i < wrappedLines.length - 1; i++) {
|
|
884
|
+
this.printLine(
|
|
885
|
+
`${continueIndent}${this.colorize('white', wrappedLines[i]!)}`,
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Print last line with colon and stats aligned
|
|
890
|
+
// Use pre-calculated currentSuiteMaxNameLen for perfect alignment
|
|
891
|
+
if (wrappedLines.length > 1) {
|
|
892
|
+
const lastLine = wrappedLines[wrappedLines.length - 1]!;
|
|
893
|
+
const lastLineLen = this.getVisibleLength(lastLine);
|
|
894
|
+
const lastLinePad = ' '.repeat(
|
|
895
|
+
Math.max(0, this.currentSuiteMaxNameLen - lastLineLen),
|
|
896
|
+
);
|
|
897
|
+
this.printLine(
|
|
898
|
+
`${continueIndent}${this.colorize('white', lastLine)}${lastLinePad}: ${statsStr}`,
|
|
899
|
+
);
|
|
900
|
+
} else {
|
|
901
|
+
// Single wrapped line (shouldn't happen if nameLength > MAX but handle it)
|
|
902
|
+
const lastLinePad = ' '.repeat(
|
|
903
|
+
Math.max(0, this.currentSuiteMaxNameLen - nameLength),
|
|
904
|
+
);
|
|
905
|
+
this.printLine(
|
|
906
|
+
`${BASE_INDENT}${status} ${this.colorize('white', name)}${lastLinePad}: ${statsStr}`,
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
} else {
|
|
910
|
+
// Normal length - print on same line with pre-calculated alignment
|
|
911
|
+
const namePad = ' '.repeat(
|
|
912
|
+
Math.max(0, this.currentSuiteMaxNameLen - nameLength),
|
|
913
|
+
);
|
|
914
|
+
|
|
915
|
+
this.printLine(
|
|
916
|
+
`${BASE_INDENT}${status} ${this.colorize('white', name)}${namePad}: ${statsStr}`,
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (this.verbose && result.iterations > 0) {
|
|
921
|
+
this.printLine(
|
|
922
|
+
` ${this.colorize('dim', `${result.iterations} iterations`)}`,
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
649
927
|
/**
|
|
650
928
|
* Render the progress window at the bottom
|
|
651
929
|
*/
|
|
@@ -664,4 +942,45 @@ export class HumanReporter extends BaseReporter {
|
|
|
664
942
|
console.log(this.lastProgressLine);
|
|
665
943
|
this.progressWindowActive = true;
|
|
666
944
|
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Wrap text to a maximum width, breaking at word boundaries when possible
|
|
948
|
+
*/
|
|
949
|
+
private wrapText(text: string, maxWidth: number): string[] {
|
|
950
|
+
if (this.getVisibleLength(text) <= maxWidth) {
|
|
951
|
+
return [text];
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const lines: string[] = [];
|
|
955
|
+
let currentLine = '';
|
|
956
|
+
|
|
957
|
+
const words = text.split(/(\s+)/); // Keep whitespace in split
|
|
958
|
+
|
|
959
|
+
for (const word of words) {
|
|
960
|
+
const testLine = currentLine + word;
|
|
961
|
+
if (this.getVisibleLength(testLine) <= maxWidth) {
|
|
962
|
+
currentLine = testLine;
|
|
963
|
+
} else {
|
|
964
|
+
// If current line has content, save it
|
|
965
|
+
if (currentLine.trim()) {
|
|
966
|
+
lines.push(currentLine.trimEnd());
|
|
967
|
+
currentLine = word.trim() + ' ';
|
|
968
|
+
} else {
|
|
969
|
+
// Single word is too long, force break it
|
|
970
|
+
if (this.getVisibleLength(word) > maxWidth) {
|
|
971
|
+
lines.push(word.substring(0, maxWidth));
|
|
972
|
+
currentLine = word.substring(maxWidth);
|
|
973
|
+
} else {
|
|
974
|
+
currentLine = word;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (currentLine.trim()) {
|
|
981
|
+
lines.push(currentLine.trimEnd());
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
return lines;
|
|
985
|
+
}
|
|
667
986
|
}
|