mirror of
https://github.com/codeflash-ai/codeflash.git
synced 2026-05-04 18:25:17 +00:00
Merge branch 'main' into fix/js-jest30-loop-runner
This commit is contained in:
commit
4c61d08ef9
18 changed files with 588 additions and 240 deletions
|
|
@ -359,11 +359,13 @@ def _handle_show_config() -> None:
|
|||
detected = detect_project(project_root)
|
||||
|
||||
# Check if config exists or is auto-detected
|
||||
config_exists, _ = has_existing_config(project_root)
|
||||
config_exists, config_file = has_existing_config(project_root)
|
||||
status = "Saved config" if config_exists else "Auto-detected (not saved)"
|
||||
|
||||
console.print()
|
||||
console.print(f"[bold]Codeflash Configuration[/bold] ({status})")
|
||||
if config_exists and config_file:
|
||||
console.print(f"[dim]Config file: {project_root / config_file}[/dim]")
|
||||
console.print()
|
||||
|
||||
table = Table(show_header=True, header_style="bold cyan")
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import json
|
|||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from codeflash.setup.detector import is_build_output_dir
|
||||
|
||||
PACKAGE_JSON_CACHE: dict[Path, Path] = {}
|
||||
PACKAGE_JSON_DATA_CACHE: dict[Path, dict[str, Any]] = {}
|
||||
|
||||
|
|
@ -50,12 +52,15 @@ def detect_module_root(project_root: Path, package_data: dict[str, Any]) -> str:
|
|||
"""Detect module root from package.json fields or directory conventions.
|
||||
|
||||
Detection order:
|
||||
1. package.json "exports" field (extract directory from main export)
|
||||
2. package.json "module" field (ESM entry point)
|
||||
3. package.json "main" field (CJS entry point)
|
||||
4. "src/" directory if it exists
|
||||
1. src/, lib/, source/ directories (common source directories)
|
||||
2. package.json "exports" field (if not in build output directory)
|
||||
3. package.json "module" field (ESM, if not in build output directory)
|
||||
4. package.json "main" field (CJS, if not in build output directory)
|
||||
5. Fall back to "." (project root)
|
||||
|
||||
Build output directories (build/, dist/, out/) are skipped since they contain
|
||||
compiled code, not source files.
|
||||
|
||||
Args:
|
||||
project_root: Root directory of the project.
|
||||
package_data: Parsed package.json data.
|
||||
|
|
@ -64,6 +69,11 @@ def detect_module_root(project_root: Path, package_data: dict[str, Any]) -> str:
|
|||
Detected module root path (relative to project root).
|
||||
|
||||
"""
|
||||
# Check for common source directories first - these are always preferred
|
||||
for src_dir in ["src", "lib", "source"]:
|
||||
if (project_root / src_dir).is_dir():
|
||||
return src_dir
|
||||
|
||||
# Check exports field (modern Node.js)
|
||||
exports = package_data.get("exports")
|
||||
if exports:
|
||||
|
|
@ -80,27 +90,38 @@ def detect_module_root(project_root: Path, package_data: dict[str, Any]) -> str:
|
|||
|
||||
if entry_path and isinstance(entry_path, str):
|
||||
parent = Path(entry_path).parent
|
||||
if parent != Path() and (project_root / parent).is_dir():
|
||||
if (
|
||||
parent != Path()
|
||||
and parent.as_posix() != "."
|
||||
and (project_root / parent).is_dir()
|
||||
and not is_build_output_dir(parent)
|
||||
):
|
||||
return parent.as_posix()
|
||||
|
||||
# Check module field (ESM)
|
||||
module_field = package_data.get("module")
|
||||
if module_field and isinstance(module_field, str):
|
||||
parent = Path(module_field).parent
|
||||
if parent != Path() and (project_root / parent).is_dir():
|
||||
if (
|
||||
parent != Path()
|
||||
and parent.as_posix() != "."
|
||||
and (project_root / parent).is_dir()
|
||||
and not is_build_output_dir(parent)
|
||||
):
|
||||
return parent.as_posix()
|
||||
|
||||
# Check main field (CJS)
|
||||
main_field = package_data.get("main")
|
||||
if main_field and isinstance(main_field, str):
|
||||
parent = Path(main_field).parent
|
||||
if parent != Path() and (project_root / parent).is_dir():
|
||||
if (
|
||||
parent != Path()
|
||||
and parent.as_posix() != "."
|
||||
and (project_root / parent).is_dir()
|
||||
and not is_build_output_dir(parent)
|
||||
):
|
||||
return parent.as_posix()
|
||||
|
||||
# Check for src/ directory convention
|
||||
if (project_root / "src").is_dir():
|
||||
return "src"
|
||||
|
||||
# Default to project root
|
||||
return "."
|
||||
|
||||
|
|
|
|||
|
|
@ -222,6 +222,7 @@ def _convert_destructuring_to_imports(names_str: str) -> str:
|
|||
|
||||
Returns:
|
||||
Import names string with aliases using 'as' syntax
|
||||
|
||||
"""
|
||||
# Split by commas and process each name
|
||||
parts = []
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ from typing import Any
|
|||
|
||||
import tomlkit
|
||||
|
||||
_BUILD_DIRS = frozenset({"build", "dist", "out", ".next", ".nuxt"})
|
||||
|
||||
|
||||
@dataclass
|
||||
class DetectedProject:
|
||||
|
|
@ -310,14 +312,21 @@ def _detect_js_module_root(project_root: Path) -> tuple[Path, str]:
|
|||
"""Detect JavaScript/TypeScript module root.
|
||||
|
||||
Priority:
|
||||
1. package.json "exports" field
|
||||
2. package.json "module" field (ESM)
|
||||
3. package.json "main" field (CJS)
|
||||
4. src/ directory
|
||||
5. lib/ directory
|
||||
6. Project root
|
||||
1. src/, lib/, source/ directories (common source directories)
|
||||
2. package.json "exports" field (if not in build output directory)
|
||||
3. package.json "module" field (ESM, if not in build output directory)
|
||||
4. package.json "main" field (CJS, if not in build output directory)
|
||||
5. Project root
|
||||
|
||||
Build output directories (build/, dist/, out/) are skipped since they contain
|
||||
compiled code, not source files.
|
||||
|
||||
"""
|
||||
# Check for common source directories first - these are always preferred
|
||||
for src_dir in ["src", "lib", "source"]:
|
||||
if (project_root / src_dir).is_dir():
|
||||
return project_root / src_dir, f"{src_dir}/ directory"
|
||||
|
||||
package_json_path = project_root / "package.json"
|
||||
package_data: dict[str, Any] = {}
|
||||
|
||||
|
|
@ -334,32 +343,52 @@ def _detect_js_module_root(project_root: Path) -> tuple[Path, str]:
|
|||
entry_path = _extract_entry_path(exports)
|
||||
if entry_path:
|
||||
parent = Path(entry_path).parent
|
||||
if parent != Path() and parent.as_posix() != "." and (project_root / parent).is_dir():
|
||||
if (
|
||||
parent != Path()
|
||||
and parent.as_posix() != "."
|
||||
and (project_root / parent).is_dir()
|
||||
and not is_build_output_dir(parent)
|
||||
):
|
||||
return project_root / parent, f'{parent.as_posix()}/ (from package.json "exports")'
|
||||
|
||||
# Check module field (ESM)
|
||||
module_field = package_data.get("module")
|
||||
if module_field and isinstance(module_field, str):
|
||||
parent = Path(module_field).parent
|
||||
if parent != Path() and parent.as_posix() != "." and (project_root / parent).is_dir():
|
||||
if (
|
||||
parent != Path()
|
||||
and parent.as_posix() != "."
|
||||
and (project_root / parent).is_dir()
|
||||
and not is_build_output_dir(parent)
|
||||
):
|
||||
return project_root / parent, f'{parent.as_posix()}/ (from package.json "module")'
|
||||
|
||||
# Check main field (CJS)
|
||||
main_field = package_data.get("main")
|
||||
if main_field and isinstance(main_field, str):
|
||||
parent = Path(main_field).parent
|
||||
if parent != Path() and parent.as_posix() != "." and (project_root / parent).is_dir():
|
||||
if (
|
||||
parent != Path()
|
||||
and parent.as_posix() != "."
|
||||
and (project_root / parent).is_dir()
|
||||
and not is_build_output_dir(parent)
|
||||
):
|
||||
return project_root / parent, f'{parent.as_posix()}/ (from package.json "main")'
|
||||
|
||||
# Check for common source directories
|
||||
for src_dir in ["src", "lib", "source"]:
|
||||
if (project_root / src_dir).is_dir():
|
||||
return project_root / src_dir, f"{src_dir}/ directory"
|
||||
|
||||
# Default to project root
|
||||
return project_root, "project root"
|
||||
|
||||
|
||||
def is_build_output_dir(path: Path) -> bool:
|
||||
"""Check if a path is within a common build output directory.
|
||||
|
||||
Build output directories contain compiled code and should be skipped
|
||||
in favor of source directories.
|
||||
|
||||
"""
|
||||
return not _BUILD_DIRS.isdisjoint(path.parts)
|
||||
|
||||
|
||||
def _extract_entry_path(exports: Any) -> str | None:
|
||||
"""Extract entry path from package.json exports field."""
|
||||
if isinstance(exports, str):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
# codeflash
|
||||
|
||||
AI-powered code performance optimization for JavaScript and TypeScript.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install -g codeflash
|
||||
# or
|
||||
npx codeflash
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Get your API key from [codeflash.ai](https://codeflash.ai)
|
||||
|
||||
2. Set your API key:
|
||||
```bash
|
||||
export CODEFLASH_API_KEY=your-api-key
|
||||
```
|
||||
|
||||
3. Optimize a function:
|
||||
```bash
|
||||
codeflash --file src/utils.ts --function slowFunction
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Optimize a specific function
|
||||
codeflash --file <path> --function <name>
|
||||
|
||||
# Optimize all functions in a directory
|
||||
codeflash --all src/
|
||||
|
||||
# Initialize GitHub Actions workflow
|
||||
codeflash init-actions
|
||||
|
||||
# Verify setup
|
||||
codeflash --verify-setup
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js >= 16.0.0
|
||||
- A codeflash API key
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
- Linux (x64, arm64)
|
||||
- macOS (x64, arm64)
|
||||
- Windows (x64)
|
||||
|
||||
## Documentation
|
||||
|
||||
See [codeflash.ai/docs](https://codeflash.ai/docs) for full documentation.
|
||||
|
||||
## License
|
||||
|
||||
BSL-1.1
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Wrapper script for codeflash CLI.
|
||||
* Invokes the downloaded binary with all passed arguments.
|
||||
*/
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
function getBinaryPath() {
|
||||
const binDir = __dirname;
|
||||
const isWindows = process.platform === 'win32';
|
||||
return path.join(binDir, isWindows ? 'codeflash.exe' : 'codeflash-binary');
|
||||
}
|
||||
|
||||
function main() {
|
||||
const binaryPath = getBinaryPath();
|
||||
|
||||
if (!fs.existsSync(binaryPath)) {
|
||||
console.error('\x1b[31mError: codeflash binary not found.\x1b[0m');
|
||||
console.error('Try reinstalling: npm install codeflash');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Pass all arguments to the binary
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
const child = spawn(binaryPath, args, {
|
||||
stdio: 'inherit',
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
child.on('error', (error) => {
|
||||
console.error(`\x1b[31mError running codeflash: ${error.message}\x1b[0m`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
child.on('exit', (code, signal) => {
|
||||
if (signal) {
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(code || 0);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
{
|
||||
"name": "codeflash",
|
||||
"version": "0.0.0",
|
||||
"description": "AI-powered code performance optimization - automatically find and fix slow code",
|
||||
"keywords": [
|
||||
"codeflash",
|
||||
"performance",
|
||||
"optimization",
|
||||
"ai",
|
||||
"code",
|
||||
"profiler",
|
||||
"typescript",
|
||||
"javascript"
|
||||
],
|
||||
"author": "CodeFlash Inc. <contact@codeflash.ai>",
|
||||
"license": "BSL-1.1",
|
||||
"homepage": "https://codeflash.ai",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/codeflash-ai/codeflash.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/codeflash-ai/codeflash/issues"
|
||||
},
|
||||
"bin": {
|
||||
"codeflash": "./bin/codeflash"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node lib/install.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"os": [
|
||||
"darwin",
|
||||
"linux",
|
||||
"win32"
|
||||
],
|
||||
"cpu": [
|
||||
"x64",
|
||||
"arm64"
|
||||
],
|
||||
"files": [
|
||||
"bin/",
|
||||
"lib/"
|
||||
]
|
||||
}
|
||||
4
packages/codeflash/package-lock.json
generated
4
packages/codeflash/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "codeflash",
|
||||
"version": "0.5.0",
|
||||
"version": "0.7.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "codeflash",
|
||||
"version": "0.5.0",
|
||||
"version": "0.7.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "codeflash",
|
||||
"version": "0.5.0",
|
||||
"version": "0.7.0",
|
||||
"description": "Codeflash - AI-powered code optimization for JavaScript and TypeScript",
|
||||
"main": "runtime/index.js",
|
||||
"types": "runtime/index.d.ts",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Test: Dynamic environment variable reading
|
||||
*
|
||||
* This test verifies that the performance configuration functions read
|
||||
* environment variables at runtime rather than at module load time.
|
||||
*
|
||||
* This is critical for Vitest compatibility, where modules may be cached
|
||||
* and loaded before environment variables are set.
|
||||
*
|
||||
* Run with: node __tests__/dynamic-env-vars.test.js
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
// Clear any existing env vars before loading the module
|
||||
delete process.env.CODEFLASH_PERF_LOOP_COUNT;
|
||||
delete process.env.CODEFLASH_PERF_MIN_LOOPS;
|
||||
delete process.env.CODEFLASH_PERF_TARGET_DURATION_MS;
|
||||
delete process.env.CODEFLASH_PERF_BATCH_SIZE;
|
||||
delete process.env.CODEFLASH_PERF_STABILITY_CHECK;
|
||||
delete process.env.CODEFLASH_PERF_CURRENT_BATCH;
|
||||
|
||||
// Now load the module - at this point env vars are not set
|
||||
const capture = require('../capture');
|
||||
|
||||
console.log('Testing dynamic environment variable reading...\n');
|
||||
|
||||
// Test 1: Default values when env vars are not set
|
||||
console.log('Test 1: Default values');
|
||||
assert.strictEqual(capture.getPerfLoopCount(), 1, 'getPerfLoopCount default should be 1');
|
||||
assert.strictEqual(capture.getPerfMinLoops(), 5, 'getPerfMinLoops default should be 5');
|
||||
assert.strictEqual(capture.getPerfTargetDurationMs(), 10000, 'getPerfTargetDurationMs default should be 10000');
|
||||
assert.strictEqual(capture.getPerfBatchSize(), 10, 'getPerfBatchSize default should be 10');
|
||||
assert.strictEqual(capture.getPerfStabilityCheck(), false, 'getPerfStabilityCheck default should be false');
|
||||
assert.strictEqual(capture.getPerfCurrentBatch(), 0, 'getPerfCurrentBatch default should be 0');
|
||||
console.log(' PASS: All defaults correct\n');
|
||||
|
||||
// Test 2: Values change when env vars are set AFTER module load
|
||||
// This is the critical test - if these were constants, they would still return defaults
|
||||
console.log('Test 2: Dynamic reading after module load');
|
||||
process.env.CODEFLASH_PERF_LOOP_COUNT = '100';
|
||||
process.env.CODEFLASH_PERF_MIN_LOOPS = '10';
|
||||
process.env.CODEFLASH_PERF_TARGET_DURATION_MS = '5000';
|
||||
process.env.CODEFLASH_PERF_BATCH_SIZE = '20';
|
||||
process.env.CODEFLASH_PERF_STABILITY_CHECK = 'true';
|
||||
process.env.CODEFLASH_PERF_CURRENT_BATCH = '5';
|
||||
|
||||
assert.strictEqual(capture.getPerfLoopCount(), 100, 'getPerfLoopCount should read 100 from env');
|
||||
assert.strictEqual(capture.getPerfMinLoops(), 10, 'getPerfMinLoops should read 10 from env');
|
||||
assert.strictEqual(capture.getPerfTargetDurationMs(), 5000, 'getPerfTargetDurationMs should read 5000 from env');
|
||||
assert.strictEqual(capture.getPerfBatchSize(), 20, 'getPerfBatchSize should read 20 from env');
|
||||
assert.strictEqual(capture.getPerfStabilityCheck(), true, 'getPerfStabilityCheck should read true from env');
|
||||
assert.strictEqual(capture.getPerfCurrentBatch(), 5, 'getPerfCurrentBatch should read 5 from env');
|
||||
console.log(' PASS: Dynamic reading works correctly\n');
|
||||
|
||||
// Test 3: Values change again when env vars are modified
|
||||
console.log('Test 3: Values update when env vars change');
|
||||
process.env.CODEFLASH_PERF_LOOP_COUNT = '500';
|
||||
process.env.CODEFLASH_PERF_BATCH_SIZE = '50';
|
||||
|
||||
assert.strictEqual(capture.getPerfLoopCount(), 500, 'getPerfLoopCount should update to 500');
|
||||
assert.strictEqual(capture.getPerfBatchSize(), 50, 'getPerfBatchSize should update to 50');
|
||||
console.log(' PASS: Values update correctly\n');
|
||||
|
||||
// Cleanup
|
||||
delete process.env.CODEFLASH_PERF_LOOP_COUNT;
|
||||
delete process.env.CODEFLASH_PERF_MIN_LOOPS;
|
||||
delete process.env.CODEFLASH_PERF_TARGET_DURATION_MS;
|
||||
delete process.env.CODEFLASH_PERF_BATCH_SIZE;
|
||||
delete process.env.CODEFLASH_PERF_STABILITY_CHECK;
|
||||
delete process.env.CODEFLASH_PERF_CURRENT_BATCH;
|
||||
|
||||
console.log('All tests passed!');
|
||||
|
|
@ -47,14 +47,30 @@ const TEST_MODULE = process.env.CODEFLASH_TEST_MODULE;
|
|||
// Batch 1: Test1(5 loops) → Test2(5 loops) → Test3(5 loops)
|
||||
// Batch 2: Test1(5 loops) → Test2(5 loops) → Test3(5 loops)
|
||||
// ...until time budget exhausted
|
||||
const PERF_LOOP_COUNT = parseInt(process.env.CODEFLASH_PERF_LOOP_COUNT || '1', 10);
|
||||
const PERF_MIN_LOOPS = parseInt(process.env.CODEFLASH_PERF_MIN_LOOPS || '5', 10);
|
||||
const PERF_TARGET_DURATION_MS = parseInt(process.env.CODEFLASH_PERF_TARGET_DURATION_MS || '10000', 10);
|
||||
const PERF_BATCH_SIZE = parseInt(process.env.CODEFLASH_PERF_BATCH_SIZE || '10', 10);
|
||||
const PERF_STABILITY_CHECK = (process.env.CODEFLASH_PERF_STABILITY_CHECK || 'false').toLowerCase() === 'true';
|
||||
//
|
||||
// IMPORTANT: These are getter functions, NOT constants!
|
||||
// Vitest caches modules and may load this file before env vars are set.
|
||||
// Using getter functions ensures we read the env vars at runtime when they're actually needed.
|
||||
function getPerfLoopCount() {
|
||||
return parseInt(process.env.CODEFLASH_PERF_LOOP_COUNT || '1', 10);
|
||||
}
|
||||
function getPerfMinLoops() {
|
||||
return parseInt(process.env.CODEFLASH_PERF_MIN_LOOPS || '5', 10);
|
||||
}
|
||||
function getPerfTargetDurationMs() {
|
||||
return parseInt(process.env.CODEFLASH_PERF_TARGET_DURATION_MS || '10000', 10);
|
||||
}
|
||||
function getPerfBatchSize() {
|
||||
return parseInt(process.env.CODEFLASH_PERF_BATCH_SIZE || '10', 10);
|
||||
}
|
||||
function getPerfStabilityCheck() {
|
||||
return (process.env.CODEFLASH_PERF_STABILITY_CHECK || 'false').toLowerCase() === 'true';
|
||||
}
|
||||
// Current batch number - set by loop-runner before each batch
|
||||
// This allows continuous loop indices even when Jest resets module state
|
||||
const PERF_CURRENT_BATCH = parseInt(process.env.CODEFLASH_PERF_CURRENT_BATCH || '0', 10);
|
||||
function getPerfCurrentBatch() {
|
||||
return parseInt(process.env.CODEFLASH_PERF_CURRENT_BATCH || '0', 10);
|
||||
}
|
||||
|
||||
// Stability constants (matching Python's config_consts.py)
|
||||
const STABILITY_WINDOW_SIZE = 0.35;
|
||||
|
|
@ -88,7 +104,7 @@ function checkSharedTimeLimit() {
|
|||
return false;
|
||||
}
|
||||
const elapsed = Date.now() - sharedPerfState.startTime;
|
||||
if (elapsed >= PERF_TARGET_DURATION_MS && sharedPerfState.totalLoopsCompleted >= PERF_MIN_LOOPS) {
|
||||
if (elapsed >= getPerfTargetDurationMs() && sharedPerfState.totalLoopsCompleted >= getPerfMinLoops()) {
|
||||
sharedPerfState.shouldStop = true;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -113,7 +129,7 @@ function getInvocationLoopIndex(invocationKey) {
|
|||
// Calculate global loop index using batch number from environment
|
||||
// PERF_CURRENT_BATCH is 1-based (set by loop-runner before each batch)
|
||||
const currentBatch = parseInt(process.env.CODEFLASH_PERF_CURRENT_BATCH || '1', 10);
|
||||
const globalIndex = (currentBatch - 1) * PERF_BATCH_SIZE + localIndex;
|
||||
const globalIndex = (currentBatch - 1) * getPerfBatchSize() + localIndex;
|
||||
|
||||
return globalIndex;
|
||||
}
|
||||
|
|
@ -608,7 +624,7 @@ function capture(funcName, lineId, fn, ...args) {
|
|||
*/
|
||||
function capturePerf(funcName, lineId, fn, ...args) {
|
||||
// Check if we should skip looping entirely (shared time budget exceeded)
|
||||
const shouldLoop = PERF_LOOP_COUNT > 1 && !checkSharedTimeLimit();
|
||||
const shouldLoop = getPerfLoopCount() > 1 && !checkSharedTimeLimit();
|
||||
|
||||
// Get test context (computed once, reused across batch)
|
||||
let testModulePath;
|
||||
|
|
@ -638,9 +654,9 @@ function capturePerf(funcName, lineId, fn, ...args) {
|
|||
// If so, just execute the function once without timing (for test assertions)
|
||||
const peekLoopIndex = (sharedPerfState.invocationLoopCounts[invocationKey] || 0);
|
||||
const currentBatch = parseInt(process.env.CODEFLASH_PERF_CURRENT_BATCH || '1', 10);
|
||||
const nextGlobalIndex = (currentBatch - 1) * PERF_BATCH_SIZE + peekLoopIndex + 1;
|
||||
const nextGlobalIndex = (currentBatch - 1) * getPerfBatchSize() + peekLoopIndex + 1;
|
||||
|
||||
if (shouldLoop && nextGlobalIndex > PERF_LOOP_COUNT) {
|
||||
if (shouldLoop && nextGlobalIndex > getPerfLoopCount()) {
|
||||
// All loops completed, just execute once for test assertion
|
||||
return fn(...args);
|
||||
}
|
||||
|
|
@ -656,7 +672,7 @@ function capturePerf(funcName, lineId, fn, ...args) {
|
|||
// Batched looping: run BATCH_SIZE loops per capturePerf call when using loop-runner
|
||||
// For Vitest (no loop-runner), do all loops internally in a single call
|
||||
const batchSize = shouldLoop
|
||||
? (hasExternalLoopRunner ? PERF_BATCH_SIZE : PERF_LOOP_COUNT)
|
||||
? (hasExternalLoopRunner ? getPerfBatchSize() : getPerfLoopCount())
|
||||
: 1;
|
||||
|
||||
// Initialize runtime tracking for this invocation if needed
|
||||
|
|
@ -683,7 +699,7 @@ function capturePerf(funcName, lineId, fn, ...args) {
|
|||
const loopIndex = getInvocationLoopIndex(invocationKey);
|
||||
|
||||
// Check if we've exceeded max loops for this invocation
|
||||
if (loopIndex > PERF_LOOP_COUNT) {
|
||||
if (loopIndex > getPerfLoopCount()) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -991,7 +1007,11 @@ module.exports = {
|
|||
LOOP_INDEX,
|
||||
OUTPUT_FILE,
|
||||
TEST_ITERATION,
|
||||
// Batch configuration
|
||||
PERF_BATCH_SIZE,
|
||||
PERF_LOOP_COUNT,
|
||||
// Batch configuration (getter functions for dynamic env var reading)
|
||||
getPerfBatchSize,
|
||||
getPerfLoopCount,
|
||||
getPerfMinLoops,
|
||||
getPerfTargetDurationMs,
|
||||
getPerfStabilityCheck,
|
||||
getPerfCurrentBatch,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,6 +32,67 @@
|
|||
|
||||
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)
|
||||
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 from the PROJECT's node_modules, not from codeflash package
|
||||
// This ensures we use the same version of jest-runner that the project uses
|
||||
|
|
@ -41,20 +102,7 @@ let jestRunnerAvailable = false;
|
|||
let jestVersion = 0;
|
||||
|
||||
try {
|
||||
// Resolve jest-runner from the current working directory (project root)
|
||||
// This is important because the codeflash package may bundle a different version
|
||||
const projectRoot = process.cwd();
|
||||
const projectRequire = createRequire(path.join(projectRoot, 'node_modules', 'package.json'));
|
||||
|
||||
let jestRunnerPath;
|
||||
try {
|
||||
// First try to resolve from project's node_modules
|
||||
jestRunnerPath = projectRequire.resolve('jest-runner');
|
||||
} catch (e) {
|
||||
// Fall back to default resolution (codeflash's bundled version)
|
||||
jestRunnerPath = require.resolve('jest-runner');
|
||||
}
|
||||
|
||||
const jestRunnerPath = resolveJestRunner();
|
||||
const internalRequire = createRequire(jestRunnerPath);
|
||||
|
||||
// Try to get the TestRunner class (Jest 30+)
|
||||
|
|
|
|||
|
|
@ -127,13 +127,14 @@ class TestDetectModuleRoot:
|
|||
assert result == "lib"
|
||||
|
||||
def test_detects_from_exports_object_dot(self, tmp_path: Path) -> None:
|
||||
"""Should detect module root from exports object with '.' key."""
|
||||
"""Should skip build output dirs and return '.' when no src dir exists."""
|
||||
(tmp_path / "dist").mkdir()
|
||||
package_data = {"exports": {".": "./dist/index.js"}}
|
||||
|
||||
result = detect_module_root(tmp_path, package_data)
|
||||
|
||||
assert result == "dist"
|
||||
# dist is a build output directory, so it's skipped
|
||||
assert result == "."
|
||||
|
||||
def test_detects_from_exports_object_nested(self, tmp_path: Path) -> None:
|
||||
"""Should detect module root from nested exports object."""
|
||||
|
|
@ -227,13 +228,14 @@ class TestDetectModuleRoot:
|
|||
assert result == "src"
|
||||
|
||||
def test_handles_deeply_nested_exports(self, tmp_path: Path) -> None:
|
||||
"""Should handle deeply nested export paths."""
|
||||
"""Should handle deeply nested export paths but skip build output dirs."""
|
||||
(tmp_path / "packages" / "core" / "dist").mkdir(parents=True)
|
||||
package_data = {"exports": {".": {"import": "./packages/core/dist/index.mjs"}}}
|
||||
|
||||
result = detect_module_root(tmp_path, package_data)
|
||||
|
||||
assert result == "packages/core/dist"
|
||||
# dist is a build output directory, so it's skipped even when nested
|
||||
assert result == "."
|
||||
|
||||
def test_handles_empty_exports(self, tmp_path: Path) -> None:
|
||||
"""Should handle empty exports gracefully."""
|
||||
|
|
@ -756,7 +758,7 @@ class TestRealWorldPackageJsonExamples:
|
|||
assert config["formatter_cmds"] == ["npx eslint --fix $file"]
|
||||
|
||||
def test_library_with_exports(self, tmp_path: Path) -> None:
|
||||
"""Should handle library with modern exports field."""
|
||||
"""Should handle library with modern exports field, skipping build output dirs."""
|
||||
(tmp_path / "dist").mkdir()
|
||||
package_json = tmp_path / "package.json"
|
||||
package_json.write_text(
|
||||
|
|
@ -773,7 +775,8 @@ class TestRealWorldPackageJsonExamples:
|
|||
|
||||
assert result is not None
|
||||
config, _ = result
|
||||
assert config["module_root"] == str((tmp_path / "dist").resolve())
|
||||
# dist is a build output directory, so it's skipped and falls back to project root
|
||||
assert config["module_root"] == str(tmp_path.resolve())
|
||||
|
||||
def test_monorepo_package(self, tmp_path: Path) -> None:
|
||||
"""Should handle monorepo package configuration."""
|
||||
|
|
|
|||
|
|
@ -1822,15 +1822,15 @@ export const sendSlackMessage = async (
|
|||
target_func = "sendSlackMessage"
|
||||
|
||||
functions = ts_support.discover_functions(file_path)
|
||||
func_info = next(f for f in functions if f.name == target_func)
|
||||
func_info = next(f for f in functions if f.function_name == target_func)
|
||||
fto = FunctionToOptimize(
|
||||
function_name=target_func,
|
||||
file_path=file_path,
|
||||
parents=func_info.parents,
|
||||
starting_line=func_info.start_line,
|
||||
ending_line=func_info.end_line,
|
||||
starting_col=func_info.start_col,
|
||||
ending_col=func_info.end_col,
|
||||
starting_line=func_info.starting_line,
|
||||
ending_line=func_info.ending_line,
|
||||
starting_col=func_info.starting_col,
|
||||
ending_col=func_info.ending_col,
|
||||
is_async=func_info.is_async,
|
||||
language="typescript",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from codeflash.setup.detector import (
|
|||
_find_project_root,
|
||||
detect_project,
|
||||
has_existing_config,
|
||||
is_build_output_dir,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -139,15 +140,15 @@ class TestDetectModuleRoot:
|
|||
assert "pyproject.toml" in detail
|
||||
|
||||
def test_js_detects_from_exports(self, tmp_path):
|
||||
"""Should detect module root from package.json exports."""
|
||||
"""Should detect module root from package.json exports when no common src dir exists."""
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "test",
|
||||
"exports": {".": "./src/index.js"}
|
||||
"exports": {".": "./packages/core/index.js"}
|
||||
}))
|
||||
(tmp_path / "src").mkdir()
|
||||
(tmp_path / "packages" / "core").mkdir(parents=True)
|
||||
|
||||
module_root, detail = _detect_js_module_root(tmp_path)
|
||||
assert module_root == tmp_path / "src"
|
||||
assert module_root == tmp_path / "packages" / "core"
|
||||
assert "exports" in detail
|
||||
|
||||
def test_js_detects_src_convention(self, tmp_path):
|
||||
|
|
@ -158,6 +159,214 @@ class TestDetectModuleRoot:
|
|||
module_root, detail = _detect_js_module_root(tmp_path)
|
||||
assert module_root == tmp_path / "src"
|
||||
|
||||
def test_js_prefers_src_over_build_src(self, tmp_path):
|
||||
"""Should prefer src/ over build/src/ even when package.json points to build/."""
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "test",
|
||||
"main": "build/src/index.js",
|
||||
"module": "build/src/index.js"
|
||||
}))
|
||||
(tmp_path / "src").mkdir()
|
||||
(tmp_path / "build" / "src").mkdir(parents=True)
|
||||
|
||||
module_root, detail = _detect_js_module_root(tmp_path)
|
||||
assert module_root == tmp_path / "src"
|
||||
assert "src/ directory" in detail
|
||||
|
||||
def test_js_skips_build_dir_from_main(self, tmp_path):
|
||||
"""Should skip build output directories from package.json main field."""
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "test",
|
||||
"main": "build/index.js"
|
||||
}))
|
||||
(tmp_path / "build").mkdir()
|
||||
|
||||
module_root, detail = _detect_js_module_root(tmp_path)
|
||||
assert module_root == tmp_path
|
||||
assert "project root" in detail
|
||||
|
||||
def test_js_skips_dist_dir_from_exports(self, tmp_path):
|
||||
"""Should skip dist output directories from package.json exports field."""
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "test",
|
||||
"exports": {".": "./dist/index.js"}
|
||||
}))
|
||||
(tmp_path / "dist").mkdir()
|
||||
|
||||
module_root, detail = _detect_js_module_root(tmp_path)
|
||||
assert module_root == tmp_path
|
||||
assert "project root" in detail
|
||||
|
||||
def test_js_skips_out_dir_from_module(self, tmp_path):
|
||||
"""Should skip out output directories from package.json module field."""
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "test",
|
||||
"module": "out/esm/index.js"
|
||||
}))
|
||||
(tmp_path / "out" / "esm").mkdir(parents=True)
|
||||
|
||||
module_root, detail = _detect_js_module_root(tmp_path)
|
||||
assert module_root == tmp_path
|
||||
assert "project root" in detail
|
||||
|
||||
def test_js_prefers_lib_over_build_dir(self, tmp_path):
|
||||
"""Should prefer lib/ over build output directories."""
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "test",
|
||||
"main": "dist/index.js"
|
||||
}))
|
||||
(tmp_path / "lib").mkdir()
|
||||
(tmp_path / "dist").mkdir()
|
||||
|
||||
module_root, detail = _detect_js_module_root(tmp_path)
|
||||
assert module_root == tmp_path / "lib"
|
||||
assert "lib/ directory" in detail
|
||||
|
||||
def test_js_prefers_source_over_build_dir(self, tmp_path):
|
||||
"""Should prefer source/ over build output directories."""
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "test",
|
||||
"main": "build/index.js"
|
||||
}))
|
||||
(tmp_path / "source").mkdir()
|
||||
(tmp_path / "build").mkdir()
|
||||
|
||||
module_root, detail = _detect_js_module_root(tmp_path)
|
||||
assert module_root == tmp_path / "source"
|
||||
assert "source/ directory" in detail
|
||||
|
||||
def test_js_falls_back_to_valid_exports_path(self, tmp_path):
|
||||
"""Should use exports path when no common source dirs exist and path is not build output."""
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "test",
|
||||
"exports": {".": "./packages/core/index.js"}
|
||||
}))
|
||||
(tmp_path / "packages" / "core").mkdir(parents=True)
|
||||
|
||||
module_root, detail = _detect_js_module_root(tmp_path)
|
||||
assert module_root == tmp_path / "packages" / "core"
|
||||
assert "exports" in detail
|
||||
|
||||
def test_js_falls_back_to_valid_main_path(self, tmp_path):
|
||||
"""Should use main path when no common source dirs exist and path is not build output."""
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "test",
|
||||
"main": "packages/main/index.js"
|
||||
}))
|
||||
(tmp_path / "packages" / "main").mkdir(parents=True)
|
||||
|
||||
module_root, detail = _detect_js_module_root(tmp_path)
|
||||
assert module_root == tmp_path / "packages" / "main"
|
||||
assert "main" in detail
|
||||
|
||||
def test_js_falls_back_to_valid_module_path(self, tmp_path):
|
||||
"""Should use module path when no common source dirs exist and path is not build output."""
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "test",
|
||||
"module": "esm/index.js"
|
||||
}))
|
||||
(tmp_path / "esm").mkdir()
|
||||
|
||||
module_root, detail = _detect_js_module_root(tmp_path)
|
||||
assert module_root == tmp_path / "esm"
|
||||
assert "module" in detail
|
||||
|
||||
def test_js_returns_project_root_when_all_paths_are_build_output(self, tmp_path):
|
||||
"""Should return project root when all package.json paths point to build outputs."""
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "test",
|
||||
"main": "dist/cjs/index.js",
|
||||
"module": "dist/esm/index.js",
|
||||
"exports": {".": "./build/index.js"}
|
||||
}))
|
||||
(tmp_path / "dist" / "cjs").mkdir(parents=True)
|
||||
(tmp_path / "dist" / "esm").mkdir(parents=True)
|
||||
(tmp_path / "build").mkdir()
|
||||
|
||||
module_root, detail = _detect_js_module_root(tmp_path)
|
||||
assert module_root == tmp_path
|
||||
assert "project root" in detail
|
||||
|
||||
def test_js_handles_malformed_package_json(self, tmp_path):
|
||||
"""Should handle malformed package.json gracefully."""
|
||||
(tmp_path / "package.json").write_text("{ invalid json }")
|
||||
|
||||
module_root, detail = _detect_js_module_root(tmp_path)
|
||||
assert module_root == tmp_path
|
||||
assert "project root" in detail
|
||||
|
||||
|
||||
class TestIsBuildOutputDir:
|
||||
"""Tests for is_build_output_dir function."""
|
||||
|
||||
def test_detects_build_dir(self):
|
||||
"""Should detect build/ as build output."""
|
||||
from pathlib import Path
|
||||
assert is_build_output_dir(Path("build"))
|
||||
assert is_build_output_dir(Path("build/src"))
|
||||
assert is_build_output_dir(Path("build/src/index.js"))
|
||||
|
||||
def test_detects_dist_dir(self):
|
||||
"""Should detect dist/ as build output."""
|
||||
from pathlib import Path
|
||||
assert is_build_output_dir(Path("dist"))
|
||||
assert is_build_output_dir(Path("dist/esm"))
|
||||
assert is_build_output_dir(Path("dist/cjs/index.js"))
|
||||
|
||||
def test_detects_out_dir(self):
|
||||
"""Should detect out/ as build output."""
|
||||
from pathlib import Path
|
||||
assert is_build_output_dir(Path("out"))
|
||||
assert is_build_output_dir(Path("out/src"))
|
||||
|
||||
def test_detects_next_dir(self):
|
||||
"""Should detect .next/ as build output."""
|
||||
from pathlib import Path
|
||||
assert is_build_output_dir(Path(".next"))
|
||||
assert is_build_output_dir(Path(".next/static"))
|
||||
|
||||
def test_detects_nuxt_dir(self):
|
||||
"""Should detect .nuxt/ as build output."""
|
||||
from pathlib import Path
|
||||
assert is_build_output_dir(Path(".nuxt"))
|
||||
assert is_build_output_dir(Path(".nuxt/dist"))
|
||||
|
||||
def test_detects_nested_build_dir(self):
|
||||
"""Should detect build dir nested in path."""
|
||||
from pathlib import Path
|
||||
assert is_build_output_dir(Path("packages/build/index.js"))
|
||||
assert is_build_output_dir(Path("foo/dist/bar"))
|
||||
|
||||
def test_does_not_detect_src(self):
|
||||
"""Should not detect src/ as build output."""
|
||||
from pathlib import Path
|
||||
assert not is_build_output_dir(Path("src"))
|
||||
assert not is_build_output_dir(Path("src/index.js"))
|
||||
|
||||
def test_does_not_detect_lib(self):
|
||||
"""Should not detect lib/ as build output."""
|
||||
from pathlib import Path
|
||||
assert not is_build_output_dir(Path("lib"))
|
||||
assert not is_build_output_dir(Path("lib/utils"))
|
||||
|
||||
def test_does_not_detect_source(self):
|
||||
"""Should not detect source/ as build output."""
|
||||
from pathlib import Path
|
||||
assert not is_build_output_dir(Path("source"))
|
||||
|
||||
def test_does_not_detect_packages(self):
|
||||
"""Should not detect packages/ as build output."""
|
||||
from pathlib import Path
|
||||
assert not is_build_output_dir(Path("packages"))
|
||||
assert not is_build_output_dir(Path("packages/core"))
|
||||
|
||||
def test_does_not_detect_similar_names(self):
|
||||
"""Should not detect directories with similar but different names."""
|
||||
from pathlib import Path
|
||||
assert not is_build_output_dir(Path("builder"))
|
||||
assert not is_build_output_dir(Path("distribution"))
|
||||
assert not is_build_output_dir(Path("output"))
|
||||
|
||||
|
||||
class TestDetectTestsRoot:
|
||||
"""Tests for tests root detection."""
|
||||
|
|
|
|||
|
|
@ -857,6 +857,48 @@ class TestE2ECLIFlags:
|
|||
# Should complete without error
|
||||
_handle_show_config()
|
||||
|
||||
def test_show_config_displays_config_path_when_saved(self, project_with_existing_config, monkeypatch):
|
||||
"""Should display config file path when saved config exists."""
|
||||
monkeypatch.chdir(project_with_existing_config)
|
||||
|
||||
# Track what gets printed
|
||||
printed_messages = []
|
||||
|
||||
def mock_print(msg="", *args, **kwargs):
|
||||
printed_messages.append(str(msg))
|
||||
|
||||
from codeflash.cli_cmds import console
|
||||
monkeypatch.setattr(console.console, "print", mock_print)
|
||||
|
||||
from codeflash.cli_cmds.cli import _handle_show_config
|
||||
_handle_show_config()
|
||||
|
||||
# Verify config path is displayed
|
||||
all_output = "\n".join(printed_messages)
|
||||
assert "pyproject.toml" in all_output
|
||||
assert "Config file:" in all_output
|
||||
|
||||
def test_show_config_no_path_when_auto_detected(self, python_src_layout, monkeypatch):
|
||||
"""Should not display config file path when config is auto-detected."""
|
||||
monkeypatch.chdir(python_src_layout)
|
||||
|
||||
# Track what gets printed
|
||||
printed_messages = []
|
||||
|
||||
def mock_print(msg="", *args, **kwargs):
|
||||
printed_messages.append(str(msg))
|
||||
|
||||
from codeflash.cli_cmds import console
|
||||
monkeypatch.setattr(console.console, "print", mock_print)
|
||||
|
||||
from codeflash.cli_cmds.cli import _handle_show_config
|
||||
_handle_show_config()
|
||||
|
||||
# Verify no config path line is displayed
|
||||
all_output = "\n".join(printed_messages)
|
||||
assert "Config file:" not in all_output
|
||||
assert "Auto-detected" in all_output
|
||||
|
||||
def test_reset_config_removes_from_pyproject(self, project_with_existing_config, monkeypatch):
|
||||
"""Should remove codeflash config from pyproject.toml."""
|
||||
monkeypatch.chdir(project_with_existing_config)
|
||||
|
|
|
|||
Loading…
Reference in a new issue