Skip to content

Commit 66fadf7

Browse files
Saga4claude
andcommitted
fix: use getter functions for perf env vars to support Vitest module caching
Vitest caches modules and may load capture.js before environment variables like CODEFLASH_PERF_LOOP_COUNT are set. When these were read as constants at module load time, they would always return default values. This change converts the performance configuration from constants to getter functions that read environment variables at runtime, ensuring correct values are used even when the module is cached. Fixes: - PERF_LOOP_COUNT always being 1 in Vitest - PERF_BATCH_SIZE, PERF_MIN_LOOPS, etc. using defaults instead of env values Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent c76cfaa commit 66fadf7

File tree

2 files changed

+109
-16
lines changed

2 files changed

+109
-16
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Test: Dynamic environment variable reading
3+
*
4+
* This test verifies that the performance configuration functions read
5+
* environment variables at runtime rather than at module load time.
6+
*
7+
* This is critical for Vitest compatibility, where modules may be cached
8+
* and loaded before environment variables are set.
9+
*
10+
* Run with: node __tests__/dynamic-env-vars.test.js
11+
*/
12+
13+
const assert = require('assert');
14+
15+
// Clear any existing env vars before loading the module
16+
delete process.env.CODEFLASH_PERF_LOOP_COUNT;
17+
delete process.env.CODEFLASH_PERF_MIN_LOOPS;
18+
delete process.env.CODEFLASH_PERF_TARGET_DURATION_MS;
19+
delete process.env.CODEFLASH_PERF_BATCH_SIZE;
20+
delete process.env.CODEFLASH_PERF_STABILITY_CHECK;
21+
delete process.env.CODEFLASH_PERF_CURRENT_BATCH;
22+
23+
// Now load the module - at this point env vars are not set
24+
const capture = require('../capture');
25+
26+
console.log('Testing dynamic environment variable reading...\n');
27+
28+
// Test 1: Default values when env vars are not set
29+
console.log('Test 1: Default values');
30+
assert.strictEqual(capture.getPerfLoopCount(), 1, 'getPerfLoopCount default should be 1');
31+
assert.strictEqual(capture.getPerfMinLoops(), 5, 'getPerfMinLoops default should be 5');
32+
assert.strictEqual(capture.getPerfTargetDurationMs(), 10000, 'getPerfTargetDurationMs default should be 10000');
33+
assert.strictEqual(capture.getPerfBatchSize(), 10, 'getPerfBatchSize default should be 10');
34+
assert.strictEqual(capture.getPerfStabilityCheck(), false, 'getPerfStabilityCheck default should be false');
35+
assert.strictEqual(capture.getPerfCurrentBatch(), 0, 'getPerfCurrentBatch default should be 0');
36+
console.log(' PASS: All defaults correct\n');
37+
38+
// Test 2: Values change when env vars are set AFTER module load
39+
// This is the critical test - if these were constants, they would still return defaults
40+
console.log('Test 2: Dynamic reading after module load');
41+
process.env.CODEFLASH_PERF_LOOP_COUNT = '100';
42+
process.env.CODEFLASH_PERF_MIN_LOOPS = '10';
43+
process.env.CODEFLASH_PERF_TARGET_DURATION_MS = '5000';
44+
process.env.CODEFLASH_PERF_BATCH_SIZE = '20';
45+
process.env.CODEFLASH_PERF_STABILITY_CHECK = 'true';
46+
process.env.CODEFLASH_PERF_CURRENT_BATCH = '5';
47+
48+
assert.strictEqual(capture.getPerfLoopCount(), 100, 'getPerfLoopCount should read 100 from env');
49+
assert.strictEqual(capture.getPerfMinLoops(), 10, 'getPerfMinLoops should read 10 from env');
50+
assert.strictEqual(capture.getPerfTargetDurationMs(), 5000, 'getPerfTargetDurationMs should read 5000 from env');
51+
assert.strictEqual(capture.getPerfBatchSize(), 20, 'getPerfBatchSize should read 20 from env');
52+
assert.strictEqual(capture.getPerfStabilityCheck(), true, 'getPerfStabilityCheck should read true from env');
53+
assert.strictEqual(capture.getPerfCurrentBatch(), 5, 'getPerfCurrentBatch should read 5 from env');
54+
console.log(' PASS: Dynamic reading works correctly\n');
55+
56+
// Test 3: Values change again when env vars are modified
57+
console.log('Test 3: Values update when env vars change');
58+
process.env.CODEFLASH_PERF_LOOP_COUNT = '500';
59+
process.env.CODEFLASH_PERF_BATCH_SIZE = '50';
60+
61+
assert.strictEqual(capture.getPerfLoopCount(), 500, 'getPerfLoopCount should update to 500');
62+
assert.strictEqual(capture.getPerfBatchSize(), 50, 'getPerfBatchSize should update to 50');
63+
console.log(' PASS: Values update correctly\n');
64+
65+
// Cleanup
66+
delete process.env.CODEFLASH_PERF_LOOP_COUNT;
67+
delete process.env.CODEFLASH_PERF_MIN_LOOPS;
68+
delete process.env.CODEFLASH_PERF_TARGET_DURATION_MS;
69+
delete process.env.CODEFLASH_PERF_BATCH_SIZE;
70+
delete process.env.CODEFLASH_PERF_STABILITY_CHECK;
71+
delete process.env.CODEFLASH_PERF_CURRENT_BATCH;
72+
73+
console.log('All tests passed!');

