Files
wehub-resource-sync 2114ccd278
CI / Lint & Test (Python 3.13) (push) Failing after 2s
CI / Lint & Test (Python 3.14) (push) Failing after 1s
CI / Lint & Test (Python 3.12) (push) Failing after 2s
CI / DCO Check (push) Has been skipped
Scorecard supply-chain security / Scorecard analysis (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:23:39 +08:00

798 lines
31 KiB
Python

# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Mutation test — Max's 4 risk areas. Injects bugs, verifies tests catch them.
Areas: 1) Pool acquire/release 2) 429 backoff/recovery
3) Monkey-patches 4) GapFillAnalyzer.parse_response
"""
from __future__ import annotations
import unittest, sys, time
from pathlib import Path
_project_root = Path(__file__).resolve().parents[4]
sys.path.insert(0, str(_project_root))
results = []
def mutate(label: str, module: str, target: str, broken_fn, test_specs: list[tuple[str, str]]):
"""Inject *broken_fn* into *module.target*, run *test_specs*, restore."""
mod = __import__(module, fromlist=[""])
parts = target.split(".")
obj = mod
for p in parts[:-1]:
obj = getattr(obj, p)
attr = parts[-1]
original = getattr(obj, attr)
setattr(obj, attr, broken_fn)
try:
for test_mod, test_cls in test_specs:
suite = unittest.TestLoader().loadTestsFromName(
f"contrib.batch_scan.tests.tests-pro.{test_mod}.{test_cls}"
)
r = unittest.TextTestRunner(verbosity=0).run(suite)
caught = not r.wasSuccessful()
results.append((label, test_cls, caught))
finally:
setattr(obj, attr, original)
# ═══════════════════════════════════════════════════════════════════════
# Area 1: Pool acquire/release
# ═══════════════════════════════════════════════════════════════════════
# Mutation 1a: acquire forgets to increment active_requests
import contrib.batch_scan.api_pool as _ap
_orig_acquire = _ap.ApiKeyPool.acquire
def _broken_acquire_no_increment(self, timeout=None):
import time as _t
deadline = _t.monotonic() + timeout if timeout is not None else None
with self._condition:
while True:
now = _t.monotonic()
self._recover_expired_keys(now)
available = [k for k in self._keys if k.available]
if available:
key = min(available, key=lambda k: k.active_requests)
# BUG: forgot key.active_requests += 1
key.total_requests += 1
return key
wait_for = self._next_available_in(now)
remaining = self._remaining_timeout(deadline)
if remaining is not None and remaining <= 0:
raise RuntimeError("timeout")
self._condition.wait(timeout=min(wait_for or remaining, remaining or 5.0))
_ap.ApiKeyPool.acquire = _broken_acquire_no_increment
mutate("acquire forgets active_requests++", "contrib.batch_scan.api_pool",
"ApiKeyPool.acquire", _broken_acquire_no_increment,
[("test_api_pool", "TestAcquireRelease")])
_ap.ApiKeyPool.acquire = _orig_acquire
# Mutation 1b: release forgets to decrement active_requests
_orig_release = _ap.ApiKeyPool.release
def _broken_release_no_decrement(self, key, *, success=True):
with self._condition:
# BUG: forgot key.active_requests = max(0, key.active_requests - 1)
if success:
key.consecutive_429 = 0
else:
key.consecutive_429 += 1
key.rate_limited_until = time.monotonic() + min(
30 * (2 ** (key.consecutive_429 - 1)), 300
)
key.rate_limited = True
self._rate_limits_hit += 1
self._condition.notify_all()
_ap.ApiKeyPool.release = _broken_release_no_decrement
mutate("release forgets active_requests--", "contrib.batch_scan.api_pool",
"ApiKeyPool.release", _broken_release_no_decrement,
[("test_api_pool", "TestAcquireRelease"),
("test_api_pool", "TestResourceLeakRecovery")])
_ap.ApiKeyPool.release = _orig_release
# Mutation 1c: least-loaded scheduling broken — always returns first key
_orig_acquire2 = _ap.ApiKeyPool.acquire
def _broken_acquire_no_load_balance(self, timeout=None):
import time as _t
deadline = _t.monotonic() + timeout if timeout is not None else None
with self._condition:
while True:
now = _t.monotonic()
self._recover_expired_keys(now)
available = [k for k in self._keys if k.available]
if available:
# BUG: always returns first available key, ignoring load
key = available[0]
key.active_requests += 1
key.total_requests += 1
self._total_requests_served += 1
_now_active = sum(k.active_requests for k in self._keys)
if _now_active > self._peak_active_requests:
self._peak_active_requests = _now_active
return key
wait_for = self._next_available_in(now)
remaining = self._remaining_timeout(deadline)
if remaining is not None and remaining <= 0:
raise RuntimeError("timeout")
self._condition.wait(timeout=min(wait_for or remaining, remaining or 5.0))
_ap.ApiKeyPool.acquire = _broken_acquire_no_load_balance
mutate("least-loaded scheduling broken", "contrib.batch_scan.api_pool",
"ApiKeyPool.acquire", _broken_acquire_no_load_balance,
[("test_api_pool", "TestEdgeCases")]) # test_released_slot_returns_least_loaded_key
_ap.ApiKeyPool.acquire = _orig_acquire2
# Mutation 1d: try_acquire ignores rate-limited keys
_orig_try_acquire = _ap.ApiKeyPool.try_acquire
def _broken_try_acquire(self):
with self._lock:
# BUG: _recover_expired_keys NOT called — rate-limited keys never recover via try_acquire
available = [k for k in self._keys if k.available]
if not available:
return None
key = min(available, key=lambda k: k.active_requests)
key.active_requests += 1
key.total_requests += 1
self._total_requests_served += 1
_now_active = sum(k.active_requests for k in self._keys)
if _now_active > self._peak_active_requests:
self._peak_active_requests = _now_active
return key
_ap.ApiKeyPool.try_acquire = _broken_try_acquire
mutate("try_acquire recovery broken", "contrib.batch_scan.api_pool",
"ApiKeyPool.try_acquire", _broken_try_acquire,
[("test_api_pool", "TestRecoveredKeyScheduling")])
_ap.ApiKeyPool.try_acquire = _orig_try_acquire
# ═══════════════════════════════════════════════════════════════════════
# Area 2: 429 backoff/recovery
# ═══════════════════════════════════════════════════════════════════════
# Mutation 2a: backoff always 5s regardless of consecutive count
_orig_release2 = _ap.ApiKeyPool.release
def _broken_release_fixed_backoff(self, key, *, success=True):
with self._condition:
key.active_requests = max(0, key.active_requests - 1)
if success:
key.consecutive_429 = 0
else:
key.consecutive_429 += 1
# BUG: always 5s, not min(30*2^(n-1), 300)
key.rate_limited_until = time.monotonic() + 5
key.rate_limited = True
self._rate_limits_hit += 1
self._condition.notify_all()
_ap.ApiKeyPool.release = _broken_release_fixed_backoff
mutate("backoff always 5s", "contrib.batch_scan.api_pool",
"ApiKeyPool.release", _broken_release_fixed_backoff,
[("test_api_pool", "TestRateLimitBackoff")])
_ap.ApiKeyPool.release = _orig_release2
# Mutation 2b: _recover_expired_keys never recovers
_orig_recover = _ap.ApiKeyPool._recover_expired_keys
def _broken_recover(self, now):
pass # BUG: never recovers rate-limited keys
_ap.ApiKeyPool._recover_expired_keys = _broken_recover
mutate("recovery never runs", "contrib.batch_scan.api_pool",
"ApiKeyPool._recover_expired_keys", _broken_recover,
[("test_api_pool", "TestRateLimitBackoff")]) # TestRecoveredKeyScheduling hangs: acquire() blocks forever w/o recovery
_ap.ApiKeyPool._recover_expired_keys = _orig_recover
# ═══════════════════════════════════════════════════════════════════════
# Area 3: Monkey-patches
# ═══════════════════════════════════════════════════════════════════════
# Mutation 3a: Patch 1 broken — doesn't set response_schema=None
import contrib.batch_scan.runner as _runner
_orig_patched_init = _runner._patched_base_init
def _broken_patched_init(self, base_prompt, model):
# BUG: forgot self.response_schema = None
_runner._original_base_init(self, base_prompt, model)
_runner._patched_base_init = _broken_patched_init
_runner.LLMAnalyzerBase.__init__ = _broken_patched_init
# Need to re-apply patches via setup for this mutation to take effect
# Actually, just test via direct replacement
del _runner._patched_base_init
# Restore properly
_runner._patched_base_init = _orig_patched_init
# Better approach: directly test with deepseek_compat context
_orig_apply = _runner._apply_patches
def _broken_apply_no_patch1():
if _runner._patches_depth > 0:
_runner._patches_depth += 1
return
_runner._verify_patch_targets()
# BUG: skipping Patch 1 (LLMAnalyzerBase.__init__)
# _runner.LLMAnalyzerBase.__init__ = _runner._patched_base_init
_runner.LLMAnalyzerBase.parse_response = _runner._patched_base_parse
_runner.LLMAnalyzerBase.build_prompt = _runner._patched_base_build_prompt
_runner.LLMMetaAnalyzer.parse_response = _runner._patched_meta_parse
_runner.LLMMetaAnalyzer.build_prompt = _runner._patched_meta_build_prompt
try:
import httpx
from langchain_openai import ChatOpenAI as _CO
_runner._original_chatopenai_init = _CO.__init__
_CO.__init__ = _runner._patched_chatopenai_init
except ImportError:
pass
_runner._asyncio.run = _runner._patched_asyncio_run
_runner._patches_depth = 1
_runner._apply_patches = _broken_apply_no_patch1
mutate("Patch 1 not applied", "contrib.batch_scan.runner",
"_apply_patches", _broken_apply_no_patch1,
[("test_runner_patches", "TestContextManagerApplyRestore")])
_runner._apply_patches = _orig_apply
# Mutation 3b: Patch 6 timeout not injected
_orig_patched_co = _runner._patched_chatopenai_init
def _broken_co_init(self, **kwargs):
# BUG: forgot to inject timeout
_runner._original_chatopenai_init(self, **kwargs)
_runner._patched_chatopenai_init = _broken_co_init
mutate("Patch 6 no timeout", "contrib.batch_scan.runner",
"_patched_chatopenai_init", _broken_co_init,
[("test_runner_patches", "TestPatch6ChatOpenAITimeout")])
_runner._patched_chatopenai_init = _orig_patched_co
# ═══════════════════════════════════════════════════════════════════════
# Area 4: GapFillAnalyzer.parse_response
# ═══════════════════════════════════════════════════════════════════════
import contrib.batch_scan.gap_fill as _gf
# Mutation 4a: confidence filter broken — threshold 0.7 → 0.0
_orig_parse = _gf.GapFillAnalyzer.parse_response
def _broken_parse_no_filter(self, response, batch):
import json as _json
text = str(response).strip()
if text.startswith("```"):
nl = text.find("\n")
if nl != -1:
text = text[nl + 1:]
if text.rstrip().endswith("```"):
text = text.rstrip()[:-3].rstrip()
try:
data = _json.loads(text)
except _json.JSONDecodeError:
return []
try:
result = _gf.GapFillResult.model_validate(data)
items = []
for item in result.findings:
if item.rule_id not in _gf._GAP_FILL_RULE_IDS:
continue
# BUG: confidence check removed — all findings pass regardless
items.append(item.to_finding(batch.file_path))
return items
except Exception:
return []
# Apply directly to class since mutation test targets the class method
_gf.GapFillAnalyzer.parse_response = _broken_parse_no_filter
mutate("confidence filter removed", "contrib.batch_scan.gap_fill",
"GapFillAnalyzer.parse_response", _broken_parse_no_filter,
[("test_gap_fill", "TestParseResponseFiltering")])
_gf.GapFillAnalyzer.parse_response = _orig_parse
# Mutation 4b: markdown fence stripping broken
_orig_parse2 = _gf.GapFillAnalyzer.parse_response
def _broken_parse_no_fence_strip(self, response, batch):
import json as _json
# BUG: fence stripping removed entirely
text = str(response) # missing .strip()
try:
data = _json.loads(text)
except _json.JSONDecodeError:
return []
try:
result = _gf.GapFillResult.model_validate(data)
return [item.to_finding(batch.file_path)
for item in result.findings
if item.rule_id in _gf._GAP_FILL_RULE_IDS and item.confidence >= 0.7]
except Exception:
return []
_gf.GapFillAnalyzer.parse_response = _broken_parse_no_fence_strip
mutate("fence stripping broken", "contrib.batch_scan.gap_fill",
"GapFillAnalyzer.parse_response", _broken_parse_no_fence_strip,
[("test_gap_fill", "TestParseResponseMarkdownFences")])
_gf.GapFillAnalyzer.parse_response = _orig_parse2
# ── Patch 2 mutation: parse_response broken ──────────────────────
_orig_patched_parse = _runner._patched_base_parse
def _broken_patched_parse(self, response, batch):
# BUG: always returns empty — JSON parsing silently broken
if isinstance(response, _runner.LLMAnalysisResult):
return _runner._original_base_parse(self, response, batch)
return [] # BUG: swallows all findings
_runner._patched_base_parse = _broken_patched_parse
_runner.LLMAnalyzerBase.parse_response = _broken_patched_parse
mutate("Patch 2 parse always empty", "contrib.batch_scan.runner",
"_patched_base_parse", _broken_patched_parse,
[("test_runner_patches", "TestContextManagerApplyRestore")])
_runner._patched_base_parse = _orig_patched_parse
# ── Patch 3 mutation: _sanitize_meta_finding broken ───────────────
_orig_meta_parse = _runner._patched_meta_parse
def _broken_meta_parse(self, response, batch):
if isinstance(response, _runner.MetaAnalyzerResult):
return _runner._original_meta_parse(self, response, batch)
text = _runner._strip_markdown_fences(str(response))
try:
import json as _json
data = _json.loads(text)
result = _runner.MetaAnalyzerResult.model_validate(data)
items = []
for f in result.findings:
d = f.model_dump()
# BUG: _sanitize_meta_finding NOT called — null fields leak through
d["_file"] = batch.file_path
items.append(d)
return items
except Exception:
return []
_runner._patched_meta_parse = _broken_meta_parse
_runner.LLMMetaAnalyzer.parse_response = _broken_meta_parse
mutate("Patch 3 sanitize broken", "contrib.batch_scan.runner",
"_patched_meta_parse", _broken_meta_parse,
[("test_runner_patches", "TestSanitizeMetaFinding")])
_runner._patched_meta_parse = _orig_meta_parse
# ── Patch 4 mutation: build_prompt appends nothing ─────────────────
_orig_base_build = _runner._patched_base_build_prompt
def _broken_base_build(self, batch, **kwargs):
# BUG: JSON instruction NOT appended
return _runner._original_base_build_prompt(self, batch, **kwargs)
_runner._patched_base_build_prompt = _broken_base_build
_runner.LLMAnalyzerBase.build_prompt = _broken_base_build
mutate("Patch 4 JSON prompt missing", "contrib.batch_scan.runner",
"_patched_base_build_prompt", _broken_base_build,
[("test_runner_patches", "TestContextManagerApplyRestore")])
_runner._patched_base_build_prompt = _orig_base_build
# ── Patch 5 mutation: meta build_prompt appends nothing ────────────
_orig_meta_build = _runner._patched_meta_build_prompt
def _broken_meta_build(self, batch, **kwargs):
return _runner._original_meta_build_prompt(self, batch, **kwargs)
_runner._patched_meta_build_prompt = _broken_meta_build
_runner.LLMMetaAnalyzer.build_prompt = _broken_meta_build
mutate("Patch 5 JSON meta prompt missing", "contrib.batch_scan.runner",
"_patched_meta_build_prompt", _broken_meta_build,
[("test_runner_patches", "TestContextManagerApplyRestore")])
_runner._patched_meta_build_prompt = _orig_meta_build
# ── Patch 7 mutation: asyncio.run NOT replaced ────────────────────
_orig_patched_asyncio = _runner._patched_asyncio_run
def _broken_asyncio_run(main, *, debug=None, loop_factory=None):
# BUG: completely bypasses the quiet-loop wrapper
return _runner._original_asyncio_run(main, debug=debug, loop_factory=loop_factory)
_runner._patched_asyncio_run = _broken_asyncio_run
mutate("Patch 7 asyncio not patched", "contrib.batch_scan.runner",
"_patched_asyncio_run", _broken_asyncio_run,
[("test_runner_patches", "TestPatch7AsyncioQuietLoop")])
_runner._patched_asyncio_run = _orig_patched_asyncio
# ── GapFill: rule_id filtering broken ─────────────────────────────
_orig_parse3 = _gf.GapFillAnalyzer.parse_response
def _broken_parse_no_rule_filter(self, response, batch):
import json as _json
text = str(response).strip()
if text.startswith("```"):
nl = text.find("\n")
if nl != -1:
text = text[nl + 1:]
if text.rstrip().endswith("```"):
text = text.rstrip()[:-3].rstrip()
try:
data = _json.loads(text)
except _json.JSONDecodeError:
return []
try:
result = _gf.GapFillResult.model_validate(data)
items = []
for item in result.findings:
if item.confidence < 0.7:
continue
# BUG: rule_id check removed — unknown rules accepted
items.append(item.to_finding(batch.file_path))
return items
except Exception:
return []
_gf.GapFillAnalyzer.parse_response = _broken_parse_no_rule_filter
mutate("rule_id filter removed", "contrib.batch_scan.gap_fill",
"GapFillAnalyzer.parse_response", _broken_parse_no_rule_filter,
[("test_gap_fill", "TestParseResponseFiltering")])
_gf.GapFillAnalyzer.parse_response = _orig_parse3
# ── GapFill: JSON decode errors not caught ─────────────────────────
_orig_parse4 = _gf.GapFillAnalyzer.parse_response
def _broken_parse_no_json_catch(self, response, batch):
import json as _json
text = str(response).strip()
if text.startswith("```"):
nl = text.find("\n")
if nl != -1:
text = text[nl + 1:]
if text.rstrip().endswith("```"):
text = text.rstrip()[:-3].rstrip()
data = _json.loads(text) # BUG: JSONDecodeError not caught — will crash
result = _gf.GapFillResult.model_validate(data)
return [item.to_finding(batch.file_path)
for item in result.findings
if item.rule_id in _gf._GAP_FILL_RULE_IDS and item.confidence >= 0.7]
_gf.GapFillAnalyzer.parse_response = _broken_parse_no_json_catch
mutate("JSON decode error not caught", "contrib.batch_scan.gap_fill",
"GapFillAnalyzer.parse_response", _broken_parse_no_json_catch,
[("test_gap_fill", "TestParseResponseInvalidInput")])
_gf.GapFillAnalyzer.parse_response = _orig_parse4
# ── GapFill: Pydantic validation errors not caught ─────────────────
_orig_parse5 = _gf.GapFillAnalyzer.parse_response
def _broken_parse_no_pydantic_catch(self, response, batch):
import json as _json
text = str(response).strip()
if text.startswith("```"):
nl = text.find("\n")
if nl != -1:
text = text[nl + 1:]
if text.rstrip().endswith("```"):
text = text.rstrip()[:-3].rstrip()
try:
data = _json.loads(text)
except _json.JSONDecodeError:
return []
result = _gf.GapFillResult.model_validate(data) # BUG: validation error not caught
return [item.to_finding(batch.file_path)
for item in result.findings
if item.rule_id in _gf._GAP_FILL_RULE_IDS and item.confidence >= 0.7]
_gf.GapFillAnalyzer.parse_response = _broken_parse_no_pydantic_catch
mutate("Pydantic validation error not caught", "contrib.batch_scan.gap_fill",
"GapFillAnalyzer.parse_response", _broken_parse_no_pydantic_catch,
[("test_gap_fill", "TestParseResponseInvalidInput")])
_gf.GapFillAnalyzer.parse_response = _orig_parse5
# ── Area 5: Hedge — untested risky code from RISK_TABLE ─────────────
# Mutation 5a: _next_available_in broken — always returns None
_orig_next_avail = _ap.ApiKeyPool._next_available_in
def _broken_next_avail(self, now):
return None # BUG: never reports recovery time — acquire() waits forever
_ap.ApiKeyPool._next_available_in = _broken_next_avail
# Note: this mutation can't be directly tested without a rate-limited+full pool scenario
# which is Q16's blind spot. Test validates the function exists but not this branch.
mutate("_next_available_in always None", "contrib.batch_scan.api_pool",
"ApiKeyPool._next_available_in", _broken_next_avail,
[]) # No matching test — documented as Q16/Q17 blind spot
_ap.ApiKeyPool._next_available_in = _orig_next_avail
# Mutation 5b: _restore_patches broken — forgets to restore Patch 6
_orig_restore = _runner._restore_patches
def _broken_restore():
if _runner._patches_depth == 0:
return
_runner._patches_depth -= 1
if _runner._patches_depth > 0:
return
_runner.LLMAnalyzerBase.__init__ = _runner._original_base_init
_runner.LLMAnalyzerBase.parse_response = _runner._original_base_parse
_runner.LLMAnalyzerBase.build_prompt = _runner._original_base_build_prompt
_runner.LLMMetaAnalyzer.parse_response = _runner._original_meta_parse
_runner.LLMMetaAnalyzer.build_prompt = _runner._original_meta_build_prompt
# BUG: Patch 6 (ChatOpenAI) and Patch 7 (asyncio) NOT restored
_runner._restore_patches = _broken_restore
mutate("_restore_patches skips Patch 6+7", "contrib.batch_scan.runner",
"_restore_patches", _broken_restore,
[("test_runner_patches", "TestContextManagerApplyRestore")])
_runner._restore_patches = _orig_restore
# Mutation 5c: _verify_patch_targets broken — always passes silently
_orig_verify = _runner._verify_patch_targets
def _broken_verify():
pass # BUG: skips all 17 checks — never raises
_runner._verify_patch_targets = _broken_verify
mutate("_verify_patch_targets no-op", "contrib.batch_scan.runner",
"_verify_patch_targets", _broken_verify,
[]) # Q13: no test asserts guard actually ran — documented blind spot
_runner._verify_patch_targets = _orig_verify
# Mutation 5d: _check_signature broken — never raises
_orig_check = _runner._check_signature
def _broken_check(func, expected, label, num):
pass # BUG: never validates — all signatures silently pass
_runner._check_signature = _broken_check
mutate("_check_signature no-op", "contrib.batch_scan.runner",
"_check_signature", _broken_check,
[]) # No test directly calls _check_signature — documented
_runner._check_signature = _orig_check
# Mutation 5e: set_api_pool broken — doesn't save original
_orig_set_api = _runner.set_api_pool
def _broken_set_api(pool):
_runner._api_pool = pool
if pool is None:
return
import skillspector.llm_utils as _u
def _bad_wrapper(model=None):
if _runner._api_pool:
from contrib.batch_scan.api_pool import PooledChatModel
return PooledChatModel(_runner._api_pool)
# BUG: fallback calls patched version instead of original
return _u.get_chat_model(model)
_u.get_chat_model = _bad_wrapper
_runner.set_api_pool = _broken_set_api
mutate("set_api_pool broken fallback", "contrib.batch_scan.runner",
"set_api_pool", _broken_set_api,
[("test_runner_patches", "TestSetApiPoolRestore")])
_runner.set_api_pool = _orig_set_api
# Mutation 5f: annotate_findings broken — always returns incompatible
import contrib.batch_scan.annotation as _ann
_orig_annotate = _ann.annotate_findings
def _broken_annotate(issues, detected_language):
annotated = []
for issue in issues:
entry = dict(issue)
entry["language_compatible"] = False # BUG: always False regardless of rule
annotated.append(entry)
return annotated
_ann.annotate_findings = _broken_annotate
mutate("annotate_findings always incompatible", "contrib.batch_scan.annotation",
"annotate_findings", _broken_annotate,
[("test_annotation", "TestAnnotateFindings")])
_ann.annotate_findings = _orig_annotate
# Mutation 5g: is_language_compatible broken — always True
_orig_is_compat = _ann.is_language_compatible
def _broken_is_compat(rule_id, detected_language):
return True # BUG: all rules compatible — English keyword rules misclassified
_ann.is_language_compatible = _broken_is_compat
mutate("is_language_compatible always True", "contrib.batch_scan.annotation",
"is_language_compatible", _broken_is_compat,
[("test_annotation", "TestAnnotateFindings")])
_ann.is_language_compatible = _orig_is_compat
# ── Area 6: Remaining untested functions from RISK_TABLE ────────────
# Mutation 6a: build_prompt broken — missing file label
_orig_build = _gf.GapFillAnalyzer.build_prompt
def _broken_build_prompt(self, batch, **kwargs):
prompt = self.base_prompt
# BUG: file_label + numbered_content NOT included — LLM gets no context
return prompt
_gf.GapFillAnalyzer.build_prompt = _broken_build_prompt
mutate("build_prompt missing file content", "contrib.batch_scan.gap_fill",
"GapFillAnalyzer.build_prompt", _broken_build_prompt,
[("test_gap_fill", "TestBuildPrompt")])
_gf.GapFillAnalyzer.build_prompt = _orig_build
# Mutation 6b: get_batches broken — always returns empty
_orig_batches = _gf.GapFillAnalyzer.get_batches
def _broken_get_batches(self, file_paths, file_cache, findings=None):
return [] # BUG: all files skipped — no analysis happens
_gf.GapFillAnalyzer.get_batches = _broken_get_batches
mutate("get_batches always empty", "contrib.batch_scan.gap_fill",
"GapFillAnalyzer.get_batches", _broken_get_batches,
[("test_gap_fill", "TestGetBatchesAndCollectFindings")])
_gf.GapFillAnalyzer.get_batches = _orig_batches
# Mutation 6c: collect_findings broken — returns empty
_orig_collect = _gf.GapFillAnalyzer.collect_findings
def _broken_collect_findings(self, batch_results):
return [] # BUG: all findings discarded
_gf.GapFillAnalyzer.collect_findings = _broken_collect_findings
mutate("collect_findings always empty", "contrib.batch_scan.gap_fill",
"GapFillAnalyzer.collect_findings", _broken_collect_findings,
[("test_gap_fill", "TestGetBatchesAndCollectFindings")])
_gf.GapFillAnalyzer.collect_findings = _orig_collect
# Mutation 6d: run_gap_fill broken — ignores all findings
_orig_run_gf = _gf.run_gap_fill
def _broken_run_gap_fill(file_cache, language, model=None, api_pool=None):
return [] # BUG: always returns empty — never runs LLM
_gf.run_gap_fill = _broken_run_gap_fill
mutate("run_gap_fill always empty", "contrib.batch_scan.gap_fill",
"run_gap_fill", _broken_run_gap_fill,
[("test_gap_fill", "TestRunGapFill")])
_gf.run_gap_fill = _orig_run_gf
# Mutation 6e: _is_rate_limit broken — always False
_orig_is_rl = _ap.PooledChatModel._is_rate_limit
def _broken_is_rl(exc):
return False # BUG: never detects rate limits — retries never happen
_ap.PooledChatModel._is_rate_limit = staticmethod(_broken_is_rl)
mutate("_is_rate_limit always False", "contrib.batch_scan.api_pool",
"PooledChatModel._is_rate_limit", staticmethod(_broken_is_rl),
[("test_api_pool", "TestIsRateLimit")])
_ap.PooledChatModel._is_rate_limit = _orig_is_rl
# Mutation 6f: create_api_key_pool_from_env broken — always returns None
_orig_create_pool = _ap.create_api_key_pool_from_env
def _broken_create_pool(max_concurrent_per_key=5):
return None # BUG: pool never created — all LLM calls use single key
_ap.create_api_key_pool_from_env = _broken_create_pool
mutate("create_api_key_pool_from_env always None", "contrib.batch_scan.api_pool",
"create_api_key_pool_from_env", _broken_create_pool,
[("test_api_pool", "TestCreateApiKeyPoolFromEnv")])
_ap.create_api_key_pool_from_env = _orig_create_pool
# Mutation 6g: deepseek_compat broken — doesn't restore on exception
from contextlib import contextmanager as _ctx_mgr
_orig_ds_compat = _runner.deepseek_compat
@_ctx_mgr
def _broken_ds_compat():
_runner._apply_patches()
try:
yield
# BUG: missing finally — patches NOT restored on exception
finally:
pass # should be _restore_patches()
_runner.deepseek_compat = _broken_ds_compat
mutate("deepseek_compat no restore on exception", "contrib.batch_scan.runner",
"deepseek_compat", _broken_ds_compat,
[("test_runner_patches", "TestContextManagerApplyRestore")])
_runner.deepseek_compat = _orig_ds_compat
# ═══════════════════════════════════════════════════════════════════════
# Summary
# ═══════════════════════════════════════════════════════════════════════
print(f"\n{'='*60}")
print(f"Mutation Test Results — Max's 4 Risk Areas")
print(f"{'='*60}")
for label, cls, caught in results:
status = "✅ CAUGHT" if caught else "❌ MISSED"
print(f" {status} | {label}{cls}")
caught = sum(1 for _, _, c in results if c)
missed = sum(1 for _, _, c in results if not c)
print(f"\nTotal: {caught}/{caught+missed} mutations caught")
if missed == 0:
print("All mutations detected — tests are real.")
else:
print(f"⚠ {missed} mutation(s) NOT caught — review blind spots.")