codeflash 0.0.1 → 0.2.0

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.
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Codeflash Loop Runner - Custom Jest Test Runner for Performance Benchmarking
3
+ *
4
+ * Implements BATCHED LOOPING for fair distribution across all test invocations:
5
+ *
6
+ * Batch 1: Test1(5 loops) → Test2(5 loops) → Test3(5 loops) → ...
7
+ * Batch 2: Test1(5 loops) → Test2(5 loops) → Test3(5 loops) → ...
8
+ * ...until time budget exhausted
9
+ *
10
+ * This ensures:
11
+ * - Fair distribution: All test invocations get equal loop counts
12
+ * - Batched overhead: Console.log overhead amortized over batches
13
+ * - Good utilization: Time budget shared across all tests
14
+ *
15
+ * Configuration via environment variables:
16
+ * CODEFLASH_PERF_LOOP_COUNT - Max loops per invocation (default: 10000)
17
+ * CODEFLASH_PERF_BATCH_SIZE - Loops per batch (default: 5)
18
+ * CODEFLASH_PERF_MIN_LOOPS - Min loops before stopping (default: 5)
19
+ * CODEFLASH_PERF_TARGET_DURATION_MS - Target total duration (default: 10000)
20
+ *
21
+ * Usage:
22
+ * npx jest --runner=codeflash/loop-runner
23
+ */
24
+
25
+ 'use strict';
26
+
27
+ const { createRequire } = require('module');
28
+ const path = require('path');
29
+
30
+ const jestRunnerPath = require.resolve('jest-runner');
31
+ const internalRequire = createRequire(jestRunnerPath);
32
+ const runTest = internalRequire('./runTest').default;
33
+
34
+ // Configuration
35
+ const MAX_BATCHES = parseInt(process.env.CODEFLASH_PERF_LOOP_COUNT || '10000', 10);
36
+ const TARGET_DURATION_MS = parseInt(process.env.CODEFLASH_PERF_TARGET_DURATION_MS || '10000', 10);
37
+ const MIN_BATCHES = parseInt(process.env.CODEFLASH_PERF_MIN_LOOPS || '5', 10);
38
+
39
+ /**
40
+ * Simple event emitter for Jest compatibility.
41
+ */
42
+ class SimpleEventEmitter {
43
+ constructor() {
44
+ this.listeners = new Map();
45
+ }
46
+
47
+ on(eventName, listener) {
48
+ if (!this.listeners.has(eventName)) {
49
+ this.listeners.set(eventName, new Set());
50
+ }
51
+ this.listeners.get(eventName).add(listener);
52
+ return () => {
53
+ const set = this.listeners.get(eventName);
54
+ if (set) set.delete(listener);
55
+ };
56
+ }
57
+
58
+ async emit(eventName, data) {
59
+ const set = this.listeners.get(eventName);
60
+ if (set) {
61
+ for (const listener of set) {
62
+ await listener(data);
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Deep copy utility.
70
+ */
71
+ function deepCopy(obj, seen = new WeakMap()) {
72
+ if (obj === null || typeof obj !== 'object') return obj;
73
+ if (seen.has(obj)) return seen.get(obj);
74
+ if (Array.isArray(obj)) {
75
+ const copy = [];
76
+ seen.set(obj, copy);
77
+ for (let i = 0; i < obj.length; i++) copy[i] = deepCopy(obj[i], seen);
78
+ return copy;
79
+ }
80
+ if (obj instanceof Date) return new Date(obj.getTime());
81
+ if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);
82
+ const copy = {};
83
+ seen.set(obj, copy);
84
+ for (const key of Object.keys(obj)) copy[key] = deepCopy(obj[key], seen);
85
+ return copy;
86
+ }
87
+
88
+ /**
89
+ * Codeflash Loop Runner with Batched Looping
90
+ */
91
+ class CodeflashLoopRunner {
92
+ constructor(globalConfig, context) {
93
+ this._globalConfig = globalConfig;
94
+ this._context = context || {};
95
+ this._eventEmitter = new SimpleEventEmitter();
96
+ }
97
+
98
+ get supportsEventEmitters() {
99
+ return true;
100
+ }
101
+
102
+ get isSerial() {
103
+ return true;
104
+ }
105
+
106
+ on(eventName, listener) {
107
+ return this._eventEmitter.on(eventName, listener);
108
+ }
109
+
110
+ /**
111
+ * Run tests with batched looping for fair distribution.
112
+ */
113
+ async runTests(tests, watcher, options) {
114
+ const startTime = Date.now();
115
+ let batchCount = 0;
116
+ let hasFailure = false;
117
+ let allConsoleOutput = '';
118
+
119
+ // Import shared state functions from capture module
120
+ // We need to do this dynamically since the module may be reloaded
121
+ let checkSharedTimeLimit;
122
+ let incrementBatch;
123
+ try {
124
+ const capture = require('codeflash');
125
+ checkSharedTimeLimit = capture.checkSharedTimeLimit;
126
+ incrementBatch = capture.incrementBatch;
127
+ } catch (e) {
128
+ // Fallback if codeflash module not available
129
+ checkSharedTimeLimit = () => {
130
+ const elapsed = Date.now() - startTime;
131
+ return elapsed >= TARGET_DURATION_MS && batchCount >= MIN_BATCHES;
132
+ };
133
+ incrementBatch = () => {};
134
+ }
135
+
136
+ // Batched looping: run all test files multiple times
137
+ while (batchCount < MAX_BATCHES) {
138
+ batchCount++;
139
+
140
+ // Check time limit BEFORE each batch
141
+ if (batchCount > MIN_BATCHES && checkSharedTimeLimit()) {
142
+ break;
143
+ }
144
+
145
+ // Check if interrupted
146
+ if (watcher.isInterrupted()) {
147
+ break;
148
+ }
149
+
150
+ // Increment batch counter in shared state and set env var
151
+ // The env var persists across Jest module resets, ensuring continuous loop indices
152
+ incrementBatch();
153
+ process.env.CODEFLASH_PERF_CURRENT_BATCH = String(batchCount);
154
+
155
+ // Run all test files in this batch
156
+ const batchResult = await this._runAllTestsOnce(tests, watcher);
157
+ allConsoleOutput += batchResult.consoleOutput;
158
+
159
+ if (batchResult.hasFailure) {
160
+ hasFailure = true;
161
+ break;
162
+ }
163
+
164
+ // Check time limit AFTER each batch
165
+ if (checkSharedTimeLimit()) {
166
+ break;
167
+ }
168
+ }
169
+
170
+ const totalTimeMs = Date.now() - startTime;
171
+
172
+ // Output all collected console logs - this is critical for timing marker extraction
173
+ // The console output contains the !######...######! timing markers from capturePerf
174
+ if (allConsoleOutput) {
175
+ process.stdout.write(allConsoleOutput);
176
+ }
177
+
178
+ console.log(`[codeflash] Batched runner completed: ${batchCount} batches, ${tests.length} test files, ${totalTimeMs}ms total`);
179
+ }
180
+
181
+ /**
182
+ * Run all test files once (one batch).
183
+ */
184
+ async _runAllTestsOnce(tests, watcher) {
185
+ let hasFailure = false;
186
+ let allConsoleOutput = '';
187
+
188
+ for (const test of tests) {
189
+ if (watcher.isInterrupted()) break;
190
+
191
+ const sendMessageToJest = (eventName, args) => {
192
+ this._eventEmitter.emit(eventName, deepCopy(args));
193
+ };
194
+
195
+ await this._eventEmitter.emit('test-file-start', [test]);
196
+
197
+ try {
198
+ const result = await runTest(
199
+ test.path,
200
+ this._globalConfig,
201
+ test.context.config,
202
+ test.context.resolver,
203
+ this._context,
204
+ sendMessageToJest
205
+ );
206
+
207
+ if (result.console && Array.isArray(result.console)) {
208
+ allConsoleOutput += result.console.map(e => e.message || '').join('\n') + '\n';
209
+ }
210
+
211
+ if (result.numFailingTests > 0) {
212
+ hasFailure = true;
213
+ }
214
+
215
+ await this._eventEmitter.emit('test-file-success', [test, result]);
216
+ } catch (error) {
217
+ hasFailure = true;
218
+ await this._eventEmitter.emit('test-file-failure', [test, error]);
219
+ }
220
+ }
221
+
222
+ return { consoleOutput: allConsoleOutput, hasFailure };
223
+ }
224
+ }
225
+
226
+ module.exports = CodeflashLoopRunner;