packages/codeflash/runtime/capture.js

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,30 @@ const TEST_MODULE = process.env.CODEFLASH_TEST_MODULE;
4747
// Batch 1: Test1(5 loops) → Test2(5 loops) → Test3(5 loops)
4848
// Batch 2: Test1(5 loops) → Test2(5 loops) → Test3(5 loops)
4949
// ...until time budget exhausted
50-
const PERF_LOOP_COUNT = parseInt(process.env.CODEFLASH_PERF_LOOP_COUNT || '1', 10);
51-
const PERF_MIN_LOOPS = parseInt(process.env.CODEFLASH_PERF_MIN_LOOPS || '5', 10);
52-
const PERF_TARGET_DURATION_MS = parseInt(process.env.CODEFLASH_PERF_TARGET_DURATION_MS || '10000', 10);
53-
const PERF_BATCH_SIZE = parseInt(process.env.CODEFLASH_PERF_BATCH_SIZE || '10', 10);
54-
const PERF_STABILITY_CHECK = (process.env.CODEFLASH_PERF_STABILITY_CHECK || 'false').toLowerCase() === 'true';
50+
//
51+
// IMPORTANT: These are getter functions, NOT constants!
52+
// Vitest caches modules and may load this file before env vars are set.
53+
// Using getter functions ensures we read the env vars at runtime when they're actually needed.
54+
function getPerfLoopCount() {
55+
return parseInt(process.env.CODEFLASH_PERF_LOOP_COUNT || '1', 10);
56+
}
57+
function getPerfMinLoops() {
58+
return parseInt(process.env.CODEFLASH_PERF_MIN_LOOPS || '5', 10);
59+
}
60+
function getPerfTargetDurationMs() {
61+
return parseInt(process.env.CODEFLASH_PERF_TARGET_DURATION_MS || '10000', 10);
62+
}
63+
function getPerfBatchSize() {
64+
return parseInt(process.env.CODEFLASH_PERF_BATCH_SIZE || '10', 10);
65+
}
66+
function getPerfStabilityCheck() {
67+
return (process.env.CODEFLASH_PERF_STABILITY_CHECK || 'false').toLowerCase() === 'true';
68+
}
5569
// Current batch number - set by loop-runner before each batch
5670
// This allows continuous loop indices even when Jest resets module state
57-
const PERF_CURRENT_BATCH = parseInt(process.env.CODEFLASH_PERF_CURRENT_BATCH || '0', 10);
71+
function getPerfCurrentBatch() {
72+
return parseInt(process.env.CODEFLASH_PERF_CURRENT_BATCH || '0', 10);
73+
}
5874

