gabriel / muse public
test_terminal_supercharge.py python
289 lines 13.6 KB
Raw
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 20 days ago
1 """Seven-tier tests for ``muse/core/terminal.py`` — ``use_color``.
2
3 Tiers
4 -----
5 Unit — each branch: NO_COLOR set, TERM=dumb, isatty True/False.
6 Integration — env var combinations, interaction with stdout redirection.
7 End-to-end — CLI commands respect use_color (no ANSI in piped output).
8 Stress — 100 000 calls; concurrent calls with env mutation.
9 Data integrity — return value is strictly bool; NO_COLOR value doesn't matter.
10 Security — hostile env var values do not crash or inject.
11 Performance — 100 000 calls under 1 s.
12 """
13
14 from __future__ import annotations
15
16 import os
17 import pathlib
18 import subprocess
19 import sys
20 import threading
21 import time
22 from unittest import mock
23
24 import pytest
25
26
27 # ──────────────────────────────────────────────────────────────────────────────
28 # Unit — branch-by-branch
29 # ──────────────────────────────────────────────────────────────────────────────
30
31
32 class TestUnit:
33 def test_no_color_set_returns_false(self) -> None:
34 from muse.core.terminal import use_color
35 with mock.patch.dict(os.environ, {"NO_COLOR": "1"}, clear=False):
36 assert use_color() is False
37
38 def test_no_color_empty_string_returns_false(self) -> None:
39 """NO_COLOR spec says any value (including empty) disables colour."""
40 from muse.core.terminal import use_color
41 with mock.patch.dict(os.environ, {"NO_COLOR": ""}, clear=False):
42 assert use_color() is False
43
44 def test_term_dumb_returns_false(self) -> None:
45 from muse.core.terminal import use_color
46 env = {k: v for k, v in os.environ.items() if k != "NO_COLOR"}
47 env["TERM"] = "dumb"
48 with mock.patch.dict(os.environ, env, clear=True):
49 assert use_color() is False
50
51 def test_tty_true_no_inhibitors_returns_true(self) -> None:
52 from muse.core.terminal import use_color
53 env = {k: v for k, v in os.environ.items()
54 if k not in ("NO_COLOR", "TERM")}
55 with mock.patch.dict(os.environ, env, clear=True), \
56 mock.patch("sys.stdout") as mock_stdout:
57 mock_stdout.isatty.return_value = True
58 assert use_color() is True
59
60 def test_tty_false_returns_false(self) -> None:
61 from muse.core.terminal import use_color
62 env = {k: v for k, v in os.environ.items()
63 if k not in ("NO_COLOR", "TERM")}
64 with mock.patch.dict(os.environ, env, clear=True), \
65 mock.patch("sys.stdout") as mock_stdout:
66 mock_stdout.isatty.return_value = False
67 assert use_color() is False
68
69 def test_returns_bool_type(self) -> None:
70 from muse.core.terminal import use_color
71 with mock.patch.dict(os.environ, {"NO_COLOR": "1"}):
72 result = use_color()
73 assert type(result) is bool
74
75
76 # ──────────────────────────────────────────────────────────────────────────────
77 # Integration — env var combinations
78 # ──────────────────────────────────────────────────────────────────────────────
79
80
81 class TestIntegration:
82 def test_no_color_beats_tty(self) -> None:
83 """NO_COLOR must win even when stdout is a TTY."""
84 from muse.core.terminal import use_color
85 with mock.patch.dict(os.environ, {"NO_COLOR": "1"}), \
86 mock.patch("sys.stdout") as mock_stdout:
87 mock_stdout.isatty.return_value = True
88 assert use_color() is False
89
90 def test_term_dumb_beats_tty(self) -> None:
91 from muse.core.terminal import use_color
92 env = {k: v for k, v in os.environ.items() if k != "NO_COLOR"}
93 env["TERM"] = "dumb"
94 with mock.patch.dict(os.environ, env, clear=True), \
95 mock.patch("sys.stdout") as mock_stdout:
96 mock_stdout.isatty.return_value = True
97 assert use_color() is False
98
99 def test_no_color_and_term_dumb_both_set_returns_false(self) -> None:
100 from muse.core.terminal import use_color
101 with mock.patch.dict(os.environ, {"NO_COLOR": "1", "TERM": "dumb"}):
102 assert use_color() is False
103
104 def test_term_xterm_not_dumb_does_not_inhibit(self) -> None:
105 from muse.core.terminal import use_color
106 env = {k: v for k, v in os.environ.items() if k != "NO_COLOR"}
107 env["TERM"] = "xterm-256color"
108 with mock.patch.dict(os.environ, env, clear=True), \
109 mock.patch("sys.stdout") as mock_stdout:
110 mock_stdout.isatty.return_value = True
111 assert use_color() is True
112
113 def test_no_color_unset_term_non_dumb_tty_false_returns_false(self) -> None:
114 from muse.core.terminal import use_color
115 env = {k: v for k, v in os.environ.items()
116 if k not in ("NO_COLOR",)}
117 env["TERM"] = "xterm"
118 with mock.patch.dict(os.environ, env, clear=True), \
119 mock.patch("sys.stdout") as mock_stdout:
120 mock_stdout.isatty.return_value = False
121 assert use_color() is False
122
123
124 # ──────────────────────────────────────────────────────────────────────────────
125 # End-to-end — subprocess with NO_COLOR; CLI output has no ANSI
126 # ──────────────────────────────────────────────────────────────────────────────
127
128
129 class TestEndToEnd:
130 def test_no_color_env_prevents_ansi_in_subprocess(self, tmp_path: "pathlib.Path") -> None:
131 """When NO_COLOR is set, use_color() returns False in a fresh process."""
132 script = "from muse.core.terminal import use_color; print(use_color())"
133 result = subprocess.run(
134 [sys.executable, "-c", script],
135 env={**os.environ, "NO_COLOR": "1"},
136 capture_output=True, text=True,
137 )
138 assert result.stdout.strip() == "False"
139
140 def test_term_dumb_prevents_ansi_in_subprocess(self, tmp_path: "pathlib.Path") -> None:
141 script = "from muse.core.terminal import use_color; print(use_color())"
142 env = {k: v for k, v in os.environ.items() if k != "NO_COLOR"}
143 env["TERM"] = "dumb"
144 result = subprocess.run(
145 [sys.executable, "-c", script],
146 env=env, capture_output=True, text=True,
147 )
148 assert result.stdout.strip() == "False"
149
150 def test_piped_output_is_not_tty(self) -> None:
151 """When stdout is redirected to a pipe, isatty() returns False."""
152 script = "import sys; print(sys.stdout.isatty())"
153 result = subprocess.run(
154 [sys.executable, "-c", script],
155 capture_output=True, text=True,
156 )
157 assert result.stdout.strip() == "False"
158
159
160 # ──────────────────────────────────────────────────────────────────────────────
161 # Stress
162 # ──────────────────────────────────────────────────────────────────────────────
163
164
165 class TestStress:
166 def test_100000_calls_no_crash(self) -> None:
167 from muse.core.terminal import use_color
168 with mock.patch.dict(os.environ, {"NO_COLOR": "1"}):
169 for _ in range(100_000):
170 use_color()
171
172 def test_concurrent_calls_consistent(self) -> None:
173 """Concurrent calls with NO_COLOR=1 must all return False."""
174 from muse.core.terminal import use_color
175 results: list[bool] = []
176 lock = threading.Lock()
177
178 def _call() -> None:
179 with mock.patch.dict(os.environ, {"NO_COLOR": "1"}):
180 v = use_color()
181 with lock:
182 results.append(v)
183
184 threads = [threading.Thread(target=_call) for _ in range(100)]
185 for t in threads:
186 t.start()
187 for t in threads:
188 t.join()
189 assert all(v is False for v in results)
190 assert len(results) == 100
191
192 def test_alternating_no_color_calls(self) -> None:
193 from muse.core.terminal import use_color
194 for i in range(10_000):
195 with mock.patch.dict(os.environ, {"NO_COLOR": "1"} if i % 2 == 0 else {}):
196 result = use_color()
197 if i % 2 == 0:
198 assert result is False
199
200
201 # ──────────────────────────────────────────────────────────────────────────────
202 # Data integrity
203 # ──────────────────────────────────────────────────────────────────────────────
204
205
206 class TestDataIntegrity:
207 def test_return_is_always_bool(self) -> None:
208 from muse.core.terminal import use_color
209 for env_patch, isatty in [
210 ({"NO_COLOR": "1"}, True),
211 ({}, False),
212 ]:
213 with mock.patch.dict(os.environ, env_patch), \
214 mock.patch("sys.stdout") as m:
215 m.isatty.return_value = isatty
216 result = use_color()
217 assert type(result) is bool
218
219 def test_no_color_any_value_returns_false(self) -> None:
220 """NO_COLOR spec: any non-empty value disables colour."""
221 from muse.core.terminal import use_color
222 for val in ("1", "true", "yes", "0", "false", "no", "anything"):
223 with mock.patch.dict(os.environ, {"NO_COLOR": val}):
224 assert use_color() is False
225
226 def test_result_is_deterministic_for_same_env(self) -> None:
227 from muse.core.terminal import use_color
228 with mock.patch.dict(os.environ, {"NO_COLOR": "1"}):
229 results = {use_color() for _ in range(1000)}
230 assert results == {False}
231
232
233 # ──────────────────────────────────────────────────────────────────────────────
234 # Security
235 # ──────────────────────────────────────────────────────────────────────────────
236
237
238 class TestSecurity:
239 def test_ansi_in_no_color_value_does_not_crash(self) -> None:
240 from muse.core.terminal import use_color
241 with mock.patch.dict(os.environ, {"NO_COLOR": "\x1b[31mred\x1b[0m"}):
242 result = use_color()
243 assert result is False # any value for NO_COLOR disables colour
244
245 def test_null_byte_in_term_rejected_by_os(self) -> None:
246 """os.environ rejects null bytes at the OS level — verify the ValueError."""
247 env = {k: v for k, v in os.environ.items() if k != "NO_COLOR"}
248 env["TERM"] = "xterm\x00malicious"
249 with pytest.raises((ValueError, TypeError)):
250 mock.patch.dict(os.environ, env, clear=True).__enter__()
251
252 def test_very_long_no_color_value_does_not_crash(self) -> None:
253 from muse.core.terminal import use_color
254 with mock.patch.dict(os.environ, {"NO_COLOR": "x" * 100_000}):
255 assert use_color() is False
256
257 def test_very_long_term_value_does_not_crash(self) -> None:
258 from muse.core.terminal import use_color
259 env = {k: v for k, v in os.environ.items() if k != "NO_COLOR"}
260 env["TERM"] = "x" * 100_000
261 with mock.patch.dict(os.environ, env, clear=True), \
262 mock.patch("sys.stdout") as m:
263 m.isatty.return_value = False
264 result = use_color()
265 assert type(result) is bool
266
267
268 # ──────────────────────────────────────────────────────────────────────────────
269 # Performance
270 # ──────────────────────────────────────────────────────────────────────────────
271
272
273 class TestPerformance:
274 def test_100000_calls_under_1s(self) -> None:
275 from muse.core.terminal import use_color
276 with mock.patch.dict(os.environ, {"NO_COLOR": "1"}):
277 start = time.perf_counter()
278 for _ in range(100_000):
279 use_color()
280 elapsed = time.perf_counter() - start
281 assert elapsed < 1.0, f"100 000 calls took {elapsed:.2f}s — expected < 1s"
282
283 def test_single_call_under_1ms(self) -> None:
284 from muse.core.terminal import use_color
285 with mock.patch.dict(os.environ, {"NO_COLOR": "1"}):
286 start = time.perf_counter()
287 use_color()
288 elapsed = time.perf_counter() - start
289 assert elapsed < 0.001, f"Single call took {elapsed*1000:.2f}ms — expected < 1ms"
File History 4 commits
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 20 days ago
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 22 days ago
sha256:c06a9b9b9fee26c68ea725b44d54b2c0a171301ce9de746d5b656617b4463a9a fix: repair four test failures from post-migration audit Sonnet 4.6 patch 28 days ago
sha256:1900655993c83c4107067375548a7be823e471d2515830842f1a12cba4bd3cdf fix: unified object store migration — idempotent writes, JS… Sonnet 4.6 minor 28 days ago