From 39091c5b24e2ff37fc6d275352511ef9b176df79 Mon Sep 17 00:00:00 2001 From: wallstop Date: Wed, 14 Oct 2026 17:23:09 +0000 Subject: [PATCH] Speed up the remaining targeted harness tests The eight files named in #234 serialize independent child-process waits. Run them through the existing bounded run_cases helper, retaining every case body and assertion. Each case owns a temporary root or reads the repository only. Child commands use explicit cwd, arguments, and env. The member-order, changelog-length, and line-ending suites previously changed os.environ around child processes. Give each child its own env mapping before enabling concurrency. Keep the recycler readiness and require_live checks and preserve non-POSIX skip/pass output order. Validation: check:fast and check:gate pass (38/38 self-test files). Strict pyright reports zero errors on all nine changed Python files. AST comparisons prove case bodies unchanged. Three interleaved runs per side match ordered PASS/FAIL/SKIP names for every converted file; a simulated non-POSIX recycler run matches the old output exactly. Unity checks do not apply: this change only touches harness tests. Interleaved targeted-file medians on this container (seconds, main -> branch; timing is indicative under shared-host load): - devcontainer_lifecycle: 7.103 -> 3.606 - harness_scope: 2.657 -> 0.715 - gate: 2.119 -> 0.915 - opencode_service_recycle: 1.104 -> 0.264 - csharp_member_order: 1.114 -> 0.311 - assembly_warnings: 1.010 -> 0.214 - changelog_length: 0.749 -> 0.195 - line_endings: 1.339 -> 0.302 Fixes #234. Refs #171. --- scripts/tests/_test_helpers.py | 5 +- scripts/tests/test_assembly_warnings.py | 45 +++------- scripts/tests/test_changelog_length.py | 30 +++---- scripts/tests/test_csharp_member_order.py | 60 ++++++------- scripts/tests/test_devcontainer_lifecycle.py | 18 ++-- scripts/tests/test_gate.py | 89 +++++-------------- scripts/tests/test_harness_scope.py | 82 +++++++---------- scripts/tests/test_line_endings.py | 34 +++---- .../tests/test_opencode_service_recycle.py | 41 +++++---- 9 files changed, 152 insertions(+), 252 deletions(-) diff --git a/scripts/tests/_test_helpers.py b/scripts/tests/_test_helpers.py index eafb1d5..865df25 100644 --- a/scripts/tests/_test_helpers.py +++ b/scripts/tests/_test_helpers.py @@ -24,7 +24,7 @@ import uuid from concurrent.futures import ThreadPoolExecutor from pathlib import Path -from typing import Any, Callable, List, Optional, Sequence, Tuple +from typing import Any, Callable, List, Mapping, Optional, Sequence, Tuple _failure_count_value = 0 @@ -200,7 +200,7 @@ def find_python() -> str: raise RuntimeError("the harness self-tests require an executable Python interpreter") -def run_tool_output(command: Sequence[str]) -> Tuple[str, int]: +def run_tool_output(command: Sequence[str], *, env: Optional[Mapping[str, str]] = None) -> Tuple[str, int]: """Run a child tool with all output merged, returning ``(output, exit code)``. This mirrors the PowerShell originals' ``*>&1 | Out-String``: stdout and @@ -210,6 +210,7 @@ def run_tool_output(command: Sequence[str]) -> Tuple[str, int]: """ completed = subprocess.run( list(command), + env=env, capture_output=True, text=True, encoding="utf-8", diff --git a/scripts/tests/test_assembly_warnings.py b/scripts/tests/test_assembly_warnings.py index 8775cb1..21c185c 100644 --- a/scripts/tests/test_assembly_warnings.py +++ b/scripts/tests/test_assembly_warnings.py @@ -24,7 +24,7 @@ find_python, new_temp_root, remove_temp_root, - run_case, + run_cases, write_fixture_file, ) @@ -308,36 +308,19 @@ def main() -> int: # interpreter because the fixtures never touch a toolchain. python = find_python() - run_case("Passes_ForRepositoryAssemblies", lambda: case_passes_for_repository_assemblies(python)) - run_case( - "Passes_When_EveryAssemblyHasWarningsAsErrors", - lambda: case_passes_when_every_assembly_has_warnings_as_errors(python), - ) - run_case("Fails_WhenAssemblyHasNoRuleset", lambda: case_fails_when_assembly_has_no_ruleset(python)) - run_case( - "Fails_WhenAssemblyHasNoCompilerResponseFile", - lambda: case_fails_when_assembly_has_no_compiler_response_file(python), - ) - run_case( - "Fails_WhenAssemblyHasAmbiguousCompilerResponseFiles", - lambda: case_fails_when_assembly_has_ambiguous_compiler_response_files(python), - ) - for response_file_case in RESPONSE_FILE_CASES: - run_case(response_file_case.name, lambda case=response_file_case: run_response_file_case(python, case)) - run_case("Fails_WhenAssemblyHasMultipleRulesets", lambda: case_fails_when_assembly_has_multiple_rulesets(python)) - run_case("Fails_WhenRulesetIsMalformed", lambda: case_fails_when_ruleset_is_malformed(python)) - run_case( - "Fails_WhenIncludeAllDoesNotPromoteWarnings", - lambda: case_fails_when_include_all_does_not_promote_warnings(python), - ) - run_case( - "Fails_WhenRepositoryContainsLabeledAnalyzer", - lambda: case_fails_when_repository_contains_labeled_analyzer(python), - ) - run_case( - "Fails_WhenRepositoryContainsKnownAnalyzerPayload", - lambda: case_fails_when_repository_contains_known_analyzer_payload(python), - ) + run_cases([ + ("Passes_ForRepositoryAssemblies", lambda: case_passes_for_repository_assemblies(python)), + ("Passes_When_EveryAssemblyHasWarningsAsErrors", lambda: case_passes_when_every_assembly_has_warnings_as_errors(python)), + ("Fails_WhenAssemblyHasNoRuleset", lambda: case_fails_when_assembly_has_no_ruleset(python)), + ("Fails_WhenAssemblyHasNoCompilerResponseFile", lambda: case_fails_when_assembly_has_no_compiler_response_file(python)), + ("Fails_WhenAssemblyHasAmbiguousCompilerResponseFiles", lambda: case_fails_when_assembly_has_ambiguous_compiler_response_files(python)), + *[(response_file_case.name, lambda case=response_file_case: run_response_file_case(python, case)) for response_file_case in RESPONSE_FILE_CASES], + ("Fails_WhenAssemblyHasMultipleRulesets", lambda: case_fails_when_assembly_has_multiple_rulesets(python)), + ("Fails_WhenRulesetIsMalformed", lambda: case_fails_when_ruleset_is_malformed(python)), + ("Fails_WhenIncludeAllDoesNotPromoteWarnings", lambda: case_fails_when_include_all_does_not_promote_warnings(python)), + ("Fails_WhenRepositoryContainsLabeledAnalyzer", lambda: case_fails_when_repository_contains_labeled_analyzer(python)), + ("Fails_WhenRepositoryContainsKnownAnalyzerPayload", lambda: case_fails_when_repository_contains_known_analyzer_payload(python)), + ]) failures = failure_count() print(f"== assembly warnings policy: {failures} failure(s) ==") diff --git a/scripts/tests/test_changelog_length.py b/scripts/tests/test_changelog_length.py index decb227..fe9fe6c 100644 --- a/scripts/tests/test_changelog_length.py +++ b/scripts/tests/test_changelog_length.py @@ -24,7 +24,7 @@ find_python, new_temp_root, remove_temp_root, - run_case, + run_cases, run_tool_output, write_fixture_file, ) @@ -38,17 +38,10 @@ def invoke_changelog_length_lint(changelog_path: Path, lint_arguments: Sequence[str] = ()) -> Tuple[str, int]: - """Run the lint with the changelog override env var set, then restore it.""" - had_override = "CHANGELOG_LENGTH_CHANGELOG" in os.environ - previous_override = os.environ.get("CHANGELOG_LENGTH_CHANGELOG") - try: - os.environ["CHANGELOG_LENGTH_CHANGELOG"] = str(changelog_path) - return run_tool_output([find_python(), str(LINT_SCRIPT), *lint_arguments]) - finally: - if had_override: - os.environ["CHANGELOG_LENGTH_CHANGELOG"] = previous_override or "" - else: - os.environ.pop("CHANGELOG_LENGTH_CHANGELOG", None) + """Run the lint with private child-environment overrides.""" + env = dict(os.environ) + env["CHANGELOG_LENGTH_CHANGELOG"] = str(changelog_path) + return run_tool_output([find_python(), str(LINT_SCRIPT), *lint_arguments], env=env) def invoke_fixture_lint(root: Path, lint_arguments: Sequence[str] = ()) -> Tuple[str, int]: @@ -319,12 +312,13 @@ def case_passes_against_the_repository_itself() -> None: def main() -> int: print("== Changelog entry length self-tests ==") - for case in CASES: - run_case(case.name, lambda bound_case=case: run_table_case(bound_case)) - run_case("Fails_WhenUnreleasedIsMissing", case_fails_when_unreleased_is_missing) - run_case("Fails_WhenUnreleasedIsDuplicated", case_fails_when_unreleased_is_duplicated) - run_case("Fails_WhenTheChangelogIsMissing", case_fails_when_the_changelog_is_missing) - run_case("Passes_AgainstTheRepositoryItself", case_passes_against_the_repository_itself) + run_cases([ + *[(case.name, lambda bound_case=case: run_table_case(bound_case)) for case in CASES], + ("Fails_WhenUnreleasedIsMissing", case_fails_when_unreleased_is_missing), + ("Fails_WhenUnreleasedIsDuplicated", case_fails_when_unreleased_is_duplicated), + ("Fails_WhenTheChangelogIsMissing", case_fails_when_the_changelog_is_missing), + ("Passes_AgainstTheRepositoryItself", case_passes_against_the_repository_itself), + ]) failures = failure_count() if failures > 0: diff --git a/scripts/tests/test_csharp_member_order.py b/scripts/tests/test_csharp_member_order.py index 88726ad..f1d456d 100644 --- a/scripts/tests/test_csharp_member_order.py +++ b/scripts/tests/test_csharp_member_order.py @@ -23,7 +23,7 @@ fixture_path, new_temp_root, remove_temp_root, - run_case, + run_cases, run_tool_output, write_fixture_file, ) @@ -34,17 +34,10 @@ def invoke_member_order_lint(root: Path, lint_arguments: Sequence[str] = ()) -> Tuple[str, int]: - """Run the lint with the roots override env var set, then restore it.""" - had_previous = "CSHARP_MEMBER_ORDER_ROOTS" in os.environ - previous = os.environ.get("CSHARP_MEMBER_ORDER_ROOTS") - try: - os.environ["CSHARP_MEMBER_ORDER_ROOTS"] = str(root) - return run_tool_output([find_python(), str(LINT_SCRIPT), *lint_arguments]) - finally: - if had_previous: - os.environ["CSHARP_MEMBER_ORDER_ROOTS"] = previous or "" - else: - os.environ.pop("CSHARP_MEMBER_ORDER_ROOTS", None) + """Run the lint with private child-environment overrides.""" + env = dict(os.environ) + env["CSHARP_MEMBER_ORDER_ROOTS"] = str(root) + return run_tool_output([find_python(), str(LINT_SCRIPT), *lint_arguments], env=env) ORDERED_FIXTURE = """using System; @@ -609,28 +602,27 @@ def case_paths_scans_only_the_named_files() -> None: def main() -> int: print("== C# member-order self-tests ==") - run_case("Passes_CanonicalOrder", case_passes_canonical_order) - run_case("Fails_OnTierAndAccessInversions", case_fails_on_tier_and_access_inversions) - run_case("Fails_OnUnderscoredMethodName", case_fails_on_underscored_method_name) - run_case("Fails_OnUnderscoredGenericMethodName", case_fails_on_underscored_generic_method_name) - run_case("Fails_OnConsecutiveOrdinaryLineComments", case_fails_on_consecutive_ordinary_line_comments) - run_case( - "Fails_OnLeadingBomConsecutiveOrdinaryLineComments", - case_fails_on_leading_bom_consecutive_ordinary_line_comments, - ) - run_case("Passes_SupportedCommentForms", case_passes_supported_comment_forms) - run_case("Fails_OnRuntimeLinqImportsAndQualifiedCalls", case_fails_on_runtime_linq_imports_and_qualified_calls) - run_case("Passes_RuntimeLinqTextInCommentsAndStrings", case_passes_runtime_linq_text_in_comments_and_strings) - run_case("Fails_OnPersistedStateModelLinqDependency", case_fails_on_persisted_state_model_linq_dependency) - run_case("Fails_OnSearchModelLinqDependency", case_fails_on_search_model_linq_dependency) - run_case("Fails_OnAssetPostprocessingLinqDependency", case_fails_on_asset_postprocessing_linq_dependency) - run_case("Fails_OnNamespaceDiscoveryLinqDependency", case_fails_on_namespace_discovery_linq_dependency) - run_case("Passes_OtherEditorLinqDependency", case_passes_other_editor_linq_dependency) - run_case("Fails_WhenNestedTypeIsInterspersed", case_fails_when_nested_type_is_interspersed) - run_case("FixesTierOrderByExactPermutation", case_fixes_tier_order_by_exact_permutation) - run_case("Fails_OnCountedArrayLoopWithUnusedIndex", case_fails_on_counted_array_loop_with_unused_index) - run_case("Passes_CountedLoopsWhenTheIndexIsNeeded", case_passes_counted_loops_when_the_index_is_needed) - run_case("Paths_ScansOnlyTheNamedFiles", case_paths_scans_only_the_named_files) + run_cases([ + ("Passes_CanonicalOrder", case_passes_canonical_order), + ("Fails_OnTierAndAccessInversions", case_fails_on_tier_and_access_inversions), + ("Fails_OnUnderscoredMethodName", case_fails_on_underscored_method_name), + ("Fails_OnUnderscoredGenericMethodName", case_fails_on_underscored_generic_method_name), + ("Fails_OnConsecutiveOrdinaryLineComments", case_fails_on_consecutive_ordinary_line_comments), + ("Fails_OnLeadingBomConsecutiveOrdinaryLineComments", case_fails_on_leading_bom_consecutive_ordinary_line_comments), + ("Passes_SupportedCommentForms", case_passes_supported_comment_forms), + ("Fails_OnRuntimeLinqImportsAndQualifiedCalls", case_fails_on_runtime_linq_imports_and_qualified_calls), + ("Passes_RuntimeLinqTextInCommentsAndStrings", case_passes_runtime_linq_text_in_comments_and_strings), + ("Fails_OnPersistedStateModelLinqDependency", case_fails_on_persisted_state_model_linq_dependency), + ("Fails_OnSearchModelLinqDependency", case_fails_on_search_model_linq_dependency), + ("Fails_OnAssetPostprocessingLinqDependency", case_fails_on_asset_postprocessing_linq_dependency), + ("Fails_OnNamespaceDiscoveryLinqDependency", case_fails_on_namespace_discovery_linq_dependency), + ("Passes_OtherEditorLinqDependency", case_passes_other_editor_linq_dependency), + ("Fails_WhenNestedTypeIsInterspersed", case_fails_when_nested_type_is_interspersed), + ("FixesTierOrderByExactPermutation", case_fixes_tier_order_by_exact_permutation), + ("Fails_OnCountedArrayLoopWithUnusedIndex", case_fails_on_counted_array_loop_with_unused_index), + ("Passes_CountedLoopsWhenTheIndexIsNeeded", case_passes_counted_loops_when_the_index_is_needed), + ("Paths_ScansOnlyTheNamedFiles", case_paths_scans_only_the_named_files), + ]) if failure_count() > 0: return 1 diff --git a/scripts/tests/test_devcontainer_lifecycle.py b/scripts/tests/test_devcontainer_lifecycle.py index 7f8563e..3633f05 100644 --- a/scripts/tests/test_devcontainer_lifecycle.py +++ b/scripts/tests/test_devcontainer_lifecycle.py @@ -24,7 +24,7 @@ sys.dont_write_bytecode = True -from _test_helpers import assert_exit_code, assert_true, failure_count, run_case +from _test_helpers import assert_exit_code, assert_true, failure_count, run_cases TESTS_DIR = Path(__file__).resolve().parent REPO_ROOT = TESTS_DIR.parent.parent @@ -388,13 +388,15 @@ def main() -> int: print("== devcontainer lifecycle: 0 failure(s) ==") return 0 - run_case("OnCreate_BootstrapsEnvLocalAndIsIdempotent", case_on_create_bootstraps_and_is_idempotent) - run_case("OnCreate_ToleratesNpmRefreshFailureOnly", case_on_create_tolerates_npm_failure_only) - run_case("PostCreate_RetriesUntilTheThirdAttemptSucceeds", case_post_create_retries_until_success) - run_case("PostCreate_InstallsPreCommitHooksBestEffort", case_post_create_installs_pre_commit_hooks) - run_case("PostStart_ReplacesBashrcBlockAndLoadsEnvWithFilePrecedence", case_post_start_replaces_block_and_loads_env) - run_case("UpdateContent_RestoresAndSyncsOnlyWhenPresent", case_update_content_restores_and_syncs_only_when_present) - run_case("Lifecycle_MissingWorkspaceFailsForEveryEntry", case_missing_workspace_fails_for_every_entry) + run_cases([ + ("OnCreate_BootstrapsEnvLocalAndIsIdempotent", case_on_create_bootstraps_and_is_idempotent), + ("OnCreate_ToleratesNpmRefreshFailureOnly", case_on_create_tolerates_npm_failure_only), + ("PostCreate_RetriesUntilTheThirdAttemptSucceeds", case_post_create_retries_until_success), + ("PostCreate_InstallsPreCommitHooksBestEffort", case_post_create_installs_pre_commit_hooks), + ("PostStart_ReplacesBashrcBlockAndLoadsEnvWithFilePrecedence", case_post_start_replaces_block_and_loads_env), + ("UpdateContent_RestoresAndSyncsOnlyWhenPresent", case_update_content_restores_and_syncs_only_when_present), + ("Lifecycle_MissingWorkspaceFailsForEveryEntry", case_missing_workspace_fails_for_every_entry), + ]) failures = failure_count() print(f"== devcontainer lifecycle: {failures} failure(s) ==") diff --git a/scripts/tests/test_gate.py b/scripts/tests/test_gate.py index 2784c11..983b0fd 100644 --- a/scripts/tests/test_gate.py +++ b/scripts/tests/test_gate.py @@ -26,7 +26,7 @@ find_python, new_temp_root, remove_temp_root, - run_case, + run_cases, write_fixture_file, ) @@ -602,71 +602,28 @@ def main() -> int: # only need a working interpreter because --plan executes nothing. python = find_python() - run_case("Plan_PrintsTheFullLaneTable", lambda: case_plan_prints_the_full_lane_table(python)) - run_case("Plan_CarriesCommandsAndDependencies", lambda: case_plan_carries_commands_and_dependencies(python)) - run_case( - "Plan_MarksTheDriftCheckAsAnEmptyOutputCheck", - lambda: case_plan_marks_the_drift_check_as_an_empty_output_check(python), - ) - run_case("Plan_ResolvesEveryProgram", lambda: case_plan_resolves_every_program(python)) - run_case( - "Only_SelectsTheRequestedStepsWithDependencies", - lambda: case_only_selects_the_requested_steps_with_dependencies(python), - ) - run_case("Only_FailsOnUnknownStepNames", lambda: case_only_fails_on_unknown_step_names(python)) - run_case("Plan_ExcludesTheUnitySuites", lambda: case_plan_excludes_the_unity_suites(python)) - run_case( - "LaunchFailure_SurfacesTheReasonInConsoleAndReport", - lambda: case_launch_failure_surfaces_the_reason_in_console_and_report(python), - ) - run_case( - "ReportWriteFailure_IsVisibleWithoutCrashingTheGate", - lambda: case_report_write_failure_is_visible_without_crashing_the_gate(python), - ) - run_case( - "LaneCrash_SurfacesTheReasonInConsoleAndReport", - lambda: case_lane_crash_surfaces_the_reason_in_console_and_report(python), - ) - run_case( - "ScopedLaneTable_RewritesTheThreeLintSteps", - lambda: case_scoped_lane_table_rewrites_the_three_lint_steps(python), - ) - run_case( - "ScopedLaneTable_DropsTheCSharpLaneWhenNoCSharpChanged", - lambda: case_scoped_lane_table_drops_the_csharp_lane_when_no_csharp_changed(python), - ) - run_case( - "ScopedLaneTable_DropsTheUnaffectedStepsAndRemapsDependencies", - lambda: case_scoped_lane_table_drops_the_unaffected_steps_and_remaps_dependencies(python), - ) - run_case( - "ScopedLaneTable_ScopesTheSuiteSelection", - lambda: case_scoped_lane_table_scopes_the_suite_selection(python), - ) - run_case( - "ScopedLaneTable_KeepsWholeTreeWithoutABase", - lambda: case_scoped_lane_table_keeps_whole_tree_without_a_base(python), - ) - run_case( - "PackagePathsChanged_MatchesOnlyThePayload", - lambda: case_package_paths_changed_matches_only_the_payload(python), - ) - run_case( - "Execution_ScopesTheLintStepsToTheScannedDiff", - lambda: case_execution_scopes_the_lint_steps_to_the_scanned_diff(python), - ) - run_case( - "Execution_SkipsTheLintStepsWhenNoCSharpChanged", - lambda: case_execution_skips_the_lint_steps_when_no_csharp_changed(python), - ) - run_case( - "Execution_SkipsEveryUnaffectedStepWhenTheDiffCannotAffectThem", - lambda: case_execution_skips_every_unaffected_step_when_the_diff_cannot_affect_them(python), - ) - run_case( - "Execution_ScopesTheSuiteToTheSelectedFiles", - lambda: case_execution_scopes_the_suite_to_the_selected_files(python), - ) + run_cases([ + ("Plan_PrintsTheFullLaneTable", lambda: case_plan_prints_the_full_lane_table(python)), + ("Plan_CarriesCommandsAndDependencies", lambda: case_plan_carries_commands_and_dependencies(python)), + ("Plan_MarksTheDriftCheckAsAnEmptyOutputCheck", lambda: case_plan_marks_the_drift_check_as_an_empty_output_check(python)), + ("Plan_ResolvesEveryProgram", lambda: case_plan_resolves_every_program(python)), + ("Only_SelectsTheRequestedStepsWithDependencies", lambda: case_only_selects_the_requested_steps_with_dependencies(python)), + ("Only_FailsOnUnknownStepNames", lambda: case_only_fails_on_unknown_step_names(python)), + ("Plan_ExcludesTheUnitySuites", lambda: case_plan_excludes_the_unity_suites(python)), + ("LaunchFailure_SurfacesTheReasonInConsoleAndReport", lambda: case_launch_failure_surfaces_the_reason_in_console_and_report(python)), + ("ReportWriteFailure_IsVisibleWithoutCrashingTheGate", lambda: case_report_write_failure_is_visible_without_crashing_the_gate(python)), + ("LaneCrash_SurfacesTheReasonInConsoleAndReport", lambda: case_lane_crash_surfaces_the_reason_in_console_and_report(python)), + ("ScopedLaneTable_RewritesTheThreeLintSteps", lambda: case_scoped_lane_table_rewrites_the_three_lint_steps(python)), + ("ScopedLaneTable_DropsTheCSharpLaneWhenNoCSharpChanged", lambda: case_scoped_lane_table_drops_the_csharp_lane_when_no_csharp_changed(python)), + ("ScopedLaneTable_DropsTheUnaffectedStepsAndRemapsDependencies", lambda: case_scoped_lane_table_drops_the_unaffected_steps_and_remaps_dependencies(python)), + ("ScopedLaneTable_ScopesTheSuiteSelection", lambda: case_scoped_lane_table_scopes_the_suite_selection(python)), + ("ScopedLaneTable_KeepsWholeTreeWithoutABase", lambda: case_scoped_lane_table_keeps_whole_tree_without_a_base(python)), + ("PackagePathsChanged_MatchesOnlyThePayload", lambda: case_package_paths_changed_matches_only_the_payload(python)), + ("Execution_ScopesTheLintStepsToTheScannedDiff", lambda: case_execution_scopes_the_lint_steps_to_the_scanned_diff(python)), + ("Execution_SkipsTheLintStepsWhenNoCSharpChanged", lambda: case_execution_skips_the_lint_steps_when_no_csharp_changed(python)), + ("Execution_SkipsEveryUnaffectedStepWhenTheDiffCannotAffectThem", lambda: case_execution_skips_every_unaffected_step_when_the_diff_cannot_affect_them(python)), + ("Execution_ScopesTheSuiteToTheSelectedFiles", lambda: case_execution_scopes_the_suite_to_the_selected_files(python)), + ]) failures = failure_count() if failures > 0: diff --git a/scripts/tests/test_harness_scope.py b/scripts/tests/test_harness_scope.py index 63a03b8..745de6d 100644 --- a/scripts/tests/test_harness_scope.py +++ b/scripts/tests/test_harness_scope.py @@ -24,7 +24,7 @@ find_python, new_temp_root, remove_temp_root, - run_case, + run_cases, write_fixture_file, ) @@ -462,57 +462,35 @@ def main() -> int: # nothing. python = find_python() - run_case("Skips_ADocsOnlyCommit", lambda: case_skips_a_docs_only_commit(python)) - run_case("Skips_ACSharpOnlyCommit", lambda: case_skips_a_csharp_only_commit(python)) - run_case("Runs_ForAScriptsCommit", lambda: case_runs_for_a_scripts_commit(python)) - run_case("Runs_ForHarnessDirectoryCommits", lambda: case_runs_for_harness_directory_commits(python)) - run_case("Runs_ForARootManifestCommit", lambda: case_runs_for_a_root_manifest_commit(python)) - run_case("Runs_ForAnUntrackedSurfaceFile", lambda: case_runs_for_an_untracked_surface_file(python)) - run_case("Runs_ForAWorkingTreeSurfaceEdit", lambda: case_runs_for_a_working_tree_surface_edit(python)) - run_case("Runs_WhenTheBaseCannotBeResolved", lambda: case_runs_when_the_base_cannot_be_resolved(python)) - run_case( - "SurfacePath_MatchesTheDeclaredSurfaceOnly", - lambda: case_surface_path_matches_the_declared_surface_only(python), - ) - run_case("Selection_SelectsTheMappedSubjectTests", lambda: case_selection_selects_the_mapped_subject_tests(python)) - run_case("Selection_UnionsSubjectsAcrossPaths", lambda: case_selection_unions_subjects_across_paths(python)) - run_case( - "Selection_PrefersTheSpecificMcpSubjectOverTheCoarseTree", - lambda: case_selection_prefers_the_specific_mcp_subject_over_the_coarse_tree(python), - ) - run_case("Selection_MapsATestFileToItself", lambda: case_selection_maps_a_test_file_to_itself(python)) - run_case("Selection_MapsTheGateSurface", lambda: case_selection_maps_the_gate_surface(python)) - run_case("Selection_MapsTheMigratedTrioSurface", lambda: case_selection_maps_the_migrated_trio_surface(python)) - run_case( - "Selection_FallsBackToAllForSharedSuitePaths", - lambda: case_selection_falls_back_to_all_for_shared_suite_paths(python), - ) - run_case( - "Selection_FallsBackToAllForUnmappedSubjects", - lambda: case_selection_falls_back_to_all_for_unmapped_subjects(python), - ) - run_case("Selection_IgnoresPathsOutsideTheSurface", lambda: case_selection_ignores_paths_outside_the_surface(python)) - run_case( - "Selection_FailsSafeWhenAMappedNameIsUnavailable", - lambda: case_selection_fails_safe_when_a_mapped_name_is_unavailable(python), - ) - run_case("Selection_AcceptsTheAvailableNames", lambda: case_selection_accepts_the_available_names(python)) - run_case( - "SelectionOutput_NamesTheSubjectForAMappedCommit", - lambda: case_selection_output_names_the_subject_for_a_mapped_commit(python), - ) - run_case( - "SelectionOutput_FallsBackToAllForAnUnmappedCommit", - lambda: case_selection_output_falls_back_to_all_for_an_unmapped_commit(python), - ) - run_case("CiVerdicts_SkipADocsOnlyCommit", lambda: case_ci_verdicts_skip_a_docs_only_commit(python)) - run_case("CiVerdicts_SkipMarkdownForACSharpOnlyCommit", lambda: case_ci_verdicts_skip_markdown_for_a_csharp_only_commit(python)) - run_case("CiVerdicts_RunForAScriptsCommit", lambda: case_ci_verdicts_run_for_a_scripts_commit(python)) - run_case("CiVerdicts_FlagAWorkingTreeMarkdownEdit", lambda: case_ci_verdicts_flag_a_working_tree_markdown_edit(python)) - run_case( - "CiVerdicts_FailSafeWhenTheBaseCannotBeResolved", - lambda: case_ci_verdicts_fail_safe_when_the_base_cannot_be_resolved(python), - ) + run_cases([ + ("Skips_ADocsOnlyCommit", lambda: case_skips_a_docs_only_commit(python)), + ("Skips_ACSharpOnlyCommit", lambda: case_skips_a_csharp_only_commit(python)), + ("Runs_ForAScriptsCommit", lambda: case_runs_for_a_scripts_commit(python)), + ("Runs_ForHarnessDirectoryCommits", lambda: case_runs_for_harness_directory_commits(python)), + ("Runs_ForARootManifestCommit", lambda: case_runs_for_a_root_manifest_commit(python)), + ("Runs_ForAnUntrackedSurfaceFile", lambda: case_runs_for_an_untracked_surface_file(python)), + ("Runs_ForAWorkingTreeSurfaceEdit", lambda: case_runs_for_a_working_tree_surface_edit(python)), + ("Runs_WhenTheBaseCannotBeResolved", lambda: case_runs_when_the_base_cannot_be_resolved(python)), + ("SurfacePath_MatchesTheDeclaredSurfaceOnly", lambda: case_surface_path_matches_the_declared_surface_only(python)), + ("Selection_SelectsTheMappedSubjectTests", lambda: case_selection_selects_the_mapped_subject_tests(python)), + ("Selection_UnionsSubjectsAcrossPaths", lambda: case_selection_unions_subjects_across_paths(python)), + ("Selection_PrefersTheSpecificMcpSubjectOverTheCoarseTree", lambda: case_selection_prefers_the_specific_mcp_subject_over_the_coarse_tree(python)), + ("Selection_MapsATestFileToItself", lambda: case_selection_maps_a_test_file_to_itself(python)), + ("Selection_MapsTheGateSurface", lambda: case_selection_maps_the_gate_surface(python)), + ("Selection_MapsTheMigratedTrioSurface", lambda: case_selection_maps_the_migrated_trio_surface(python)), + ("Selection_FallsBackToAllForSharedSuitePaths", lambda: case_selection_falls_back_to_all_for_shared_suite_paths(python)), + ("Selection_FallsBackToAllForUnmappedSubjects", lambda: case_selection_falls_back_to_all_for_unmapped_subjects(python)), + ("Selection_IgnoresPathsOutsideTheSurface", lambda: case_selection_ignores_paths_outside_the_surface(python)), + ("Selection_FailsSafeWhenAMappedNameIsUnavailable", lambda: case_selection_fails_safe_when_a_mapped_name_is_unavailable(python)), + ("Selection_AcceptsTheAvailableNames", lambda: case_selection_accepts_the_available_names(python)), + ("SelectionOutput_NamesTheSubjectForAMappedCommit", lambda: case_selection_output_names_the_subject_for_a_mapped_commit(python)), + ("SelectionOutput_FallsBackToAllForAnUnmappedCommit", lambda: case_selection_output_falls_back_to_all_for_an_unmapped_commit(python)), + ("CiVerdicts_SkipADocsOnlyCommit", lambda: case_ci_verdicts_skip_a_docs_only_commit(python)), + ("CiVerdicts_SkipMarkdownForACSharpOnlyCommit", lambda: case_ci_verdicts_skip_markdown_for_a_csharp_only_commit(python)), + ("CiVerdicts_RunForAScriptsCommit", lambda: case_ci_verdicts_run_for_a_scripts_commit(python)), + ("CiVerdicts_FlagAWorkingTreeMarkdownEdit", lambda: case_ci_verdicts_flag_a_working_tree_markdown_edit(python)), + ("CiVerdicts_FailSafeWhenTheBaseCannotBeResolved", lambda: case_ci_verdicts_fail_safe_when_the_base_cannot_be_resolved(python)), + ]) failures = failure_count() if failures > 0: diff --git a/scripts/tests/test_line_endings.py b/scripts/tests/test_line_endings.py index 99f2480..abac48e 100644 --- a/scripts/tests/test_line_endings.py +++ b/scripts/tests/test_line_endings.py @@ -24,7 +24,7 @@ find_python, new_temp_root, remove_temp_root, - run_case, + run_cases, run_tool_output, write_fixture_file, ) @@ -40,24 +40,11 @@ def invoke_line_ending_lint( git_attributes: str, lint_arguments: Sequence[str] = (), ) -> Tuple[str, int]: - """Run the lint with both overrides set, then restore them.""" - had_editor_config = "LINE_ENDINGS_EDITORCONFIG" in os.environ - previous_editor_config = os.environ.get("LINE_ENDINGS_EDITORCONFIG") - had_git_attributes = "LINE_ENDINGS_GITATTRIBUTES" in os.environ - previous_git_attributes = os.environ.get("LINE_ENDINGS_GITATTRIBUTES") - try: - os.environ["LINE_ENDINGS_EDITORCONFIG"] = editor_config - os.environ["LINE_ENDINGS_GITATTRIBUTES"] = git_attributes - return run_tool_output([find_python(), str(LINT_SCRIPT), *lint_arguments]) - finally: - if had_editor_config: - os.environ["LINE_ENDINGS_EDITORCONFIG"] = previous_editor_config or "" - else: - os.environ.pop("LINE_ENDINGS_EDITORCONFIG", None) - if had_git_attributes: - os.environ["LINE_ENDINGS_GITATTRIBUTES"] = previous_git_attributes or "" - else: - os.environ.pop("LINE_ENDINGS_GITATTRIBUTES", None) + """Run the lint with private child-environment overrides.""" + env = dict(os.environ) + env["LINE_ENDINGS_EDITORCONFIG"] = editor_config + env["LINE_ENDINGS_GITATTRIBUTES"] = git_attributes + return run_tool_output([find_python(), str(LINT_SCRIPT), *lint_arguments], env=env) def invoke_fixture_lint(root: Path, lint_arguments: Sequence[str] = ()) -> Tuple[str, int]: @@ -379,10 +366,11 @@ def case_passes_against_the_repository_itself() -> None: def main() -> int: print("== Line-ending contract self-tests ==") - for case in CASES: - run_case(case.name, lambda bound_case=case: run_table_case(bound_case)) - run_case("Fails_WhenAGitAttributesFileIsMissing", case_fails_when_a_git_attributes_file_is_missing) - run_case("Passes_AgainstTheRepositoryItself", case_passes_against_the_repository_itself) + run_cases([ + *[(case.name, lambda bound_case=case: run_table_case(bound_case)) for case in CASES], + ("Fails_WhenAGitAttributesFileIsMissing", case_fails_when_a_git_attributes_file_is_missing), + ("Passes_AgainstTheRepositoryItself", case_passes_against_the_repository_itself), + ]) failures = failure_count() if failures > 0: diff --git a/scripts/tests/test_opencode_service_recycle.py b/scripts/tests/test_opencode_service_recycle.py index 9635223..712d150 100644 --- a/scripts/tests/test_opencode_service_recycle.py +++ b/scripts/tests/test_opencode_service_recycle.py @@ -30,7 +30,7 @@ import sys import time from pathlib import Path -from typing import Callable, Dict, Optional, Tuple +from typing import Callable, Dict, List, Optional, Tuple sys.dont_write_bytecode = True @@ -41,7 +41,7 @@ find_python, new_temp_root, remove_temp_root, - run_case, + run_cases, run_tool_output, write_fixture_file, ) @@ -478,22 +478,27 @@ def main() -> int: # The recycle script is typed Python; any working interpreter drives it. python = find_python() - def fixture_case(name: str, body: Callable[[str], None]) -> None: - if posix: - run_case(name, lambda: body(python)) - else: - print(f" SKIP: {name}") - - fixture_case("Recycles_ServiceWithStaleCredential", case_recycles_service_with_stale_credential) - fixture_case("Removes_StaleDiscoveryRecord", case_removes_stale_discovery_record) - fixture_case("Leaves_MatchingServiceAlone", case_leaves_matching_service_alone) - fixture_case("DryRun_ReportsWithoutKilling", case_dry_run_reports_without_killing) - run_case("MissingServiceRecord_IsANoOp", lambda: case_missing_service_record_is_a_no_op(python)) - run_case("DeadPid_IsCleanedUp", lambda: case_dead_pid_is_cleaned_up(python)) - fixture_case("NoEnvFile_IsANoOp", case_no_env_file_is_a_no_op) - fixture_case("QuotedAndCrlfEnvFile_IsParsedLikeTheLoader", case_quoted_and_crlf_env_file_is_parsed_like_the_loader) - fixture_case("InheritedEnv_IsNotMistakenForAFileValue", case_inherited_env_is_not_mistaken_for_a_file_value) - fixture_case("Never_PrintsCredentialValues", case_never_prints_credential_values) + cases: List[Tuple[str, Callable[[], None]]] = [ + ("Recycles_ServiceWithStaleCredential", lambda: case_recycles_service_with_stale_credential(python)), + ("Removes_StaleDiscoveryRecord", lambda: case_removes_stale_discovery_record(python)), + ("Leaves_MatchingServiceAlone", lambda: case_leaves_matching_service_alone(python)), + ("DryRun_ReportsWithoutKilling", lambda: case_dry_run_reports_without_killing(python)), + ("MissingServiceRecord_IsANoOp", lambda: case_missing_service_record_is_a_no_op(python)), + ("DeadPid_IsCleanedUp", lambda: case_dead_pid_is_cleaned_up(python)), + ("NoEnvFile_IsANoOp", lambda: case_no_env_file_is_a_no_op(python)), + ("QuotedAndCrlfEnvFile_IsParsedLikeTheLoader", lambda: case_quoted_and_crlf_env_file_is_parsed_like_the_loader(python)), + ("InheritedEnv_IsNotMistakenForAFileValue", lambda: case_inherited_env_is_not_mistaken_for_a_file_value(python)), + ("Never_PrintsCredentialValues", lambda: case_never_prints_credential_values(python)), + ] + if posix: + run_cases(cases) + else: + # Preserve the original interleaved skip/pass order on non-POSIX hosts. + for name, body in cases: + if name in ("MissingServiceRecord_IsANoOp", "DeadPid_IsCleanedUp"): + run_cases([(name, body)]) + else: + print(f" SKIP: {name}") failures = failure_count() print(f"== opencode-service-recycle: {failures} failure(s) ==")