Skip to content

Commit 19cf40d

Browse files
authored
Merge pull request #1311 from codeflash-ai/fix/dynamic-env-var-reading
fix: use getter functions for perf env vars to support Vitest module caching
2 parents 4a7432e + e235a0a commit 19cf40d

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)