5975
// Stability constants (matching Python's config_consts.py)
6076
const STABILITY_WINDOW_SIZE = 0.35;
@@ -86,7 +102,7 @@ function checkSharedTimeLimit() {
86102
return false;
87103
}
88104
const elapsed = Date.now() - sharedPerfState.startTime;
89-
if (elapsed >= PERF_TARGET_DURATION_MS && sharedPerfState.totalLoopsCompleted >= PERF_MIN_LOOPS) {
105+
if (elapsed >= getPerfTargetDurationMs() && sharedPerfState.totalLoopsCompleted >= getPerfMinLoops()) {
90106
sharedPerfState.shouldStop = true;
91107
return true;
92108
}
@@ -111,7 +127,7 @@ function getInvocationLoopIndex(invocationKey) {
111127
// Calculate global loop index using batch number from environment
112128
// PERF_CURRENT_BATCH is 1-based (set by loop-runner before each batch)
113129
const currentBatch = parseInt(process.env.CODEFLASH_PERF_CURRENT_BATCH || '1', 10);
114-
const globalIndex = (currentBatch - 1) * PERF_BATCH_SIZE + localIndex;
130+
const globalIndex = (currentBatch - 1) * getPerfBatchSize() + localIndex;
115131

116132
return globalIndex;
117133
}
@@ -606,7 +622,7 @@ function capture(funcName, lineId, fn, ...args) {
606622
*/
607623
function capturePerf(funcName, lineId, fn, ...args) {
608624
// Check if we should skip looping entirely (shared time budget exceeded)
609-
const shouldLoop = PERF_LOOP_COUNT > 1 && !checkSharedTimeLimit();
625+
const shouldLoop = getPerfLoopCount() > 1 && !checkSharedTimeLimit();
610626

611627
// Get test context (computed once, reused across batch)
612628
let testModulePath;
@@ -636,9 +652,9 @@ function capturePerf(funcName, lineId, fn, ...args) {
636652
// If so, just execute the function once without timing (for test assertions)
637653
const peekLoopIndex = (sharedPerfState.invocationLoopCounts[invocationKey] || 0);
638654
const currentBatch = parseInt(process.env.CODEFLASH_PERF_CURRENT_BATCH || '1', 10);
639-
const nextGlobalIndex = (currentBatch - 1) * PERF_BATCH_SIZE + peekLoopIndex + 1;
655+
const nextGlobalIndex = (currentBatch - 1) * getPerfBatchSize() + peekLoopIndex + 1;
640656

641-
if (shouldLoop && nextGlobalIndex > PERF_LOOP_COUNT) {
657+
if (shouldLoop && nextGlobalIndex > getPerfLoopCount()) {
642658
// All loops completed, just execute once for test assertion
643659
return fn(...args);
644660
}
@@ -654,7 +670,7 @@ function capturePerf(funcName, lineId, fn, ...args) {
654670
// Batched looping: run BATCH_SIZE loops per capturePerf call when using loop-runner
655671
// For Vitest (no loop-runner), do all loops internally in a single call
656672
const batchSize = shouldLoop
657-
? (hasExternalLoopRunner ? PERF_BATCH_SIZE : PERF_LOOP_COUNT)
673+
? (hasExternalLoopRunner ? getPerfBatchSize() : getPerfLoopCount())
658674
: 1;
659675

660676
for (let batchIndex = 0; batchIndex < batchSize; batchIndex++) {
@@ -667,7 +683,7 @@ function capturePerf(funcName, lineId, fn, ...args) {
667683
const loopIndex = getInvocationLoopIndex(invocationKey);
668684

669685
// Check if we've exceeded max loops for this invocation
670-
if (loopIndex > PERF_LOOP_COUNT) {
686+
if (loopIndex > getPerfLoopCount()) {
671687
break;
672688
}
673689

@@ -872,7 +888,11 @@ module.exports = {
872888
LOOP_INDEX,
873889
OUTPUT_FILE,
874890
TEST_ITERATION,
875-
// Batch configuration
876-
PERF_BATCH_SIZE,
877-
PERF_LOOP_COUNT,
891+
// Batch configuration (getter functions for dynamic env var reading)
892+
getPerfBatchSize,
893+
getPerfLoopCount,
894+
getPerfMinLoops,
895+
getPerfTargetDurationMs,
896+
getPerfStabilityCheck,
897+
getPerfCurrentBatch,
878898
};

0 commit comments

Comments
 (0)