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.
Files changed (275) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE.md +55 -0
  3. package/README.md +699 -0
  4. package/dist/bootstrap.cjs +37 -0
  5. package/dist/bootstrap.cjs.map +1 -0
  6. package/dist/bootstrap.d.cts +17 -0
  7. package/dist/bootstrap.d.cts.map +1 -0
  8. package/dist/bootstrap.d.ts +17 -0
  9. package/dist/bootstrap.d.ts.map +1 -0
  10. package/dist/bootstrap.js +33 -0
  11. package/dist/bootstrap.js.map +1 -0
  12. package/dist/cli/commands/history.cjs +459 -0
  13. package/dist/cli/commands/history.cjs.map +1 -0
  14. package/dist/cli/commands/history.d.cts +34 -0
  15. package/dist/cli/commands/history.d.cts.map +1 -0
  16. package/dist/cli/commands/history.d.ts +34 -0
  17. package/dist/cli/commands/history.d.ts.map +1 -0
  18. package/dist/cli/commands/history.js +422 -0
  19. package/dist/cli/commands/history.js.map +1 -0
  20. package/dist/cli/commands/init.cjs +566 -0
  21. package/dist/cli/commands/init.cjs.map +1 -0
  22. package/dist/cli/commands/init.d.cts +26 -0
  23. package/dist/cli/commands/init.d.cts.map +1 -0
  24. package/dist/cli/commands/init.d.ts +26 -0
  25. package/dist/cli/commands/init.d.ts.map +1 -0
  26. package/dist/cli/commands/init.js +562 -0
  27. package/dist/cli/commands/init.js.map +1 -0
  28. package/dist/cli/commands/run.cjs +285 -0
  29. package/dist/cli/commands/run.cjs.map +1 -0
  30. package/dist/cli/commands/run.d.cts +37 -0
  31. package/dist/cli/commands/run.d.cts.map +1 -0
  32. package/dist/cli/commands/run.d.ts +37 -0
  33. package/dist/cli/commands/run.d.ts.map +1 -0
  34. package/dist/cli/commands/run.js +248 -0
  35. package/dist/cli/commands/run.js.map +1 -0
  36. package/dist/cli/index.cjs +523 -0
  37. package/dist/cli/index.cjs.map +1 -0
  38. package/dist/cli/index.d.cts +58 -0
  39. package/dist/cli/index.d.cts.map +1 -0
  40. package/dist/cli/index.d.ts +58 -0
  41. package/dist/cli/index.d.ts.map +1 -0
  42. package/dist/cli/index.js +515 -0
  43. package/dist/cli/index.js.map +1 -0
  44. package/dist/config/manager.cjs +370 -0
  45. package/dist/config/manager.cjs.map +1 -0
  46. package/dist/config/manager.d.cts +46 -0
  47. package/dist/config/manager.d.cts.map +1 -0
  48. package/dist/config/manager.d.ts +46 -0
  49. package/dist/config/manager.d.ts.map +1 -0
  50. package/dist/config/manager.js +333 -0
  51. package/dist/config/manager.js.map +1 -0
  52. package/dist/config/schema.cjs +182 -0
  53. package/dist/config/schema.cjs.map +1 -0
  54. package/dist/config/schema.d.cts +51 -0
  55. package/dist/config/schema.d.cts.map +1 -0
  56. package/dist/config/schema.d.ts +51 -0
  57. package/dist/config/schema.d.ts.map +1 -0
  58. package/dist/config/schema.js +145 -0
  59. package/dist/config/schema.js.map +1 -0
  60. package/dist/constants.cjs +22 -0
  61. package/dist/constants.cjs.map +1 -0
  62. package/dist/constants.d.cts +10 -0
  63. package/dist/constants.d.cts.map +1 -0
  64. package/dist/constants.d.ts +10 -0
  65. package/dist/constants.d.ts.map +1 -0
  66. package/dist/constants.js +19 -0
  67. package/dist/constants.js.map +1 -0
  68. package/dist/core/benchmark-schema.cjs +135 -0
  69. package/dist/core/benchmark-schema.cjs.map +1 -0
  70. package/dist/core/benchmark-schema.d.cts +139 -0
  71. package/dist/core/benchmark-schema.d.cts.map +1 -0
  72. package/dist/core/benchmark-schema.d.ts +139 -0
  73. package/dist/core/benchmark-schema.d.ts.map +1 -0
  74. package/dist/core/benchmark-schema.js +132 -0
  75. package/dist/core/benchmark-schema.js.map +1 -0
  76. package/dist/core/engine.cjs +669 -0
  77. package/dist/core/engine.cjs.map +1 -0
  78. package/dist/core/engine.d.cts +128 -0
  79. package/dist/core/engine.d.cts.map +1 -0
  80. package/dist/core/engine.d.ts +128 -0
  81. package/dist/core/engine.d.ts.map +1 -0
  82. package/dist/core/engine.js +632 -0
  83. package/dist/core/engine.js.map +1 -0
  84. package/dist/core/engines/accurate-engine.cjs +292 -0
  85. package/dist/core/engines/accurate-engine.cjs.map +1 -0
  86. package/dist/core/engines/accurate-engine.d.cts +63 -0
  87. package/dist/core/engines/accurate-engine.d.cts.map +1 -0
  88. package/dist/core/engines/accurate-engine.d.ts +63 -0
  89. package/dist/core/engines/accurate-engine.d.ts.map +1 -0
  90. package/dist/core/engines/accurate-engine.js +288 -0
  91. package/dist/core/engines/accurate-engine.js.map +1 -0
  92. package/dist/core/engines/index.cjs +21 -0
  93. package/dist/core/engines/index.cjs.map +1 -0
  94. package/dist/core/engines/index.d.cts +16 -0
  95. package/dist/core/engines/index.d.cts.map +1 -0
  96. package/dist/core/engines/index.d.ts +16 -0
  97. package/dist/core/engines/index.d.ts.map +1 -0
  98. package/dist/core/engines/index.js +16 -0
  99. package/dist/core/engines/index.js.map +1 -0
  100. package/dist/core/engines/tinybench-engine.cjs +286 -0
  101. package/dist/core/engines/tinybench-engine.cjs.map +1 -0
  102. package/dist/core/engines/tinybench-engine.d.cts +18 -0
  103. package/dist/core/engines/tinybench-engine.d.cts.map +1 -0
  104. package/dist/core/engines/tinybench-engine.d.ts +18 -0
  105. package/dist/core/engines/tinybench-engine.d.ts.map +1 -0
  106. package/dist/core/engines/tinybench-engine.js +282 -0
  107. package/dist/core/engines/tinybench-engine.js.map +1 -0
  108. package/dist/core/error-manager.cjs +303 -0
  109. package/dist/core/error-manager.cjs.map +1 -0
  110. package/dist/core/error-manager.d.cts +77 -0
  111. package/dist/core/error-manager.d.cts.map +1 -0
  112. package/dist/core/error-manager.d.ts +77 -0
  113. package/dist/core/error-manager.d.ts.map +1 -0
  114. package/dist/core/error-manager.js +299 -0
  115. package/dist/core/error-manager.js.map +1 -0
  116. package/dist/core/loader.cjs +287 -0
  117. package/dist/core/loader.cjs.map +1 -0
  118. package/dist/core/loader.d.cts +55 -0
  119. package/dist/core/loader.d.cts.map +1 -0
  120. package/dist/core/loader.d.ts +55 -0
  121. package/dist/core/loader.d.ts.map +1 -0
  122. package/dist/core/loader.js +250 -0
  123. package/dist/core/loader.js.map +1 -0
  124. package/dist/core/stats-utils.cjs +99 -0
  125. package/dist/core/stats-utils.cjs.map +1 -0
  126. package/dist/core/stats-utils.d.cts +50 -0
  127. package/dist/core/stats-utils.d.cts.map +1 -0
  128. package/dist/core/stats-utils.d.ts +50 -0
  129. package/dist/core/stats-utils.d.ts.map +1 -0
  130. package/dist/core/stats-utils.js +94 -0
  131. package/dist/core/stats-utils.js.map +1 -0
  132. package/dist/index.cjs +64 -0
  133. package/dist/index.cjs.map +1 -0
  134. package/dist/index.d.cts +22 -0
  135. package/dist/index.d.cts.map +1 -0
  136. package/dist/index.d.ts +22 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +30 -0
  139. package/dist/index.js.map +1 -0
  140. package/dist/progress/manager.cjs +325 -0
  141. package/dist/progress/manager.cjs.map +1 -0
  142. package/dist/progress/manager.d.cts +125 -0
  143. package/dist/progress/manager.d.cts.map +1 -0
  144. package/dist/progress/manager.d.ts +125 -0
  145. package/dist/progress/manager.d.ts.map +1 -0
  146. package/dist/progress/manager.js +321 -0
  147. package/dist/progress/manager.js.map +1 -0
  148. package/dist/reporters/csv.cjs +250 -0
  149. package/dist/reporters/csv.cjs.map +1 -0
  150. package/dist/reporters/csv.d.cts +92 -0
  151. package/dist/reporters/csv.d.cts.map +1 -0
  152. package/dist/reporters/csv.d.ts +92 -0
  153. package/dist/reporters/csv.d.ts.map +1 -0
  154. package/dist/reporters/csv.js +246 -0
  155. package/dist/reporters/csv.js.map +1 -0
  156. package/dist/reporters/human.cjs +516 -0
  157. package/dist/reporters/human.cjs.map +1 -0
  158. package/dist/reporters/human.d.cts +86 -0
  159. package/dist/reporters/human.d.cts.map +1 -0
  160. package/dist/reporters/human.d.ts +86 -0
  161. package/dist/reporters/human.d.ts.map +1 -0
  162. package/dist/reporters/human.js +509 -0
  163. package/dist/reporters/human.js.map +1 -0
  164. package/dist/reporters/index.cjs +17 -0
  165. package/dist/reporters/index.cjs.map +1 -0
  166. package/dist/reporters/index.d.cts +10 -0
  167. package/dist/reporters/index.d.cts.map +1 -0
  168. package/dist/reporters/index.d.ts +10 -0
  169. package/dist/reporters/index.d.ts.map +1 -0
  170. package/dist/reporters/index.js +10 -0
  171. package/dist/reporters/index.js.map +1 -0
  172. package/dist/reporters/json.cjs +215 -0
  173. package/dist/reporters/json.cjs.map +1 -0
  174. package/dist/reporters/json.d.cts +79 -0
  175. package/dist/reporters/json.d.cts.map +1 -0
  176. package/dist/reporters/json.d.ts +79 -0
  177. package/dist/reporters/json.d.ts.map +1 -0
  178. package/dist/reporters/json.js +211 -0
  179. package/dist/reporters/json.js.map +1 -0
  180. package/dist/reporters/registry.cjs +255 -0
  181. package/dist/reporters/registry.cjs.map +1 -0
  182. package/dist/reporters/registry.d.cts +155 -0
  183. package/dist/reporters/registry.d.cts.map +1 -0
  184. package/dist/reporters/registry.d.ts +155 -0
  185. package/dist/reporters/registry.d.ts.map +1 -0
  186. package/dist/reporters/registry.js +249 -0
  187. package/dist/reporters/registry.js.map +1 -0
  188. package/dist/reporters/simple.cjs +328 -0
  189. package/dist/reporters/simple.cjs.map +1 -0
  190. package/dist/reporters/simple.d.cts +51 -0
  191. package/dist/reporters/simple.d.cts.map +1 -0
  192. package/dist/reporters/simple.d.ts +51 -0
  193. package/dist/reporters/simple.d.ts.map +1 -0
  194. package/dist/reporters/simple.js +321 -0
  195. package/dist/reporters/simple.js.map +1 -0
  196. package/dist/schema/modestbench-config.schema.json +162 -0
  197. package/dist/storage/history.cjs +456 -0
  198. package/dist/storage/history.cjs.map +1 -0
  199. package/dist/storage/history.d.cts +99 -0
  200. package/dist/storage/history.d.cts.map +1 -0
  201. package/dist/storage/history.d.ts +99 -0
  202. package/dist/storage/history.d.ts.map +1 -0
  203. package/dist/storage/history.js +452 -0
  204. package/dist/storage/history.js.map +1 -0
  205. package/dist/types/cli.cjs +21 -0
  206. package/dist/types/cli.cjs.map +1 -0
  207. package/dist/types/cli.d.cts +296 -0
  208. package/dist/types/cli.d.cts.map +1 -0
  209. package/dist/types/cli.d.ts +296 -0
  210. package/dist/types/cli.d.ts.map +1 -0
  211. package/dist/types/cli.js +18 -0
  212. package/dist/types/cli.js.map +1 -0
  213. package/dist/types/core.cjs +14 -0
  214. package/dist/types/core.cjs.map +1 -0
  215. package/dist/types/core.d.cts +380 -0
  216. package/dist/types/core.d.cts.map +1 -0
  217. package/dist/types/core.d.ts +380 -0
  218. package/dist/types/core.d.ts.map +1 -0
  219. package/dist/types/core.js +13 -0
  220. package/dist/types/core.js.map +1 -0
  221. package/dist/types/index.cjs +27 -0
  222. package/dist/types/index.cjs.map +1 -0
  223. package/dist/types/index.d.cts +11 -0
  224. package/dist/types/index.d.cts.map +1 -0
  225. package/dist/types/index.d.ts +11 -0
  226. package/dist/types/index.d.ts.map +1 -0
  227. package/dist/types/index.js +11 -0
  228. package/dist/types/index.js.map +1 -0
  229. package/dist/types/interfaces.cjs +10 -0
  230. package/dist/types/interfaces.cjs.map +1 -0
  231. package/dist/types/interfaces.d.cts +381 -0
  232. package/dist/types/interfaces.d.cts.map +1 -0
  233. package/dist/types/interfaces.d.ts +381 -0
  234. package/dist/types/interfaces.d.ts.map +1 -0
  235. package/dist/types/interfaces.js +9 -0
  236. package/dist/types/interfaces.js.map +1 -0
  237. package/dist/types/utility.cjs +92 -0
  238. package/dist/types/utility.cjs.map +1 -0
  239. package/dist/types/utility.d.cts +330 -0
  240. package/dist/types/utility.d.cts.map +1 -0
  241. package/dist/types/utility.d.ts +330 -0
  242. package/dist/types/utility.d.ts.map +1 -0
  243. package/dist/types/utility.js +78 -0
  244. package/dist/types/utility.js.map +1 -0
  245. package/package.json +211 -0
  246. package/src/bootstrap.ts +35 -0
  247. package/src/cli/commands/history.ts +569 -0
  248. package/src/cli/commands/init.ts +658 -0
  249. package/src/cli/commands/run.ts +346 -0
  250. package/src/cli/index.ts +642 -0
  251. package/src/config/manager.ts +387 -0
  252. package/src/config/schema.ts +188 -0
  253. package/src/constants.ts +21 -0
  254. package/src/core/benchmark-schema.ts +185 -0
  255. package/src/core/engine.ts +888 -0
  256. package/src/core/engines/accurate-engine.ts +408 -0
  257. package/src/core/engines/index.ts +16 -0
  258. package/src/core/engines/tinybench-engine.ts +335 -0
  259. package/src/core/error-manager.ts +372 -0
  260. package/src/core/loader.ts +324 -0
  261. package/src/core/stats-utils.ts +135 -0
  262. package/src/index.ts +46 -0
  263. package/src/progress/manager.ts +415 -0
  264. package/src/reporters/csv.ts +368 -0
  265. package/src/reporters/human.ts +707 -0
  266. package/src/reporters/index.ts +10 -0
  267. package/src/reporters/json.ts +302 -0
  268. package/src/reporters/registry.ts +349 -0
  269. package/src/reporters/simple.ts +459 -0
  270. package/src/storage/history.ts +600 -0
  271. package/src/types/cli.ts +312 -0
  272. package/src/types/core.ts +414 -0
  273. package/src/types/index.ts +18 -0
  274. package/src/types/interfaces.ts +451 -0
  275. package/src/types/utility.ts +446 -0
