Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions codeflash/languages/javascript/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,33 @@ def _find_node_project_root(file_path: Path) -> Path | None:
return None


def _find_monorepo_root(start_path: Path) -> Path | None:
"""Find the monorepo workspace root by looking for workspace markers.

Traverses up from the given path to find a directory containing
monorepo workspace markers like yarn.lock, pnpm-workspace.yaml, etc.

Args:
start_path: A path within the monorepo.

Returns:
The monorepo root directory, or None if not found.

"""
monorepo_markers = ["yarn.lock", "pnpm-workspace.yaml", "lerna.json", "package-lock.json"]
current = start_path if start_path.is_dir() else start_path.parent

while current != current.parent:
# Check for monorepo markers
if any((current / marker).exists() for marker in monorepo_markers):
# Verify it has node_modules (it's the workspace root)
if (current / "node_modules").exists():
return current
current = current.parent

return None


def _find_jest_config(project_root: Path) -> Path | None:
"""Find Jest configuration file in the project.

Expand Down Expand Up @@ -797,6 +824,12 @@ def run_jest_benchmarking_tests(
jest_env["JEST_JUNIT_SUITE_NAME"] = "{filepath}"
jest_env["JEST_JUNIT_ADD_FILE_ATTRIBUTE"] = "true"
jest_env["JEST_JUNIT_INCLUDE_CONSOLE_OUTPUT"] = "true"

# Pass monorepo root to loop-runner for jest-runner resolution
monorepo_root = _find_monorepo_root(effective_cwd)
if monorepo_root:
jest_env["CODEFLASH_MONOREPO_ROOT"] = str(monorepo_root)
logger.debug(f"Detected monorepo root: {monorepo_root}")
codeflash_sqlite_file = get_run_tmp_file(Path("test_return_values_0.sqlite"))
jest_env["CODEFLASH_OUTPUT_FILE"] = str(codeflash_sqlite_file)
jest_env["CODEFLASH_TEST_ITERATION"] = "0"
Expand Down
45 changes: 33 additions & 12 deletions codeflash/verification/parse_test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,19 +150,31 @@ def resolve_test_file_from_class_path(test_class_path: str, base_dir: Path) -> P
# Handle file paths (contain slashes and extensions like .js/.ts)
if "/" in test_class_path or "\\" in test_class_path:
# This is a file path, not a Python module path
# Try the path as-is if it's absolute
potential_path = Path(test_class_path)
if potential_path.is_absolute() and potential_path.exists():
return potential_path

# Try to resolve relative to base_dir's parent (project root)
project_root = base_dir.parent
potential_path = project_root / test_class_path
if potential_path.exists():
return potential_path
# Normalize to resolve .. and . components
try:
potential_path = potential_path.resolve()
if potential_path.exists():
return potential_path
except (OSError, RuntimeError):
pass

# Also try relative to base_dir itself
potential_path = base_dir / test_class_path
if potential_path.exists():
return potential_path
# Try the path as-is if it's absolute
potential_path = Path(test_class_path)
if potential_path.exists():
return potential_path
try:
potential_path = potential_path.resolve()
if potential_path.exists():
return potential_path
except (OSError, RuntimeError):
pass

return None

# First try the full path (Python module path)
Expand Down Expand Up @@ -731,16 +743,25 @@ def parse_jest_test_xml(
if not test_file_path.exists():
test_file_path = base_dir / test_file_name

if test_file_path is None or not test_file_path.exists():
# For Jest tests in monorepos, test files may not exist after cleanup
# but we can still parse results and infer test type from the path
if test_file_path is None:
logger.warning(f"Could not resolve test file for Jest test: {test_class_path}")
continue

# Get test type if not already set from lookup
if test_type is None:
if test_type is None and test_file_path.exists():
test_type = test_files.get_test_type_by_instrumented_file_path(test_file_path)
if test_type is None:
# Default to GENERATED_REGRESSION for Jest tests
test_type = TestType.GENERATED_REGRESSION
# Infer test type from filename pattern
filename = test_file_path.name
if "__perf_test_" in filename or "_perf_test_" in filename:
test_type = TestType.GENERATED_PERFORMANCE
elif "__unit_test_" in filename or "_unit_test_" in filename:
test_type = TestType.GENERATED_REGRESSION
else:
# Default to GENERATED_REGRESSION for Jest tests
test_type = TestType.GENERATED_REGRESSION

# For Jest tests, keep the relative file path with extension intact
# (Python uses module_name_from_file_path which strips extensions)
Expand Down
63 changes: 62 additions & 1 deletion packages/codeflash/runtime/loop-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,74 @@

const { createRequire } = require('module');
const path = require('path');
const fs = require('fs');

/**
* Resolve jest-runner with monorepo support.
* Uses CODEFLASH_MONOREPO_ROOT environment variable if available,
* otherwise walks up the directory tree looking for node_modules/jest-runner.
*/
function resolveJestRunner() {
// Try standard resolution first (works in simple projects)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can use _find_js_project_root or _find_project_root function too to figure out the project root.

try {
return require.resolve('jest-runner');
} catch (e) {
// Standard resolution failed - try monorepo-aware resolution
}

// If Python detected a monorepo root, check there first
const monorepoRoot = process.env.CODEFLASH_MONOREPO_ROOT;
if (monorepoRoot) {
const jestRunnerPath = path.join(monorepoRoot, 'node_modules', 'jest-runner');
if (fs.existsSync(jestRunnerPath)) {
const packageJsonPath = path.join(jestRunnerPath, 'package.json');
if (fs.existsSync(packageJsonPath)) {
return jestRunnerPath;
}
}
}

// Fallback: Walk up from cwd looking for node_modules/jest-runner
const monorepoMarkers = ['yarn.lock', 'pnpm-workspace.yaml', 'lerna.json', 'package-lock.json'];
let currentDir = process.cwd();
const visitedDirs = new Set();

while (currentDir !== path.dirname(currentDir)) {
// Avoid infinite loops
if (visitedDirs.has(currentDir)) break;
visitedDirs.add(currentDir);

// Try node_modules/jest-runner at this level
const jestRunnerPath = path.join(currentDir, 'node_modules', 'jest-runner');
if (fs.existsSync(jestRunnerPath)) {
const packageJsonPath = path.join(jestRunnerPath, 'package.json');
if (fs.existsSync(packageJsonPath)) {
return jestRunnerPath;
}
}

// Check if this is a workspace root (has monorepo markers)
const isWorkspaceRoot = monorepoMarkers.some(marker =>
fs.existsSync(path.join(currentDir, marker))
);

if (isWorkspaceRoot) {
// Found workspace root but no jest-runner - stop searching
break;
}

currentDir = path.dirname(currentDir);
}

throw new Error('jest-runner not found');
}

// Try to load jest-runner - it's a peer dependency that must be installed by the user
let runTest;
let jestRunnerAvailable = false;

try {
const jestRunnerPath = require.resolve('jest-runner');
const jestRunnerPath = resolveJestRunner();
const internalRequire = createRequire(jestRunnerPath);
runTest = internalRequire('./runTest').default;
jestRunnerAvailable = true;
Expand Down
Loading