fix: resolve Windows CI failures from path separator mismatches

Normalize paths to forward slashes in JS/TS code generation and coverage
parsing — backslashes are escape chars in JavaScript strings and cause
silent corruption on Windows. Also relax timing test thresholds for CI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mohamed Ashraf 2026-04-07 23:54:07 +00:00
parent e30bdd6748
commit 4c70a21294
5 changed files with 32 additions and 16 deletions

View file

@ -2268,7 +2268,10 @@ class JavaScriptSupport:
source_without_ext = source_file_abs.with_suffix("")
# Use os.path.relpath to compute relative path from tests_root to source file
rel_path = os.path.relpath(str(source_without_ext), str(tests_root_abs))
# Replace backslashes with forward slashes — JavaScript import/require paths
# must use forward slashes. Backslashes are escape chars in JS strings
# (e.g. \t → tab, \n → newline) and would break imports on Windows.
rel_path = os.path.relpath(str(source_without_ext), str(tests_root_abs)).replace("\\", "/")
# For ESM, add .js extension (TypeScript convention)
# TypeScript requires imports to reference the OUTPUT file extension (.js),

View file

@ -369,7 +369,9 @@ def _create_runtime_jest_config(base_config_path: Path | None, project_root: Pat
runtime_config_path = config_dir / f"jest.codeflash.runtime.config{config_ext}"
test_dirs_js = ", ".join(f"'{d}'" for d in sorted(test_dirs))
# Normalize to forward slashes — backslashes in JS strings are escape chars
# (e.g. \t → tab, \n → newline) and would corrupt paths on Windows.
test_dirs_js = ", ".join(f"'{d.replace(chr(92), '/')}'" for d in sorted(test_dirs))
# In monorepos, add the root node_modules to moduleDirectories so Jest
# can resolve workspace packages that are hoisted to the monorepo root.
@ -382,6 +384,8 @@ def _create_runtime_jest_config(base_config_path: Path | None, project_root: Pat
else:
module_dirs_line_no_base = ""
project_root_posix = project_root.as_posix()
# TypeScript configs (.ts) cannot be required from CommonJS modules
# because Node.js cannot parse TypeScript syntax in require().
# When the base config is TypeScript, we create a standalone config
@ -403,7 +407,7 @@ module.exports = {{
else:
config_content = f"""// Auto-generated by codeflash - runtime config with test roots
module.exports = {{
roots: ['{project_root}', {test_dirs_js}],
roots: ['{project_root_posix}', {test_dirs_js}],
testMatch: ['**/*.test.ts', '**/*.test.js', '**/*.test.tsx', '**/*.test.jsx'],
{module_dirs_line_no_base}}};
"""

View file

@ -54,14 +54,17 @@ class JestCoverageUtils:
return CoverageData.create_empty(source_code_path, function_name, code_context)
# Find the file entry in coverage data
# Jest uses absolute paths as keys
# Jest/Vitest always writes coverage keys with forward slashes (POSIX paths),
# so we normalize our paths to POSIX for comparison — critical on Windows
# where Path.resolve() and str(Path) produce backslash paths.
file_coverage = None
source_path_str = str(source_code_path.resolve())
source_path_posix = source_code_path.resolve().as_posix()
source_relative_posix = source_code_path.as_posix()
for file_path, file_data in coverage_data.items():
# Match exact path or path ending with full relative path from src/
# Avoid matching files with same name in different directories (e.g., db/utils.ts vs utils/utils.ts)
if file_path == source_path_str or file_path.endswith(str(source_code_path)):
if file_path == source_path_posix or file_path.endswith(source_relative_posix):
file_coverage = file_data
break

View file

@ -512,13 +512,16 @@ public class PreciseWaiterTest {
stddev_runtime = statistics.stdev(runtimes)
coefficient_of_variation = stddev_runtime / mean_runtime
# Target: 10ms (10,000,000 ns), allow <5% coefficient of variation
# (accounts for JIT warmup - first iteration is cold, subsequent are optimized)
# Target: 10ms (10,000,000 ns), allow <15% coefficient of variation.
# The first iteration per test method runs with cold JIT, and shared CI VMs
# (especially Windows) have ~15ms scheduler granularity that adds noise.
# 15% still catches instrumentation bugs (e.g., 0ms or 100ms outliers)
# while the ±5% mean check below validates timing accuracy.
expected_ns = 10_000_000
runtimes_ms = [r / 1_000_000 for r in runtimes]
assert coefficient_of_variation < 0.05, (
f"Timing variance too high: CV={coefficient_of_variation:.2%} (should be <5%). "
assert coefficient_of_variation < 0.15, (
f"Timing variance too high: CV={coefficient_of_variation:.2%} (should be <15%). "
f"Runtimes: {runtimes_ms} ms (mean={mean_runtime / 1_000_000:.3f}ms)"
)
@ -597,13 +600,16 @@ public class PreciseWaiterMultiTest {
stddev_runtime = statistics.stdev(runtimes)
coefficient_of_variation = stddev_runtime / mean_runtime
# Target: 10ms (10,000,000 ns), allow <5% coefficient of variation
# (accounts for JIT warmup - first iteration is cold, subsequent are optimized)
# Target: 10ms (10,000,000 ns), allow <15% coefficient of variation.
# The first iteration per test method runs with cold JIT, and shared CI VMs
# (especially Windows) have ~15ms scheduler granularity that adds noise.
# 15% still catches instrumentation bugs (e.g., 0ms or 100ms outliers)
# while the ±5% mean check below validates timing accuracy.
expected_ns = 10_000_000
runtimes_ms = [r / 1_000_000 for r in runtimes]
assert coefficient_of_variation < 0.05, (
f"Timing variance too high: CV={coefficient_of_variation:.2%} (should be <5%). "
assert coefficient_of_variation < 0.15, (
f"Timing variance too high: CV={coefficient_of_variation:.2%} (should be <15%). "
f"Runtimes: {runtimes_ms} ms (mean={mean_runtime / 1_000_000:.3f}ms)"
)

View file

@ -91,7 +91,7 @@ try {{
capture_output=True,
text=True,
cwd=project_path,
timeout=5,
timeout=30,
)
assert result.returncode == 0, (
@ -148,7 +148,7 @@ try {{
capture_output=True,
text=True,
cwd=project_path,
timeout=5,
timeout=30,
)
assert result.returncode == 0, f"JS config should load: {result.stderr}"