@@ -0,0 +1,669 @@
1
+ "use strict";
2
+ /**
3
+ * ModestBench Core Engine
4
+ *
5
+ * Main orchestrator for benchmark discovery, validation, and execution.
6
+ * Implements the BenchmarkEngine interface with dependency injection
7
+ * architecture.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.ModestBenchEngine = void 0;
44
+ /**
45
+ * Abstract benchmark execution engine with dependency injection
46
+ *
47
+ * Provides generic orchestration logic for benchmark discovery, validation, and
48
+ * execution. Concrete implementations must provide the task execution logic via
49
+ * the executeBenchmarkTask method.
50
+ */
51
+ class ModestBenchEngine {
52
+ configManager;
53
+ errorManager;
54
+ fileLoader;
55
+ historyStorage;
56
+ progressManager;
57
+ reporterRegistry;
58
+ constructor(dependencies) {
59
+ this.configManager = dependencies.configManager;
60
+ this.fileLoader = dependencies.fileLoader;
61
+ this.reporterRegistry = dependencies.reporterRegistry;
62
+ this.historyStorage = dependencies.historyStorage;
63
+ this.progressManager = dependencies.progressManager;
64
+ this.errorManager = dependencies.errorManager;
65
+ }
66
+ /**
67
+ * Discover benchmark files matching the pattern(s)
68
+ */
69
+ async discover(pattern, exclude) {
70
+ try {
71
+ return await this.fileLoader.discover(pattern, exclude);
72
+ }
73
+ catch (error) {
74
+ const discoveryError = error instanceof Error ? error : new Error(String(error));
75
+ this.errorManager.handleError(discoveryError, {
76
+ metadata: { exclude, pattern },
77
+ phase: 'discovery',
78
+ timestamp: new Date(),
79
+ });
80
+ throw new Error(`File discovery failed: ${discoveryError.message}`);
81
+ }
82
+ }
83
+ /**
84
+ * Execute benchmarks with the given configuration
85
+ */
86
+ async execute(config, reporters = [], signal) {
87
+ const startTime = new Date();
88
+ let currentPhase = 'discovery';
89
+ try {
90
+ // 1. Merge configuration with defaults
91
+ currentPhase = 'discovery';
92
+ const mergedConfig = await this.configManager.load(undefined, // No specific config path for now
93
+ config);
94
+ // 2. Discover files if not explicitly provided
95
+ const files = config.files ||
96
+ (await this.discover(mergedConfig.pattern, mergedConfig.exclude));
97
+ if (files.length === 0) {
98
+ const error = new Error('No benchmark files found matching the pattern');
99
+ this.errorManager.handleError(error, {
100
+ phase: currentPhase,
101
+ timestamp: new Date(),
102
+ });
103
+ throw error;
104
+ }
105
+ // 3. Validate files
106
+ currentPhase = 'validation';
107
+ const validationResult = await this.validate(files);
108
+ if (!validationResult.valid) {
109
+ const error = new Error(`Validation failed: ${validationResult.errors.map((e) => e.message).join(', ')}`);
110
+ this.errorManager.handleError(error, {
111
+ phase: currentPhase,
112
+ timestamp: new Date(),
113
+ });
114
+ throw error;
115
+ }
116
+ // 4. Initialize progress tracking
117
+ currentPhase = 'setup';
118
+ const runId = this.generateRunId();
119
+ // Pre-calculate total tasks for progress tracking
120
+ let totalTasks = 0;
121
+ let totalSuites = 0;
122
+ for (const filePath of files) {
123
+ try {
124
+ const benchmarkFile = await this.fileLoader.load(filePath);
125
+ const benchmarkDef = benchmarkFile.exports;
126
+ if (benchmarkDef?.suites && typeof benchmarkDef.suites === 'object') {
127
+ const fileTags = benchmarkDef.tags;
128
+ for (const [_suiteName, suiteData] of Object.entries(benchmarkDef.suites)) {
129
+ // Use shared filtering logic
130
+ const { anyTaskMatches, suiteMatches, tasksToRun } = this.getFilteredTasksForSuite(suiteData, fileTags, mergedConfig.tags, mergedConfig.excludeTags);
131
+ // Count suite only if it or any of its tasks match
132
+ if (suiteMatches || anyTaskMatches) {
133
+ totalSuites++;
134
+ totalTasks += tasksToRun.length;
135
+ }
136
+ }
137
+ }
138
+ }
139
+ catch (error) {
140
+ // If we can't load a file for counting, we'll handle it during execution
141
+ // Only show warning if not in quiet mode
142
+ if (!mergedConfig.quiet) {
143
+ console.warn(`Warning: Could not pre-load ${filePath} for task counting:`, error);
144
+ }
145
+ }
146
+ }
147
+ // Create initial run structure for progress tracking
148
+ const gitInfo = await this.getGitInfo();
149
+ const ciInfo = await this.getCiInfo();
150
+ const initialRun = {
151
+ config: mergedConfig,
152
+ duration: 0,
153
+ endTime: startTime,
154
+ environment: await this.getEnvironmentInfo(),
155
+ files: [],
156
+ id: runId,
157
+ startTime,
158
+ ...(gitInfo && { git: gitInfo }),
159
+ ...(ciInfo && { ci: ciInfo }),
160
+ summary: {
161
+ failedTasks: 0,
162
+ fastest: null,
163
+ overallMean: 0,
164
+ passedTasks: 0,
165
+ slowest: null,
166
+ totalFiles: files.length,
167
+ totalOperations: 0,
168
+ totalSuites,
169
+ totalTasks,
170
+ },
171
+ };
172
+ this.progressManager.initialize(initialRun);
173
+ // Register progress callbacks with reporters that support them
174
+ for (const reporter of reporters) {
175
+ if (typeof reporter.onProgress === 'function') {
176
+ this.progressManager.onProgress((state) => {
177
+ void reporter.onProgress(state);
178
+ });
179
+ }
180
+ }
181
+ // 5. Call reporter onStart lifecycle method
182
+ await this.callReporters(reporters, 'onStart', initialRun);
183
+ // 6. Execute benchmark files
184
+ currentPhase = 'execution';
185
+ const fileResults = [];
186
+ for (const filePath of files) {
187
+ try {
188
+ // Call reporter onFileStart
189
+ await this.callReporters(reporters, 'onFileStart', filePath);
190
+ const fileResult = await this.executeBenchmarkFile(filePath, mergedConfig, reporters, signal);
191
+ fileResults.push(fileResult);
192
+ // Call reporter onFileEnd
193
+ await this.callReporters(reporters, 'onFileEnd', fileResult);
194
+ // Update progress
195
+ this.progressManager.update({
196
+ currentFile: filePath,
197
+ filesCompleted: fileResults.length,
198
+ });
199
+ }
200
+ catch (error) {
201
+ const fileError = error instanceof Error ? error : new Error(String(error));
202
+ this.errorManager.handleError(fileError, {
203
+ file: filePath,
204
+ phase: currentPhase,
205
+ timestamp: new Date(),
206
+ });
207
+ // Call reporter onError
208
+ await this.callReporters(reporters, 'onError', fileError);
209
+ // Create error result for this file
210
+ const now = new Date();
211
+ const errorResult = {
212
+ duration: 0,
213
+ endTime: now,
214
+ error: fileError,
215
+ filePath,
216
+ startTime: now,
217
+ suites: [],
218
+ };
219
+ fileResults.push(errorResult);
220
+ // Call reporter onFileEnd for error case
221
+ await this.callReporters(reporters, 'onFileEnd', errorResult);
222
+ }
223
+ }
224
+ // Calculate summary statistics
225
+ const finalTotalSuites = fileResults.reduce((sum, file) => sum + file.suites.length, 0);
226
+ const allTasks = fileResults.flatMap((file) => file.suites.flatMap((suite) => suite.tasks));
227
+ const finalTotalTasks = allTasks.length;
228
+ const failedTasks = allTasks.filter((task) => task.error).length;
229
+ const passedTasks = finalTotalTasks - failedTasks;
230
+ let fastest = null;
231
+ let slowest = null;
232
+ let totalOperations = 0;
233
+ let totalTime = 0;
234
+ for (const task of allTasks) {
235
+ if (!task.error) {
236
+ totalOperations += task.iterations;
237
+ totalTime += task.mean * task.iterations;
238
+ if (!fastest || task.mean < fastest.mean) {
239
+ fastest = task;
240
+ }
241
+ if (!slowest || task.mean > slowest.mean) {
242
+ slowest = task;
243
+ }
244
+ }
245
+ }
246
+ const overallMean = totalOperations > 0 ? totalTime / totalOperations : 0;
247
+ const endTime = new Date();
248
+ const finalRun = {
249
+ ...initialRun,
250
+ duration: endTime.getTime() - startTime.getTime(),
251
+ endTime,
252
+ files: fileResults,
253
+ summary: {
254
+ failedTasks,
255
+ fastest,
256
+ overallMean,
257
+ passedTasks,
258
+ slowest,
259
+ totalFiles: files.length,
260
+ totalOperations,
261
+ totalSuites: finalTotalSuites,
262
+ totalTasks: finalTotalTasks,
263
+ },
264
+ };
265
+ // 7. Save to history
266
+ await this.historyStorage.saveRun(finalRun);
267
+ // 8. Call reporter onEnd lifecycle method
268
+ await this.callReporters(reporters, 'onEnd', finalRun);
269
+ // 9. Return completed run
270
+ return finalRun;
271
+ }
272
+ catch (error) {
273
+ const executionError = error instanceof Error ? error : new Error(String(error));
274
+ const handledError = this.errorManager.handleError(executionError, {
275
+ phase: currentPhase,
276
+ timestamp: new Date(),
277
+ });
278
+ // Re-throw the original error with more context
279
+ throw new Error(`Benchmark execution failed: ${handledError.message}`);
280
+ }
281
+ }
282
+ /**
283
+ * Get the configuration manager
284
+ */
285
+ getConfigManager() {
286
+ return this.configManager;
287
+ }
288
+ /**
289
+ * Get the file loader
290
+ */
291
+ getFileLoader() {
292
+ return this.fileLoader;
293
+ }
294
+ /**
295
+ * Get the history storage
296
+ */
297
+ getHistoryStorage() {
298
+ return this.historyStorage;
299
+ }
300
+ /**
301
+ * Get the progress manager
302
+ */
303
+ getProgressManager() {
304
+ return this.progressManager;
305
+ }
306
+ /**
307
+ * Get all available reporters
308
+ */
309
+ getReporters() {
310
+ return this.reporterRegistry.getAll();
311
+ }
312
+ /**
313
+ * Register a custom reporter
314
+ */
315
+ registerReporter(name, reporter) {
316
+ this.reporterRegistry.register(name, reporter);
317
+ }
318
+ /**
319
+ * Validate benchmark files without executing them
320
+ */
321
+ async validate(files) {
322
+ try {
323
+ const errors = [];
324
+ const warnings = [];
325
+ const validatedFiles = [];
326
+ // Validate each file
327
+ for (const file of files) {
328
+ try {
329
+ const result = await this.fileLoader.validate(file);
330
+ validatedFiles.push(file);
331
+ errors.push(...result.errors);
332
+ warnings.push(...result.warnings);
333
+ }
334
+ catch (error) {
335
+ const validationError = error instanceof Error ? error : new Error(String(error));
336
+ this.errorManager.handleError(validationError, {
337
+ file,
338
+ phase: 'validation',
339
+ timestamp: new Date(),
340
+ });
341
+ errors.push({
342
+ code: 'FILE_VALIDATION_ERROR',
343
+ file,
344
+ message: `Failed to validate file: ${validationError.message}`,
345
+ severity: 'error',
346
+ });
347
+ }
348
+ }
349
+ return {
350
+ errors,
351
+ files: validatedFiles,
352
+ valid: errors.length === 0,
353
+ warnings,
354
+ };
355
+ }
356
+ catch (error) {
357
+ throw new Error(`Validation failed: ${error instanceof Error ? error.message : String(error)}`);
358
+ }
359
+ }
360
+ /**
361
+ * Helper method to call a lifecycle method on all reporters
362
+ */
363
+ async callReporters(reporters, method, ...args) {
364
+ for (const reporter of reporters) {
365
+ try {
366
+ const reporterMethod = reporter[method];
367
+ if (typeof reporterMethod === 'function') {
368
+ const result = reporterMethod.call(reporter, ...args);
369
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
370
+ if (result && typeof result.then === 'function') {
371
+ await result;
372
+ }
373
+ }
374
+ }
375
+ catch (error) {
376
+ // Log reporter errors but don't fail the benchmark run
377
+ console.error(`Reporter error in ${method}:`, error);
378
+ }
379
+ }
380
+ }
381
+ /**
382
+ * Execute a single benchmark file and return its results
383
+ */
384
+ async executeBenchmarkFile(filePath, config, reporters = [], signal) {
385
+ const startTime = new Date();
386
+ try {
387
+ // Load the benchmark file using the file loader
388
+ const benchmarkFile = await this.fileLoader.load(filePath);
389
+ const benchmarkDef = benchmarkFile.exports;
390
+ if (!benchmarkDef || typeof benchmarkDef !== 'object') {
391
+ throw new Error('Benchmark file must export a default object with suites');
392
+ }
393
+ const suiteResults = [];
394
+ const fileTags = benchmarkDef.tags;
395
+ // Process each suite in the file
396
+ if (benchmarkDef.suites && typeof benchmarkDef.suites === 'object') {
397
+ for (const [suiteName, suiteData] of Object.entries(benchmarkDef.suites)) {
398
+ // Use shared filtering logic
399
+ const { anyTaskMatches, suiteMatches } = this.getFilteredTasksForSuite(suiteData, fileTags, config.tags, config.excludeTags);
400
+ // Skip suite only if neither the suite nor any of its tasks match
401
+ if (!suiteMatches && !anyTaskMatches) {
402
+ continue;
403
+ }
404
+ await this.callReporters(reporters, 'onSuiteStart', suiteName);
405
+ const suiteResult = await this.executeBenchmarkSuite(suiteName, suiteData, config, reporters, signal, fileTags);
406
+ await this.callReporters(reporters, 'onSuiteEnd', suiteResult);
407
+ suiteResults.push(suiteResult);
408
+ }
409
+ }
410
+ const endTime = new Date();
411
+ return {
412
+ config: benchmarkDef.config,
413
+ duration: endTime.getTime() - startTime.getTime(),
414
+ endTime,
415
+ filePath,
416
+ startTime,
417
+ suites: suiteResults,
418
+ };
419
+ }
420
+ catch (error) {
421
+ const endTime = new Date();
422
+ const executionError = error instanceof Error ? error : new Error(String(error));
423
+ return {
424
+ duration: endTime.getTime() - startTime.getTime(),
425
+ endTime,
426
+ error: executionError,
427
+ filePath,
428
+ startTime,
429
+ suites: [],
430
+ };
431
+ }
432
+ }
433
+ /**
434
+ * Execute a single benchmark suite and return its results
435
+ */
436
+ async executeBenchmarkSuite(suiteName, suiteData, config, reporters = [], signal, fileTags) {
437
+ const startTime = new Date();
438
+ try {
439
+ const taskResults = [];
440
+ // Use shared filtering logic to determine which tasks will run
441
+ const { tasksToRun } = this.getFilteredTasksForSuite(suiteData, fileTags, config.tags, config.excludeTags);
442
+ // Only run setup/teardown if there are tasks to execute
443
+ if (tasksToRun.length === 0) {
444
+ // No tasks match the filters, return empty suite result
445
+ const endTime = new Date();
446
+ return {
447
+ duration: endTime.getTime() - startTime.getTime(),
448
+ endTime,
449
+ name: suiteName,
450
+ startTime,
451
+ tasks: [],
452
+ ...(suiteData.config !== undefined && { config: suiteData.config }),
453
+ ...(suiteData.metadata !== undefined && {
454
+ metadata: suiteData.metadata,
455
+ }),
456
+ ...(suiteData.tags !== undefined && { tags: suiteData.tags }),
457
+ };
458
+ }
459
+ // Run suite setup if provided
460
+ if (suiteData.setup && typeof suiteData.setup === 'function') {
461
+ try {
462
+ await suiteData.setup();
463
+ }
464
+ catch (error) {
465
+ const setupError = error instanceof Error
466
+ ? error
467
+ : new Error(`Setup failed: ${String(error)}`);
468
+ throw new Error(`Suite setup failed: ${setupError.message}`);
469
+ }
470
+ }
471
+ try {
472
+ // Process each task that passed filtering
473
+ for (const [taskName, taskData] of tasksToRun) {
474
+ await this.callReporters(reporters, 'onTaskStart', taskName);
475
+ // Mark task as in-progress (shows as 0.5 progress for current task)
476
+ const currentState = this.progressManager.getState();
477
+ this.progressManager.update({
478
+ tasksCompleted: currentState.tasksCompleted + 0.5,
479
+ });
480
+ const taskResult = await this.executeBenchmarkTask(taskName, taskData, config, reporters, signal);
481
+ await this.callReporters(reporters, 'onTaskResult', taskResult);
482
+ taskResults.push(taskResult);
483
+ // Update task-level progress - task is now complete (remove the 0.5 and add 1)
484
+ this.progressManager.update({
485
+ tasksCompleted: currentState.tasksCompleted + 1,
486
+ });
487
+ }
488
+ }
489
+ finally {
490
+ // Run suite teardown if provided (always runs, even if benchmarks fail)
491
+ if (suiteData.teardown && typeof suiteData.teardown === 'function') {
492
+ try {
493
+ await suiteData.teardown();
494
+ }
495
+ catch (error) {
496
+ // Log teardown errors but don't fail the suite
497
+ const teardownError = error instanceof Error ? error : new Error(String(error));
498
+ console.error(`Warning: Suite teardown failed for "${suiteName}":`, teardownError.message);
499
+ }
500
+ }
501
+ }
502
+ const endTime = new Date();
503
+ return {
504
+ duration: endTime.getTime() - startTime.getTime(),
505
+ endTime,
506
+ name: suiteName,
507
+ startTime,
508
+ tasks: taskResults,
509
+ ...(suiteData.config !== undefined && { config: suiteData.config }),
510
+ ...(suiteData.metadata !== undefined && {
511
+ metadata: suiteData.metadata,
512
+ }),
513
+ ...(suiteData.tags !== undefined && { tags: suiteData.tags }),
514
+ };
515
+ }
516
+ catch (error) {
517
+ const endTime = new Date();
518
+ const executionError = error instanceof Error ? error : new Error(String(error));
519
+ return {
520
+ duration: endTime.getTime() - startTime.getTime(),
521
+ endTime,
522
+ error: executionError,
523
+ name: suiteName,
524
+ startTime,
525
+ tasks: [],
526
+ };
527
+ }
528
+ }
529
+ /**
530
+ * Generate a unique run ID
531
+ */
532
+ generateRunId() {
533
+ return `run-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
534
+ }
535
+ /**
536
+ * Get CI/CD information if available
537
+ */
538
+ async getCiInfo() {
539
+ const process = await Promise.resolve().then(() => __importStar(require('node:process')));
540
+ if (!process.env.CI) {
541
+ return undefined;
542
+ }
543
+ // Detect common CI providers
544
+ if (process.env.GITHUB_ACTIONS) {
545
+ return {
546
+ provider: 'GitHub Actions',
547
+ ...(process.env.GITHUB_RUN_NUMBER && {
548
+ buildNumber: process.env.GITHUB_RUN_NUMBER,
549
+ }),
550
+ ...(process.env.GITHUB_REPOSITORY &&
551
+ process.env.GITHUB_RUN_ID && {
552
+ buildUrl: `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`,
553
+ }),
554
+ ...(process.env.GITHUB_EVENT_NAME === 'pull_request' &&
555
+ process.env.GITHUB_REF_NAME && {
556
+ pullRequest: process.env.GITHUB_REF_NAME,
557
+ }),
558
+ ...(process.env.GITHUB_REF_NAME && {
559
+ branch: process.env.GITHUB_REF_NAME,
560
+ }),
561
+ ...(process.env.GITHUB_SHA && { commit: process.env.GITHUB_SHA }),
562
+ };
563
+ }
564
+ // Default CI info
565
+ return {
566
+ provider: 'Unknown CI',
567
+ ...(process.env.BRANCH && { branch: process.env.BRANCH }),
568
+ ...(process.env.COMMIT && { commit: process.env.COMMIT }),
569
+ };
570
+ }
571
+ /**
572
+ * Get environment information
573
+ */
574
+ async getEnvironmentInfo() {
575
+ const os = await Promise.resolve().then(() => __importStar(require('node:os')));
576
+ const process = await Promise.resolve().then(() => __importStar(require('node:process')));
577
+ return {
578
+ arch: process.arch,
579
+ availableMemory: os.freemem(),
580
+ cpu: {
581
+ cores: os.cpus().length,
582
+ model: os.cpus()[0]?.model || 'Unknown',
583
+ speed: os.cpus()[0]?.speed || 0,
584
+ },
585
+ env: {
586
+ CI: process.env.CI || 'false',
587
+ NODE_ENV: process.env.NODE_ENV || 'development',
588
+ },
589
+ hostname: os.hostname(),
590
+ memory: {
591
+ free: os.freemem(),
592
+ total: os.totalmem(),
593
+ used: os.totalmem() - os.freemem(),
594
+ },
595
+ nodeVersion: process.version,
596
+ platform: process.platform,
597
+ };
598
+ }
599
+ /**
600
+ * Get filtered tasks for a suite based on tag filtering Returns suite match
601
+ * status and list of tasks to run
602
+ */
603
+ getFilteredTasksForSuite(suiteData, fileTags, includeTags, excludeTags) {
604
+ // Check if suite itself matches filters
605
+ const mergedSuiteTags = this.mergeTags(fileTags, suiteData.tags);
606
+ const suiteMatches = this.matchesTags(mergedSuiteTags, includeTags, excludeTags);
607
+ // Check which tasks match filters
608
+ const tasksToRun = [];
609
+ if (suiteData.benchmarks && typeof suiteData.benchmarks === 'object') {
610
+ for (const [taskName, taskData] of Object.entries(suiteData.benchmarks)) {
611
+ // Merge task tags with suite and file tags (cascading)
612
+ const mergedTaskTags = this.mergeTags(mergedSuiteTags, taskData.tags);
613
+ // Check if task matches tag filters
614
+ if (this.matchesTags(mergedTaskTags, includeTags, excludeTags)) {
615
+ tasksToRun.push([taskName, taskData]);
616
+ }
617
+ }
618
+ }
619
+ return {
620
+ anyTaskMatches: tasksToRun.length > 0,
621
+ suiteMatches,
622
+ tasksToRun,
623
+ };
624
+ }
625
+ /**
626
+ * Get Git information if available
627
+ */
628
+ async getGitInfo() {
629
+ // TODO: Implement Git information extraction
630
+ // This would use child_process to run git commands
631
+ return undefined;
632
+ }
633
+ /**
634
+ * Check if item tags match the filter criteria (OR logic)
635
+ */
636
+ matchesTags(itemTags, includeTags, excludeTags) {
637
+ const tags = itemTags || [];
638
+ // If exclude tags specified and any match, exclude this item
639
+ if (excludeTags.length > 0 &&
640
+ excludeTags.some((tag) => tags.includes(tag))) {
641
+ return false;
642
+ }
643
+ // If include tags specified, at least one must match
644
+ if (includeTags.length > 0) {
645
+ return includeTags.some((tag) => tags.includes(tag));
646
+ }
647
+ // No filters = include everything
648
+ return true;
649
+ }
650
+ /**
651
+ * Merge tags from parent to child (cascading)
652
+ */
653
+ mergeTags(parentTags, childTags) {
654
+ const merged = new Set();
655
+ if (parentTags) {
656
+ for (const tag of parentTags) {
657
+ merged.add(tag);
658
+ }
659
+ }
660
+ if (childTags) {
661
+ for (const tag of childTags) {
662
+ merged.add(tag);
663
+ }
664
+ }
665
+ return merged.size > 0 ? Array.from(merged) : undefined;
666
+ }
667
+ }
668
+ exports.ModestBenchEngine = ModestBenchEngine;
669
+ //# sourceMappingURL=engine.js.map