tenseleyflow/sway / 4346b16

Browse files

tests/dlm_not_imported: dummy suite + autogen clean-error when dlm absent (C7)

Authored by espadonne
SHA
4346b16350e1a78c311a664385b1c7d84276969c
Parents
4c0d941
Tree
4110cee

1 changed file

StatusFile+-
A tests/unit/test_dlm_not_imported.py 88 0
tests/unit/test_dlm_not_imported.pyadded
@@ -0,0 +1,88 @@
1
+"""C7: positively assert that sway works without ``dlm`` installed.
2
+
3
+The architectural promise from the plan: ``dlm-sway`` does not depend
4
+on the ``dlm`` package; the bridge under
5
+``src/dlm_sway/integrations/dlm/`` is the only place that imports it,
6
+and only when actually invoked.
7
+
8
+This test simulates the dlm-not-installed environment by patching
9
+``sys.modules['dlm'] = None`` (which makes Python's import machinery
10
+raise ``ModuleNotFoundError`` on subsequent ``import dlm``). It then:
11
+
12
+1. Builds a small dummy-backend suite end-to-end and confirms it runs
13
+   to completion with no ImportError.
14
+2. Invokes ``sway autogen`` and confirms a clean error pointing at
15
+   the ``[dlm]`` extra (rather than a stack trace).
16
+
17
+The integration suite cannot actually uninstall dlm at test time, so
18
+this is the next-best regression guard — it pins the lazy-import
19
+boundary against future refactors that might pull dlm into a
20
+top-level ``dlm_sway.*`` import path.
21
+"""
22
+
23
+from __future__ import annotations
24
+
25
+import sys
26
+
27
+import pytest
28
+from typer.testing import CliRunner
29
+
30
+from dlm_sway.backends.dummy import DummyDifferentialBackend, DummyResponses
31
+from dlm_sway.cli.app import app
32
+from dlm_sway.suite.runner import run as run_suite
33
+from dlm_sway.suite.spec import SwaySpec
34
+
35
+
36
+@pytest.fixture
37
+def dlm_unimportable(monkeypatch: pytest.MonkeyPatch):
38
+    """Make ``import dlm`` raise ModuleNotFoundError for the test."""
39
+    # Block the parent package and any submodule the bridge imports.
40
+    for name in (
41
+        "dlm",
42
+        "dlm.doc",
43
+        "dlm.doc.parser",
44
+        "dlm.base_models",
45
+        "dlm.store",
46
+        "dlm.store.paths",
47
+        "dlm.data",
48
+        "dlm.data.instruction_parser",
49
+        "dlm.data.preference_parser",
50
+    ):
51
+        monkeypatch.setitem(sys.modules, name, None)
52
+
53
+
54
+def test_dummy_suite_runs_without_dlm(dlm_unimportable) -> None:
55
+    """A normal suite run never touches ``dlm`` and must work without it."""
56
+    backend = DummyDifferentialBackend(base=DummyResponses(), ft=DummyResponses())
57
+    spec = SwaySpec.model_validate(
58
+        {
59
+            "version": 1,
60
+            "models": {
61
+                "base": {"base": "b"},
62
+                "ft": {"base": "b", "adapter": "/tmp/a"},
63
+            },
64
+            "suite": [
65
+                {
66
+                    "name": "dk",
67
+                    "kind": "delta_kl",
68
+                    "prompts": ["q1", "q2"],
69
+                    "assert_mean_gte": 0.0,
70
+                }
71
+            ],
72
+        }
73
+    )
74
+    result = run_suite(spec, backend)
75
+    assert len(result.probes) == 1
76
+
77
+
78
+def test_autogen_emits_clean_error_without_dlm(dlm_unimportable, tmp_path) -> None:
79
+    """``sway autogen`` is the one path that needs dlm — it must surface
80
+    a clean error pointing at the ``[dlm]`` extra, not a stack trace."""
81
+    fake_dlm = tmp_path / "doesnt-matter.dlm"
82
+    fake_dlm.write_text("# stub\n")
83
+    result = CliRunner().invoke(app, ["autogen", str(fake_dlm)])
84
+    assert result.exit_code == 2, (
85
+        f"expected exit 2; got {result.exit_code}\nstdout: {result.stdout}\nstderr: {result.stderr if result.stderr_bytes else ''}"
86
+    )
87
+    combined = (result.stdout or "") + (result.stderr or "")
88
+    assert "dlm-sway[dlm]" in combined, f"expected install hint in error output; got {combined!r}"