|
| 1 | +"""Regression lock: the JS and Go parse subprocesses must pass a wall-clock timeout, |
| 2 | +so a hung inner Node/Go parser fails cleanly instead of blocking the Python parent forever. |
| 3 | +
|
| 4 | +C/Ruby/PHP/Zig already pass timeout=1800; JS and Go did not, so a direct-Python caller of |
| 5 | +parse_repository / _parse_javascript / _parse_go had no bound. These tests fail on master |
| 6 | +(no timeout) and pass with the fix. |
| 7 | +""" |
| 8 | +import inspect |
| 9 | +import tempfile |
| 10 | + |
| 11 | +from core import parser_adapter |
| 12 | + |
| 13 | + |
| 14 | +class _FakeCompleted: |
| 15 | + returncode = 0 |
| 16 | + |
| 17 | + |
| 18 | +def _capture_run_calls(monkeypatch): |
| 19 | + calls = [] |
| 20 | + |
| 21 | + def fake_run(cmd, **kwargs): |
| 22 | + calls.append((cmd, kwargs)) |
| 23 | + return _FakeCompleted() |
| 24 | + |
| 25 | + monkeypatch.setattr(parser_adapter.subprocess, "run", fake_run) |
| 26 | + return calls |
| 27 | + |
| 28 | + |
| 29 | +def _parse_call_timeout(calls, script_name): |
| 30 | + """timeout kwarg of the subprocess.run whose cmd invokes <script_name> (the parse step).""" |
| 31 | + for cmd, kwargs in calls: |
| 32 | + if any(script_name in str(part) for part in cmd): |
| 33 | + return kwargs.get("timeout") |
| 34 | + return "NO-PARSE-CALL" |
| 35 | + |
| 36 | + |
| 37 | +def test_js_parse_subprocess_has_timeout(monkeypatch): |
| 38 | + calls = _capture_run_calls(monkeypatch) |
| 39 | + d = tempfile.mkdtemp() |
| 40 | + try: |
| 41 | + parser_adapter._parse_javascript(d, d, "reachable") |
| 42 | + except Exception: |
| 43 | + pass # we only care that the parse subprocess.run received a timeout |
| 44 | + assert _parse_call_timeout(calls, "test_pipeline.py") == 1800 |
| 45 | + |
| 46 | + |
| 47 | +def test_go_parse_subprocess_has_timeout(monkeypatch): |
| 48 | + calls = _capture_run_calls(monkeypatch) |
| 49 | + d = tempfile.mkdtemp() |
| 50 | + try: |
| 51 | + parser_adapter._parse_go(d, d, "reachable") |
| 52 | + except Exception: |
| 53 | + pass |
| 54 | + assert _parse_call_timeout(calls, "test_pipeline.py") == 1800 |
| 55 | + |
| 56 | + |
| 57 | +def test_all_subprocess_parsers_are_uniformly_timed(): |
| 58 | + """Every language whose *parse step* runs as a subprocess must carry a timeout — not just |
| 59 | + 4 of them. (Python parses in-process, so it is intentionally excluded; the npm-install |
| 60 | + dependency bootstrap in _ensure_js_parser_dependencies is a separate concern, not a parse.)""" |
| 61 | + for lang in ("_parse_javascript", "_parse_go", "_parse_c", |
| 62 | + "_parse_ruby", "_parse_php", "_parse_zig"): |
| 63 | + src = inspect.getsource(getattr(parser_adapter, lang)) |
| 64 | + assert "timeout=" in src, f"{lang} subprocess.run is missing a timeout" |
0 commit comments