@@ -28,8 +28,10 @@ def analyzer():
2828def _clear_coana_install_cache ():
2929 """The npm-install fallback caches resolved script paths in a module-level dict; isolate tests."""
3030 reachability ._INSTALLED_COANA_SCRIPT_PATHS .clear ()
31- yield
32- reachability ._INSTALLED_COANA_SCRIPT_PATHS .clear ()
31+ try :
32+ yield
33+ finally :
34+ reachability ._INSTALLED_COANA_SCRIPT_PATHS .clear ()
3335
3436
3537def _spawn_mock (analyzer , mocker , returncode = 0 , ** kwargs ):
@@ -125,27 +127,21 @@ def test_repo_branch_env_absent_when_none(analyzer, mocker):
125127# --- Coana package-spec resolution (pinned by default, latest is opt-in) ---
126128
127129
128- def test_resolve_spec_defaults_to_pinned_version (analyzer ):
129- """No --reach-version -> pinned DEFAULT_COANA_CLI_VERSION (no auto-update)."""
130- assert (
131- analyzer ._resolve_coana_package_spec (None )
132- == f"@coana-tech/cli@{ DEFAULT_COANA_CLI_VERSION } "
133- )
134-
135-
136- def test_resolve_spec_pins_explicit_version (analyzer ):
137- assert analyzer ._resolve_coana_package_spec ("1.2.3" ) == "@coana-tech/cli@1.2.3"
138-
139-
140- def test_resolve_spec_latest_opt_in (analyzer ):
141- """'latest' opts into the newest published version."""
142- assert analyzer ._resolve_coana_package_spec ("latest" ) == "@coana-tech/cli@latest"
143-
144-
145- def test_resolve_spec_is_always_versioned (analyzer ):
146- """Never the bare '@coana-tech/cli' (which would let npx pick a stray global version)."""
147- for version in (None , "latest" , "1.2.3" , " 1.2.3 " ):
148- assert analyzer ._resolve_coana_package_spec (version ).startswith ("@coana-tech/cli@" )
130+ @pytest .mark .parametrize (
131+ ("version" , "expected" ),
132+ [
133+ # No --reach-version -> pinned default (no auto-update).
134+ (None , f"@coana-tech/cli@{ DEFAULT_COANA_CLI_VERSION } " ),
135+ # Explicit version pinned through.
136+ ("1.2.3" , "@coana-tech/cli@1.2.3" ),
137+ # 'latest' opts into the newest published version.
138+ ("latest" , "@coana-tech/cli@latest" ),
139+ # Surrounding whitespace is stripped; always versioned (never bare '@coana-tech/cli').
140+ (" 1.2.3 " , "@coana-tech/cli@1.2.3" ),
141+ ],
142+ )
143+ def test_resolve_coana_package_spec (analyzer , version , expected ):
144+ assert analyzer ._resolve_coana_package_spec (version ) == expected
149145
150146
151147def _spec_in (cmd ):
@@ -161,19 +157,17 @@ def test_npx_uses_yes_and_force_flags(analyzer, mocker):
161157 assert "--force" in cmd
162158
163159
164- def test_npx_runs_pinned_version_by_default (analyzer , mocker ):
165- cmd , _ = _run (analyzer , mocker )
166- assert _spec_in (cmd ) == f"@coana-tech/cli@{ DEFAULT_COANA_CLI_VERSION } "
167-
168-
169- def test_npx_runs_explicit_version (analyzer , mocker ):
170- cmd , _ = _run (analyzer , mocker , version = "9.9.9" )
171- assert _spec_in (cmd ) == "@coana-tech/cli@9.9.9"
172-
173-
174- def test_npx_runs_latest_when_opted_in (analyzer , mocker ):
175- cmd , _ = _run (analyzer , mocker , version = "latest" )
176- assert _spec_in (cmd ) == "@coana-tech/cli@latest"
160+ @pytest .mark .parametrize (
161+ ("version" , "expected_spec" ),
162+ [
163+ (None , f"@coana-tech/cli@{ DEFAULT_COANA_CLI_VERSION } " ), # pinned default
164+ ("9.9.9" , "@coana-tech/cli@9.9.9" ), # explicit pin
165+ ("latest" , "@coana-tech/cli@latest" ), # opt-in to newest
166+ ],
167+ )
168+ def test_npx_runs_resolved_version (analyzer , mocker , version , expected_spec ):
169+ cmd , _ = _run (analyzer , mocker , version = version )
170+ assert _spec_in (cmd ) == expected_spec
177171
178172
179173def test_default_path_never_runs_npm_install (analyzer , mocker ):
@@ -195,16 +189,22 @@ def test_env_strips_npm_package_vars(analyzer, mocker, monkeypatch):
195189# --- npm-install + node fallback (when the npx launcher fails before coana starts) ---
196190
197191
198- def test_launcher_failure_heuristic ():
192+ @pytest .mark .parametrize (
193+ ("returncode" , "is_launcher_failure" ),
194+ [
195+ # Signal kills / >=128 -> launcher failure -> retry.
196+ (- 9 , True ), # killed by signal
197+ (137 , True ), # 128 + SIGKILL
198+ (249 , True ), # observed npx launcher failure
199+ # Small positive exit codes are ambiguous (coana's own codes) -> do NOT retry.
200+ (1 , False ),
201+ (2 , False ),
202+ (127 , False ),
203+ ],
204+ )
205+ def test_launcher_failure_heuristic (returncode , is_launcher_failure ):
199206 f = ReachabilityAnalyzer ._npx_launcher_failed_before_coana
200- # Signal kills / >=128 -> launcher failure -> retry.
201- assert f (- 9 ) is True
202- assert f (137 ) is True
203- assert f (249 ) is True
204- # Small positive exit codes are ambiguous (coana's own codes) -> do NOT retry.
205- assert f (1 ) is False
206- assert f (2 ) is False
207- assert f (127 ) is False
207+ assert f (returncode ) is is_launcher_failure
208208
209209
210210def _capture_spawns (analyzer , mocker , npx_behavior , ** kwargs ):
0 commit comments