Skip to content

Commit 4e39408

Browse files
committed
Merge remote-tracking branch 'origin/multi-language' into multi-language
2 parents 8821d73 + 53e0c71 commit 4e39408

5 files changed

Lines changed: 49 additions & 20 deletions

File tree

codeflash/discovery/discover_unit_tests.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -554,9 +554,7 @@ def filter_test_files_by_imports(
554554
return filtered_map
555555

556556

557-
def _detect_language_from_functions(
558-
file_to_funcs: dict[Path, list[FunctionToOptimize]] | None,
559-
) -> str | None:
557+
def _detect_language_from_functions(file_to_funcs: dict[Path, list[FunctionToOptimize]] | None) -> str | None:
560558
"""Detect language from the functions to optimize.
561559
562560
Args:
@@ -576,9 +574,7 @@ def _detect_language_from_functions(
576574

577575

578576
def discover_tests_for_language(
579-
cfg: TestConfig,
580-
language: str,
581-
file_to_funcs_to_optimize: dict[Path, list[FunctionToOptimize]] | None,
577+
cfg: TestConfig, language: str, file_to_funcs_to_optimize: dict[Path, list[FunctionToOptimize]] | None
582578
) -> tuple[dict[str, set[FunctionCalledInTest]], int, int]:
583579
"""Discover tests using language-specific support.
584580
@@ -656,6 +652,10 @@ def discover_unit_tests(
656652

657653
# Route to language-specific test discovery for non-Python languages
658654
if language and language != "python":
655+
# For JavaScript/TypeScript, tests_project_rootdir should be tests_root itself
656+
# The Jest helper will be configured to NOT include "tests." prefix to match
657+
if language in ("javascript", "typescript"):
658+
cfg.tests_project_rootdir = cfg.tests_root
659659
return discover_tests_for_language(cfg, language, file_to_funcs_to_optimize)
660660

661661
# Existing Python logic

codeflash/languages/javascript/runtime/codeflash-jest-helper.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,12 @@ function capture(funcName, lineId, fn, ...args) {
301301
// Get relative path from cwd and convert to module-style path
302302
const path = require('path');
303303
const relativePath = path.relative(process.cwd(), currentTestPath);
304-
// Convert to Python module-style path (e.g., "tests/test_foo.test.js" -> "tests.test_foo.test")
305-
// This matches what Jest's junit XML produces
304+
// Convert to module-style path (e.g., "tests/test_foo.test.js" -> "test_foo.test")
305+
// Strip the leading "tests/" directory to match what Jest's junit XML produces
306+
// and what module_name_from_file_path(test_file, tests_root) generates
306307
testModulePath = relativePath
307308
.replace(/\\/g, '/') // Handle Windows paths
309+
.replace(/^tests\//, '') // Strip leading "tests/" directory
308310
.replace(/\.js$/, '') // Remove .js extension
309311
.replace(/\.test$/, '.test') // Keep .test suffix
310312
.replace(/\//g, '.'); // Convert path separators to dots
@@ -404,9 +406,11 @@ function capturePerf(funcName, lineId, fn, ...args) {
404406
// Get relative path from cwd and convert to module-style path
405407
const path = require('path');
406408
const relativePath = path.relative(process.cwd(), currentTestPath);
407-
// Convert to Python module-style path (e.g., "tests/test_foo.test.js" -> "tests.test_foo.test")
409+
// Convert to module-style path (e.g., "tests/test_foo.test.js" -> "test_foo.test")
410+
// Strip leading "tests/" to match XML module path format
408411
testModulePath = relativePath
409412
.replace(/\\/g, '/')
413+
.replace(/^tests\//, '') // Strip leading "tests/" directory
410414
.replace(/\.js$/, '')
411415
.replace(/\.test$/, '.test')
412416
.replace(/\//g, '.');
@@ -544,9 +548,11 @@ function capturePerfLooped(funcName, lineId, fn, ...args) {
544548
// Get relative path from cwd and convert to module-style path
545549
const path = require('path');
546550
const relativePath = path.relative(process.cwd(), currentTestPath);
547-
// Convert to Python module-style path (e.g., "tests/test_foo.test.js" -> "tests.test_foo.test")
551+
// Convert to module-style path (e.g., "tests/test_foo.test.js" -> "test_foo.test")
552+
// Strip leading "tests/" to match XML module path format
548553
testModulePath = relativePath
549554
.replace(/\\/g, '/')
555+
.replace(/^tests\//, '') // Strip leading "tests/" directory
550556
.replace(/\.js$/, '')
551557
.replace(/\.test$/, '.test')
552558
.replace(/\//g, '.');

codeflash/optimization/function_optimizer.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -487,8 +487,7 @@ def __init__(
487487
rel_path = os.path.relpath(str(source_file_abs), str(tests_root_abs))
488488
self.original_module_path = rel_path
489489
logger.debug(
490-
f"!lsp|JS module path: source={source_file_abs}, "
491-
f"tests_root={tests_root_abs}, rel_path={rel_path}"
490+
f"!lsp|JS module path: source={source_file_abs}, tests_root={tests_root_abs}, rel_path={rel_path}"
492491
)
493492
except ValueError:
494493
# Fallback if paths are on different drives (Windows)
@@ -600,6 +599,24 @@ def generate_and_instrument_tests(
600599
for test_index in range(n_tests)
601600
]
602601

602+
# For JavaScript/TypeScript, copy all runtime files to tests directory
603+
if language in ("javascript", "typescript"):
604+
import shutil
605+
606+
from codeflash.languages.javascript.runtime import get_all_runtime_files
607+
608+
# Copy all runtime files (helper, serializer, comparator, etc.)
609+
for runtime_file_source in get_all_runtime_files():
610+
runtime_file_dest = self.test_cfg.tests_root / runtime_file_source.name
611+
612+
# Copy file if it doesn't exist or is outdated
613+
if (
614+
not runtime_file_dest.exists()
615+
or runtime_file_source.stat().st_mtime > runtime_file_dest.stat().st_mtime
616+
):
617+
shutil.copy2(runtime_file_source, runtime_file_dest)
618+
logger.debug(f"Copied {runtime_file_source.name} to {runtime_file_dest}")
619+
603620
test_results = self.generate_tests(
604621
testgen_context=code_context.testgen_context,
605622
helper_functions=code_context.helper_functions,
@@ -854,7 +871,9 @@ def handle_successful_candidate(
854871
else:
855872
with progress_bar("Running line-by-line profiling"):
856873
line_profile_test_results = self.line_profiler_step(
857-
code_context=code_context, original_helper_code=original_helper_code, candidate_index=candidate_index
874+
code_context=code_context,
875+
original_helper_code=original_helper_code,
876+
candidate_index=candidate_index,
858877
)
859878

860879
eval_ctx.record_line_profiler_result(candidate.optimization_id, line_profile_test_results["str_out"])
@@ -2231,6 +2250,7 @@ def establish_original_code_baseline(
22312250
for result in behavioral_results
22322251
if (result.test_type == TestType.GENERATED_REGRESSION and not result.did_pass)
22332252
]
2253+
22342254
if total_timing == 0:
22352255
logger.warning("The overall summed benchmark runtime of the original function is 0, couldn't run tests.")
22362256
console.rule()
@@ -2414,7 +2434,9 @@ def run_optimized_candidate(
24142434
logger.error(f"Candidate SQLite database not found: {candidate_sqlite}")
24152435
logger.debug("No diffs found, skipping repair")
24162436
# Use Python-style comparison on TestResults as fallback
2417-
match, diffs = compare_test_results(baseline_results.behavior_test_results, candidate_behavior_results)
2437+
match, diffs = compare_test_results(
2438+
baseline_results.behavior_test_results, candidate_behavior_results
2439+
)
24182440
else:
24192441
# Python: Compare using Python comparator
24202442
match, diffs = compare_test_results(baseline_results.behavior_test_results, candidate_behavior_results)

codeflash/verification/parse_test_output.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,7 @@ def resolve_test_file_from_class_path(test_class_path: str, base_dir: Path) -> P
140140

141141

142142
def parse_jest_json_results(
143-
file_location: Path,
144-
test_files: TestFiles,
145-
test_config: TestConfig,
146-
function_name: str | None = None,
143+
file_location: Path, test_files: TestFiles, test_config: TestConfig, function_name: str | None = None
147144
) -> TestResults:
148145
"""Parse Jest test results from JSON format written by codeflash-jest-helper.
149146
@@ -362,7 +359,7 @@ def parse_sqlite_test_results(sqlite_file_path: Path, test_files: TestFiles, tes
362359
else:
363360
# Python uses pickle serialization
364361
ret_val = (pickle.loads(val[7]),)
365-
except Exception as e: # noqa: S112
362+
except Exception as e:
366363
# If deserialization fails, skip this result
367364
logger.debug(f"Failed to deserialize return value for {test_function_name}: {e}")
368365
continue
@@ -405,6 +402,7 @@ def _extract_jest_console_output(suite_elem) -> str:
405402
406403
Returns:
407404
Concatenated message content from all log entries
405+
408406
"""
409407
import json
410408

@@ -455,6 +453,7 @@ def parse_jest_test_xml(
455453
456454
Returns:
457455
TestResults containing parsed test invocations
456+
458457
"""
459458
test_results = TestResults()
460459

@@ -1028,6 +1027,7 @@ def parse_test_results(
10281027
# (comparison happens in JavaScript land via compare_javascript_test_results)
10291028
if not skip_sqlite_cleanup:
10301029
get_run_tmp_file(Path(f"test_return_values_{optimization_iteration}.sqlite")).unlink(missing_ok=True)
1030+
10311031
results = merge_test_results(test_results_xml, test_results_data, test_config.test_framework)
10321032

10331033
all_args = False
@@ -1060,6 +1060,7 @@ def parse_test_results(
10601060

10611061
# Cleanup Jest coverage directory after coverage is parsed
10621062
import shutil
1063+
10631064
jest_coverage_dir = get_run_tmp_file(Path("jest_coverage"))
10641065
if jest_coverage_dir.exists():
10651066
shutil.rmtree(jest_coverage_dir, ignore_errors=True)

codeflash/verification/verifier.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def generate_tests(
2727
test_index: int,
2828
test_path: Path,
2929
test_perf_path: Path,
30-
) -> tuple[str, str, Path] | None:
30+
) -> tuple[str, str, str, Path, Path] | None:
3131
# TODO: Sometimes this recreates the original Class definition. This overrides and messes up the original
3232
# class import. Remove the recreation of the class definition
3333
start_time = time.perf_counter()

0 commit comments

Comments
 (0)