"""Tests for JavaScript/Jest test runner functionality.""" import sys import tempfile from pathlib import Path from unittest.mock import patch, MagicMock import pytest class TestJestRootsConfiguration: """Tests for Jest runtime config creation when test files are outside the project root.""" def test_no_runtime_config_when_tests_inside_project_root(self): """Test that no runtime config is created when test files are inside the project root.""" from codeflash.languages.javascript.test_runner import clear_created_config_files, get_created_config_files, run_jest_behavioral_tests from codeflash.models.models import TestFile, TestFiles from codeflash.models.test_type import TestType with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir).resolve() test_dir = tmpdir_path / "test" test_dir.mkdir() (tmpdir_path / "package.json").write_text('{"name": "test"}') test_file1 = test_dir / "test_func__unit_test_0.test.ts" test_file1.write_text("// test 1") mock_test_files = TestFiles( test_files=[ TestFile( original_file_path=test_file1, instrumented_behavior_file_path=test_file1, benchmarking_file_path=test_file1, test_type=TestType.GENERATED_REGRESSION, ), ] ) clear_created_config_files() with patch("subprocess.run") as mock_run: mock_result = MagicMock() mock_result.stdout = "" mock_result.stderr = "" mock_result.returncode = 1 mock_run.return_value = mock_result try: run_jest_behavioral_tests( test_paths=mock_test_files, test_env={}, cwd=tmpdir_path, project_root=tmpdir_path, ) except Exception: pass if mock_run.called: cmd = mock_run.call_args[0][0] # No --roots flags should be present assert "--roots" not in cmd, "Should not have --roots flags when tests are inside project root" # No runtime config should have been created runtime_configs = [f for f in get_created_config_files() if "codeflash.runtime" in f.name] assert len(runtime_configs) == 0, "Should not create runtime config when tests are inside project root" clear_created_config_files() def test_behavioral_tests_creates_runtime_config_for_external_tests(self): """Test that run_jest_behavioral_tests creates a runtime config when tests are outside the project root.""" from codeflash.languages.javascript.test_runner import clear_created_config_files, get_created_config_files, run_jest_behavioral_tests from codeflash.models.models import TestFile, TestFiles from codeflash.models.test_type import TestType with tempfile.TemporaryDirectory() as project_dir, tempfile.TemporaryDirectory() as external_dir: project_path = Path(project_dir).resolve() external_path = Path(external_dir).resolve() (project_path / "package.json").write_text('{"name": "test"}') test_file = external_path / "test_func__unit_test_0.test.ts" test_file.write_text("// test 1") mock_test_files = TestFiles( test_files=[ TestFile( original_file_path=test_file, instrumented_behavior_file_path=test_file, benchmarking_file_path=test_file, test_type=TestType.GENERATED_REGRESSION, ), ] ) clear_created_config_files() with patch("subprocess.run") as mock_run: mock_result = MagicMock() mock_result.stdout = "" mock_result.stderr = "" mock_result.returncode = 1 mock_run.return_value = mock_result try: run_jest_behavioral_tests( test_paths=mock_test_files, test_env={}, cwd=project_path, project_root=project_path, ) except Exception: pass if mock_run.called: cmd = mock_run.call_args[0][0] config_args = [arg for arg in cmd if arg.startswith("--config=")] assert any("codeflash.runtime" in arg for arg in config_args), ( f"Expected runtime config in --config flag, got: {config_args}" ) runtime_configs = [f for f in get_created_config_files() if "codeflash.runtime" in f.name] assert len(runtime_configs) == 1, f"Expected 1 runtime config, got {len(runtime_configs)}" config_content = runtime_configs[0].read_text(encoding="utf-8") assert external_path.as_posix() in config_content, "Runtime config should contain external test directory" clear_created_config_files() def test_benchmarking_tests_creates_runtime_config_for_external_tests(self): """Test that run_jest_benchmarking_tests creates a runtime config when tests are outside the project root.""" from codeflash.languages.javascript.test_runner import clear_created_config_files, get_created_config_files, run_jest_benchmarking_tests from codeflash.models.models import TestFile, TestFiles from codeflash.models.test_type import TestType with tempfile.TemporaryDirectory() as project_dir, tempfile.TemporaryDirectory() as external_dir: project_path = Path(project_dir).resolve() external_path = Path(external_dir).resolve() (project_path / "package.json").write_text('{"name": "test"}') test_file = external_path / "test_func__perf_test_0.test.ts" test_file.write_text("// perf test") mock_test_files = TestFiles( test_files=[ TestFile( original_file_path=test_file, instrumented_behavior_file_path=test_file, benchmarking_file_path=test_file, test_type=TestType.GENERATED_REGRESSION, ), ] ) clear_created_config_files() with patch("subprocess.run") as mock_run: mock_result = MagicMock() mock_result.stdout = "" mock_result.stderr = "" mock_result.returncode = 1 mock_run.return_value = mock_result try: run_jest_benchmarking_tests( test_paths=mock_test_files, test_env={}, cwd=project_path, project_root=project_path, ) except Exception: pass runtime_configs = [f for f in get_created_config_files() if "codeflash.runtime" in f.name] assert len(runtime_configs) == 1, "Expected runtime config for external test files" clear_created_config_files() def test_line_profile_tests_creates_runtime_config_for_external_tests(self): """Test that run_jest_line_profile_tests creates a runtime config when tests are outside the project root.""" from codeflash.languages.javascript.test_runner import clear_created_config_files, get_created_config_files, run_jest_line_profile_tests from codeflash.models.models import TestFile, TestFiles from codeflash.models.test_type import TestType with tempfile.TemporaryDirectory() as project_dir, tempfile.TemporaryDirectory() as external_dir: project_path = Path(project_dir).resolve() external_path = Path(external_dir).resolve() (project_path / "package.json").write_text('{"name": "test"}') test_file = external_path / "test_func__line_profile.test.ts" test_file.write_text("// line profile test") mock_test_files = TestFiles( test_files=[ TestFile( original_file_path=test_file, instrumented_behavior_file_path=test_file, benchmarking_file_path=test_file, test_type=TestType.GENERATED_REGRESSION, ), ] ) clear_created_config_files() with patch("subprocess.run") as mock_run: mock_result = MagicMock() mock_result.stdout = "" mock_result.stderr = "" mock_result.returncode = 1 mock_run.return_value = mock_result try: run_jest_line_profile_tests( test_paths=mock_test_files, test_env={}, cwd=project_path, project_root=project_path, ) except Exception: pass runtime_configs = [f for f in get_created_config_files() if "codeflash.runtime" in f.name] assert len(runtime_configs) == 1, "Expected runtime config for external test files" clear_created_config_files() class TestVitestTimeoutConfiguration: """Tests for Vitest subprocess timeout handling.""" def test_vitest_behavioral_subprocess_timeout_larger_than_test_timeout(self): """Test that subprocess timeout is larger than per-test timeout for Vitest behavioral tests.""" from codeflash.languages.javascript.vitest_runner import run_vitest_behavioral_tests from codeflash.models.models import TestFile, TestFiles from codeflash.models.test_type import TestType with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) test_dir = tmpdir_path / "test" test_dir.mkdir() (tmpdir_path / "package.json").write_text('{"name": "test", "devDependencies": {"vitest": "^1.0.0"}}') test_file = test_dir / "test_func.test.ts" test_file.write_text("// test") mock_test_files = TestFiles( test_files=[ TestFile( original_file_path=test_file, instrumented_behavior_file_path=test_file, benchmarking_file_path=test_file, test_type=TestType.GENERATED_REGRESSION, ), ] ) with patch("subprocess.run") as mock_run: mock_result = MagicMock() mock_result.stdout = "" mock_result.stderr = "" mock_result.returncode = 0 mock_run.return_value = mock_result # Run with a 15 second per-test timeout run_vitest_behavioral_tests( test_paths=mock_test_files, test_env={}, cwd=tmpdir_path, timeout=15, # 15 second per-test timeout project_root=tmpdir_path, ) # Verify subprocess was called with a larger timeout assert mock_run.called call_kwargs = mock_run.call_args[1] subprocess_timeout = call_kwargs.get("timeout") # Subprocess timeout should be at least 120 seconds (minimum) # or 10x the per-test timeout (150 seconds) assert subprocess_timeout >= 120, f"Expected subprocess timeout >= 120s, got {subprocess_timeout}s" assert subprocess_timeout >= 15 * 10, f"Expected subprocess timeout >= 150s (10x per-test), got {subprocess_timeout}s" def test_vitest_line_profile_subprocess_timeout_larger_than_test_timeout(self): """Test that subprocess timeout is larger than per-test timeout for Vitest line profile tests.""" from codeflash.languages.javascript.vitest_runner import run_vitest_line_profile_tests from codeflash.models.models import TestFile, TestFiles from codeflash.models.test_type import TestType with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) test_dir = tmpdir_path / "test" test_dir.mkdir() (tmpdir_path / "package.json").write_text('{"name": "test", "devDependencies": {"vitest": "^1.0.0"}}') test_file = test_dir / "test_func.test.ts" test_file.write_text("// test") mock_test_files = TestFiles( test_files=[ TestFile( original_file_path=test_file, instrumented_behavior_file_path=test_file, benchmarking_file_path=test_file, test_type=TestType.GENERATED_REGRESSION, ), ] ) with patch("subprocess.run") as mock_run: mock_result = MagicMock() mock_result.stdout = "" mock_result.stderr = "" mock_result.returncode = 0 mock_run.return_value = mock_result run_vitest_line_profile_tests( test_paths=mock_test_files, test_env={}, cwd=tmpdir_path, timeout=15, project_root=tmpdir_path, ) assert mock_run.called call_kwargs = mock_run.call_args[1] subprocess_timeout = call_kwargs.get("timeout") assert subprocess_timeout >= 120, f"Expected subprocess timeout >= 120s, got {subprocess_timeout}s" def test_vitest_default_subprocess_timeout_is_reasonable(self): """Test that default subprocess timeout is at least 120 seconds when no timeout specified.""" from codeflash.languages.javascript.vitest_runner import run_vitest_behavioral_tests from codeflash.models.models import TestFile, TestFiles from codeflash.models.test_type import TestType with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) test_dir = tmpdir_path / "test" test_dir.mkdir() (tmpdir_path / "package.json").write_text('{"name": "test", "devDependencies": {"vitest": "^1.0.0"}}') test_file = test_dir / "test_func.test.ts" test_file.write_text("// test") mock_test_files = TestFiles( test_files=[ TestFile( original_file_path=test_file, instrumented_behavior_file_path=test_file, benchmarking_file_path=test_file, test_type=TestType.GENERATED_REGRESSION, ), ] ) with patch("subprocess.run") as mock_run: mock_result = MagicMock() mock_result.stdout = "" mock_result.stderr = "" mock_result.returncode = 0 mock_run.return_value = mock_result # Run without specifying a timeout run_vitest_behavioral_tests( test_paths=mock_test_files, test_env={}, cwd=tmpdir_path, project_root=tmpdir_path, ) assert mock_run.called call_kwargs = mock_run.call_args[1] subprocess_timeout = call_kwargs.get("timeout") # Default should be at least 120 seconds (or 600 from the default) assert subprocess_timeout >= 120, f"Expected subprocess timeout >= 120s, got {subprocess_timeout}s" class TestVitestInternalLoopingConfiguration: """Tests for Vitest internal looping (no external loop-runner).""" def test_vitest_benchmarking_does_not_set_current_batch_env(self): """Test that Vitest runner does NOT set CODEFLASH_PERF_CURRENT_BATCH. This is critical: when CODEFLASH_PERF_CURRENT_BATCH is not set, capturePerf() in the npm package will do all loops internally (PERF_LOOP_COUNT iterations) instead of just PERF_BATCH_SIZE. """ from codeflash.languages.javascript.vitest_runner import run_vitest_benchmarking_tests from codeflash.models.models import TestFile, TestFiles from codeflash.models.test_type import TestType with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) test_dir = tmpdir_path / "test" test_dir.mkdir() (tmpdir_path / "package.json").write_text('{"name": "test", "devDependencies": {"vitest": "^1.0.0"}}') test_file = test_dir / "test_func.test.ts" test_file.write_text("// perf test") mock_test_files = TestFiles( test_files=[ TestFile( original_file_path=test_file, instrumented_behavior_file_path=test_file, benchmarking_file_path=test_file, test_type=TestType.GENERATED_REGRESSION, ), ] ) with patch("subprocess.run") as mock_run: mock_result = MagicMock() mock_result.stdout = "" mock_result.stderr = "" mock_result.returncode = 0 mock_run.return_value = mock_result run_vitest_benchmarking_tests( test_paths=mock_test_files, test_env={}, cwd=tmpdir_path, project_root=tmpdir_path, max_loops=100, min_loops=5, ) assert mock_run.called call_kwargs = mock_run.call_args[1] env = call_kwargs.get("env", {}) # CODEFLASH_PERF_CURRENT_BATCH should NOT be set # This allows capturePerf() to do all loops internally assert "CODEFLASH_PERF_CURRENT_BATCH" not in env, ( "CODEFLASH_PERF_CURRENT_BATCH should not be set for Vitest - " "internal looping relies on this being undefined" ) # But CODEFLASH_PERF_LOOP_COUNT should be set assert "CODEFLASH_PERF_LOOP_COUNT" in env, "CODEFLASH_PERF_LOOP_COUNT should be set" assert env["CODEFLASH_PERF_LOOP_COUNT"] == "100" def test_vitest_benchmarking_sets_loop_configuration_env_vars(self): """Test that Vitest benchmarking sets correct loop configuration environment variables.""" from codeflash.languages.javascript.vitest_runner import run_vitest_benchmarking_tests from codeflash.models.models import TestFile, TestFiles from codeflash.models.test_type import TestType with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) test_dir = tmpdir_path / "test" test_dir.mkdir() (tmpdir_path / "package.json").write_text('{"name": "test", "devDependencies": {"vitest": "^1.0.0"}}') test_file = test_dir / "test_func.test.ts" test_file.write_text("// perf test") mock_test_files = TestFiles( test_files=[ TestFile( original_file_path=test_file, instrumented_behavior_file_path=test_file, benchmarking_file_path=test_file, test_type=TestType.GENERATED_REGRESSION, ), ] ) with patch("subprocess.run") as mock_run: mock_result = MagicMock() mock_result.stdout = "" mock_result.stderr = "" mock_result.returncode = 0 mock_run.return_value = mock_result run_vitest_benchmarking_tests( test_paths=mock_test_files, test_env={}, cwd=tmpdir_path, project_root=tmpdir_path, max_loops=50, min_loops=10, target_duration_ms=5000, stability_check=True, ) assert mock_run.called call_kwargs = mock_run.call_args[1] env = call_kwargs.get("env", {}) # Verify all loop configuration env vars are set correctly assert env.get("CODEFLASH_PERF_LOOP_COUNT") == "50" assert env.get("CODEFLASH_PERF_MIN_LOOPS") == "10" assert env.get("CODEFLASH_PERF_TARGET_DURATION_MS") == "5000" assert env.get("CODEFLASH_PERF_STABILITY_CHECK") == "true" assert env.get("CODEFLASH_MODE") == "performance" class TestBundlerModuleResolutionFix: """Tests for bundler moduleResolution compatibility fix.""" def test_detect_bundler_module_resolution_true(self): """Test detection of bundler moduleResolution in tsconfig.""" import json from codeflash.languages.javascript.test_runner import _detect_bundler_module_resolution with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) # Create tsconfig with bundler moduleResolution tsconfig = { "compilerOptions": { "moduleResolution": "bundler", "module": "preserve", "target": "ES2022", } } (tmpdir_path / "tsconfig.json").write_text(json.dumps(tsconfig)) assert _detect_bundler_module_resolution(tmpdir_path) is True def test_detect_bundler_module_resolution_false(self): """Test detection returns false for Node moduleResolution.""" import json from codeflash.languages.javascript.test_runner import _detect_bundler_module_resolution with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) # Create tsconfig with Node moduleResolution tsconfig = { "compilerOptions": { "moduleResolution": "Node", "module": "ESNext", } } (tmpdir_path / "tsconfig.json").write_text(json.dumps(tsconfig)) assert _detect_bundler_module_resolution(tmpdir_path) is False def test_detect_bundler_module_resolution_no_tsconfig(self): """Test detection returns false when no tsconfig exists.""" from codeflash.languages.javascript.test_runner import _detect_bundler_module_resolution with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) assert _detect_bundler_module_resolution(tmpdir_path) is False def test_detect_bundler_module_resolution_extended_config(self): """Test detection works with extended tsconfig files.""" import json from codeflash.languages.javascript.test_runner import _detect_bundler_module_resolution with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) # Create a base config with bundler in a subdirectory (simulating node_modules) node_modules = tmpdir_path / "node_modules" / "@myorg" / "tsconfig" node_modules.mkdir(parents=True) base_tsconfig = { "compilerOptions": { "moduleResolution": "bundler", "module": "preserve", } } (node_modules / "tsconfig.json").write_text(json.dumps(base_tsconfig)) # Create a project tsconfig that extends the base project_tsconfig = { "extends": "@myorg/tsconfig/tsconfig.json", "compilerOptions": { "target": "ES2022", } } (tmpdir_path / "tsconfig.json").write_text(json.dumps(project_tsconfig)) # Should detect bundler from extended config assert _detect_bundler_module_resolution(tmpdir_path) is True def test_create_codeflash_tsconfig(self): """Test creation of codeflash-compatible tsconfig.""" import json from codeflash.languages.javascript.test_runner import _create_codeflash_tsconfig with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) # Create original tsconfig original_tsconfig = { "compilerOptions": { "moduleResolution": "bundler", "module": "preserve", "target": "ES2022", }, "include": ["src/**/*.ts"], "exclude": ["node_modules"], } (tmpdir_path / "tsconfig.json").write_text(json.dumps(original_tsconfig)) # Create codeflash tsconfig result_path = _create_codeflash_tsconfig(tmpdir_path) assert result_path.exists() assert result_path.name == "tsconfig.codeflash.json" # Verify contents codeflash_tsconfig = json.loads(result_path.read_text()) assert codeflash_tsconfig["extends"] == "./tsconfig.json" assert codeflash_tsconfig["compilerOptions"]["moduleResolution"] == "Node" assert "include" in codeflash_tsconfig def test_create_codeflash_jest_config(self): """Test creation of codeflash Jest config.""" from codeflash.languages.javascript.test_runner import _create_codeflash_jest_config with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) # Create codeflash Jest config without original result_path = _create_codeflash_jest_config(tmpdir_path, None) assert result_path is not None assert result_path.exists() assert result_path.name == "jest.codeflash.config.js" # Verify it contains ESM package transformation patterns content = result_path.read_text() assert "transformIgnorePatterns" in content assert "node_modules" in content def test_get_jest_config_for_project_with_bundler(self): """Test that bundler projects get codeflash Jest config.""" import json from codeflash.languages.javascript.test_runner import _get_jest_config_for_project with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) # Create tsconfig with bundler tsconfig = { "compilerOptions": { "moduleResolution": "bundler", "module": "preserve", } } (tmpdir_path / "tsconfig.json").write_text(json.dumps(tsconfig)) (tmpdir_path / "package.json").write_text('{"name": "test"}') result = _get_jest_config_for_project(tmpdir_path) assert result is not None assert result.name == "jest.codeflash.config.js" # Also verify tsconfig.codeflash.json was created assert (tmpdir_path / "tsconfig.codeflash.json").exists() def test_get_jest_config_for_project_without_bundler(self): """Test that non-bundler projects use original Jest config.""" import json from codeflash.languages.javascript.test_runner import _get_jest_config_for_project with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) # Create tsconfig with Node moduleResolution tsconfig = { "compilerOptions": { "moduleResolution": "Node", "module": "ESNext", } } (tmpdir_path / "tsconfig.json").write_text(json.dumps(tsconfig)) (tmpdir_path / "package.json").write_text('{"name": "test"}') # Create original Jest config (tmpdir_path / "jest.config.js").write_text("module.exports = {};") result = _get_jest_config_for_project(tmpdir_path) assert result is not None assert result.name == "jest.config.js" # Verify codeflash configs were NOT created assert not (tmpdir_path / "jest.codeflash.config.js").exists() assert not (tmpdir_path / "tsconfig.codeflash.json").exists() class TestBundledJestReporter: """Tests for the bundled codeflash/jest-reporter. Verifies that: 1. The reporter JS file exists in the runtime package 2. Jest commands reference 'codeflash/jest-reporter' (not jest-junit) 3. The reporter produces valid JUnit XML 4. The CODEFLASH_JEST_REPORTER constant is correct """ def test_reporter_js_file_exists(self): """The jest-reporter.js file must exist in the runtime directory.""" reporter_path = Path(__file__).resolve().parents[2] / "packages" / "codeflash" / "runtime" / "jest-reporter.js" assert reporter_path.exists(), f"jest-reporter.js not found at {reporter_path}" def test_reporter_constant_value(self): """CODEFLASH_JEST_REPORTER should be 'codeflash/jest-reporter'.""" from codeflash.languages.javascript.test_runner import CODEFLASH_JEST_REPORTER assert CODEFLASH_JEST_REPORTER == "codeflash/jest-reporter" def test_behavioral_command_uses_bundled_reporter(self): """run_jest_behavioral_tests should use codeflash/jest-reporter in --reporters flag.""" from codeflash.languages.javascript.test_runner import run_jest_behavioral_tests from codeflash.models.models import TestFile, TestFiles from codeflash.models.test_type import TestType with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) (tmpdir_path / "package.json").write_text('{"name": "test"}') test_dir = tmpdir_path / "test" test_dir.mkdir() test_file = test_dir / "test_func.test.js" test_file.write_text("// test") mock_test_files = TestFiles( test_files=[ TestFile( original_file_path=test_file, instrumented_behavior_file_path=test_file, benchmarking_file_path=test_file, test_type=TestType.GENERATED_REGRESSION, ), ] ) with patch("subprocess.run") as mock_run: mock_result = MagicMock() mock_result.stdout = "" mock_result.stderr = "" mock_result.returncode = 1 mock_run.return_value = mock_result try: run_jest_behavioral_tests( test_paths=mock_test_files, test_env={}, cwd=tmpdir_path, project_root=tmpdir_path, ) except Exception: pass if mock_run.called: cmd = mock_run.call_args[0][0] reporter_args = [a for a in cmd if "--reporters=" in a and "jest-reporter" in a] assert len(reporter_args) == 1, f"Expected exactly one codeflash/jest-reporter flag, got: {reporter_args}" assert reporter_args[0] == "--reporters=codeflash/jest-reporter" # Must NOT reference jest-junit jest_junit_args = [a for a in cmd if "jest-junit" in a] assert len(jest_junit_args) == 0, f"Should not reference jest-junit: {jest_junit_args}" def test_benchmarking_command_uses_bundled_reporter(self): """run_jest_benchmarking_tests should use codeflash/jest-reporter.""" from codeflash.languages.javascript.test_runner import run_jest_benchmarking_tests from codeflash.models.models import TestFile, TestFiles from codeflash.models.test_type import TestType with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) (tmpdir_path / "package.json").write_text('{"name": "test"}') test_dir = tmpdir_path / "test" test_dir.mkdir() test_file = test_dir / "test_func__perf.test.js" test_file.write_text("// test") mock_test_files = TestFiles( test_files=[ TestFile( original_file_path=test_file, instrumented_behavior_file_path=test_file, benchmarking_file_path=test_file, test_type=TestType.GENERATED_REGRESSION, ), ] ) with patch("subprocess.run") as mock_run: mock_result = MagicMock() mock_result.stdout = "" mock_result.stderr = "" mock_result.returncode = 1 mock_run.return_value = mock_result try: run_jest_benchmarking_tests( test_paths=mock_test_files, test_env={}, cwd=tmpdir_path, project_root=tmpdir_path, ) except Exception: pass if mock_run.called: cmd = mock_run.call_args[0][0] reporter_args = [a for a in cmd if "--reporters=codeflash/jest-reporter" in a] assert len(reporter_args) == 1 def test_line_profile_command_uses_bundled_reporter(self): """run_jest_line_profile_tests should use codeflash/jest-reporter.""" from codeflash.languages.javascript.test_runner import run_jest_line_profile_tests from codeflash.models.models import TestFile, TestFiles from codeflash.models.test_type import TestType with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) (tmpdir_path / "package.json").write_text('{"name": "test"}') test_dir = tmpdir_path / "test" test_dir.mkdir() test_file = test_dir / "test_func__line.test.js" test_file.write_text("// test") mock_test_files = TestFiles( test_files=[ TestFile( original_file_path=test_file, instrumented_behavior_file_path=test_file, benchmarking_file_path=test_file, test_type=TestType.GENERATED_REGRESSION, ), ] ) with patch("subprocess.run") as mock_run: mock_result = MagicMock() mock_result.stdout = "" mock_result.stderr = "" mock_result.returncode = 1 mock_run.return_value = mock_result try: run_jest_line_profile_tests( test_paths=mock_test_files, test_env={}, cwd=tmpdir_path, project_root=tmpdir_path, ) except Exception: pass if mock_run.called: cmd = mock_run.call_args[0][0] reporter_args = [a for a in cmd if "--reporters=codeflash/jest-reporter" in a] assert len(reporter_args) == 1 @pytest.mark.skipif(sys.platform == "win32", reason="Node.js subprocess pipe behavior unreliable on Windows CI") def test_reporter_produces_valid_junit_xml(self): """The reporter JS should produce JUnit XML parseable by junitparser.""" import subprocess reporter_path = Path(__file__).resolve().parents[2] / "packages" / "codeflash" / "runtime" / "jest-reporter.js" with tempfile.TemporaryDirectory() as tmpdir: output_file = Path(tmpdir) / "results.xml" # Create a Node.js script that exercises the reporter with mock data test_script = Path(tmpdir) / "test_reporter.js" reporter_path_js = reporter_path.as_posix() output_file_js = output_file.as_posix() test_script.write_text(f""" // Set env vars BEFORE requiring reporter (matches real Jest behavior) process.env.JEST_JUNIT_OUTPUT_FILE = '{output_file_js}'; process.env.JEST_JUNIT_CLASSNAME = '{{filepath}}'; process.env.JEST_JUNIT_SUITE_NAME = '{{filepath}}'; process.env.JEST_JUNIT_ADD_FILE_ATTRIBUTE = 'true'; process.env.JEST_JUNIT_INCLUDE_CONSOLE_OUTPUT = 'true'; const Reporter = require('{reporter_path_js}'); // Mock Jest globalConfig const globalConfig = {{ rootDir: '/tmp/project' }}; const reporter = new Reporter(globalConfig, {{}}); // Mock test results (matches Jest's aggregatedResults structure) const results = {{ testResults: [ {{ testFilePath: '/tmp/project/test/math.test.js', displayName: 'math tests', console: [{{ type: 'log', message: 'CODEFLASH_START test1' }}], testResults: [ {{ fullName: 'math > adds numbers', title: 'adds numbers', status: 'passed', duration: 12, }}, {{ fullName: 'math > handles failure', title: 'handles failure', status: 'failed', duration: 5, failureMessages: ['Expected 4 but got 5'], }}, {{ fullName: 'math > skipped test', title: 'skipped test', status: 'pending', duration: 0, }}, ], }}, ], }}; // Simulate onTestFileResult for console capture reporter.onTestFileResult(null, results.testResults[0], null); // Simulate onRunComplete reporter.onRunComplete([], results); console.log('OK'); """, encoding="utf-8") result = subprocess.run( ["node", str(test_script)], capture_output=True, text=True, timeout=10, ) assert result.returncode == 0, f"Reporter script failed: {result.stderr}" assert output_file.exists(), "Reporter did not create output file" xml_content = output_file.read_text() # Verify basic XML structure assert '" in xml_content # Verify system-out with console output assert "" in xml_content assert "CODEFLASH_START" in xml_content # Verify it's parseable by junitparser (our actual parser) from junitparser import JUnitXml parsed = JUnitXml.fromfile(str(output_file)) suites = list(parsed) assert len(suites) == 1 testcases = list(suites[0]) assert len(testcases) == 3 def test_reporter_export_in_package_json(self): """package.json should export codeflash/jest-reporter.""" import json pkg_path = Path(__file__).resolve().parents[2] / "packages" / "codeflash" / "package.json" with pkg_path.open() as f: pkg = json.load(f) exports = pkg.get("exports", {}) assert "./jest-reporter" in exports, "Missing ./jest-reporter export in package.json" assert exports["./jest-reporter"]["require"] == "./runtime/jest-reporter.js" class TestMonorepoModuleDirectories: """Tests for monorepo moduleDirectories in runtime Jest config.""" def test_find_monorepo_root_finds_yarn_workspace(self): """_find_monorepo_root should find a parent with yarn.lock + node_modules.""" from codeflash.languages.javascript.test_runner import _find_monorepo_root with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir).resolve() (root / "yarn.lock").write_text("") (root / "node_modules").mkdir() pkg = root / "packages" / "my-pkg" pkg.mkdir(parents=True) (pkg / "package.json").write_text('{"name": "my-pkg"}') assert _find_monorepo_root(pkg) == root def test_find_monorepo_root_returns_none_for_standalone(self): """_find_monorepo_root should return None when no monorepo markers exist.""" from codeflash.languages.javascript.test_runner import _find_monorepo_root with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir).resolve() (root / "package.json").write_text('{"name": "standalone"}') (root / "node_modules").mkdir() assert _find_monorepo_root(root) is None def test_runtime_config_includes_module_directories_for_monorepo(self): """_create_runtime_jest_config should add moduleDirectories when in a monorepo.""" from codeflash.languages.javascript.test_runner import _create_runtime_jest_config with tempfile.TemporaryDirectory() as tmpdir: monorepo_root = Path(tmpdir).resolve() (monorepo_root / "yarn.lock").write_text("") (monorepo_root / "node_modules").mkdir() (monorepo_root / "package.json").write_text('{"name": "monorepo"}') pkg = monorepo_root / "packages" / "my-pkg" pkg.mkdir(parents=True) (pkg / "package.json").write_text('{"name": "my-pkg"}') test_dirs = {"/tmp/external-tests"} config_path = _create_runtime_jest_config(None, pkg, test_dirs) assert config_path is not None content = config_path.read_text(encoding="utf-8") monorepo_nm = (monorepo_root / "node_modules").as_posix() assert "moduleDirectories" in content, "Expected moduleDirectories in config" assert monorepo_nm in content, f"Expected {monorepo_nm} in config" def test_runtime_config_no_module_directories_for_standalone(self): """_create_runtime_jest_config should NOT add moduleDirectories for standalone projects.""" from codeflash.languages.javascript.test_runner import _create_runtime_jest_config with tempfile.TemporaryDirectory() as tmpdir: project = Path(tmpdir).resolve() (project / "package.json").write_text('{"name": "standalone"}') test_dirs = {"/tmp/external-tests"} config_path = _create_runtime_jest_config(None, project, test_dirs) assert config_path is not None content = config_path.read_text(encoding="utf-8") assert "moduleDirectories" not in content def test_runtime_config_with_base_config_includes_module_directories(self): """moduleDirectories should spread base config's moduleDirectories in monorepo.""" from codeflash.languages.javascript.test_runner import _create_runtime_jest_config with tempfile.TemporaryDirectory() as tmpdir: monorepo_root = Path(tmpdir).resolve() (monorepo_root / "yarn.lock").write_text("") (monorepo_root / "node_modules").mkdir() (monorepo_root / "package.json").write_text('{"name": "monorepo"}') pkg = monorepo_root / "packages" / "my-pkg" pkg.mkdir(parents=True) (pkg / "package.json").write_text('{"name": "my-pkg"}') base_config = pkg / "jest.config.js" base_config.write_text("module.exports = {};") test_dirs = {"/tmp/external-tests"} config_path = _create_runtime_jest_config(base_config, pkg, test_dirs) assert config_path is not None content = config_path.read_text(encoding="utf-8") assert "baseConfig.moduleDirectories" in content, "Should spread base config moduleDirectories" monorepo_nm = (monorepo_root / "node_modules").as_posix() assert monorepo_nm in content class TestUnsupportedFrameworkError: """Tests for clear error on unsupported test frameworks.""" def test_unknown_framework_raises_error_behavioral(self): """run_behavioral_tests should raise NotImplementedError for unknown frameworks.""" from codeflash.languages.javascript.support import JavaScriptSupport support = JavaScriptSupport() with pytest.raises(NotImplementedError, match="not yet supported"): support.run_behavioral_tests( test_paths=MagicMock(), test_env={}, cwd=Path("."), test_framework="tap", ) def test_unknown_framework_raises_error_benchmarking(self): """run_benchmarking_tests should raise NotImplementedError for unknown frameworks.""" from codeflash.languages.javascript.support import JavaScriptSupport support = JavaScriptSupport() with pytest.raises(NotImplementedError, match="not yet supported"): support.run_benchmarking_tests( test_paths=MagicMock(), test_env={}, cwd=Path("."), test_framework="tap", ) def test_unknown_framework_raises_error_line_profile(self): """run_line_profile_tests should raise NotImplementedError for unknown frameworks.""" from codeflash.languages.javascript.support import JavaScriptSupport support = JavaScriptSupport() with pytest.raises(NotImplementedError, match="not yet supported"): support.run_line_profile_tests( test_paths=MagicMock(), test_env={}, cwd=Path("."), test_framework="tap", ) def test_jest_framework_does_not_raise_not_implemented(self): """jest framework should NOT raise NotImplementedError.""" from codeflash.languages.javascript.support import JavaScriptSupport support = JavaScriptSupport() try: support.run_behavioral_tests( test_paths=MagicMock(), test_env={}, cwd=Path("."), test_framework="jest", ) except NotImplementedError: pytest.fail("jest framework should not raise NotImplementedError") except Exception: pass # Other exceptions are fine — Jest isn't installed in test env def test_mocha_framework_does_not_raise_not_implemented(self): """mocha framework should NOT raise NotImplementedError.""" from codeflash.languages.javascript.support import JavaScriptSupport support = JavaScriptSupport() try: support.run_behavioral_tests( test_paths=MagicMock(), test_env={}, cwd=Path("."), test_framework="mocha", ) except NotImplementedError: pytest.fail("mocha framework should not raise NotImplementedError") except Exception: pass # Other exceptions are fine — Mocha isn't installed in test env