gabriel / muse public
test_transport_push_mpack_put_timeout.py python
124 lines 5.5 KB
Raw
sha256:e6465e8a9b7fa8e6223ed4a3576e96c568c913ae2caeb9c31f15e7a81b250b40 docs: add | jq convention to --json section of agent-guide Sonnet 4.6 1 day ago
1 """TDD — push_mpack_put timeout scaling (issue #40).
2
3 Large mpack PUT uploads were timing out at the global 300s ceiling on slow
4 uplinks. The fix: push_mpack_put uses its own timeout derived from the
5 payload size and a minimum throughput floor, with an env-var escape hatch.
6
7 PT-1 Small mpack (100 B) → timeout is at least _PUT_TIMEOUT_FLOOR.
8 PT-2 Large mpack (50 MB) → timeout > _PUT_TIMEOUT_FLOOR (scales with size).
9 PT-3 Timeout never exceeds _PUT_TIMEOUT_MAX.
10 PT-4 push_mpack_put passes the computed timeout to _urllib_do (not the
11 global _TIMEOUT_SECONDS).
12 PT-5 MUSE_PUSH_TIMEOUT_SECONDS env var overrides the computed value.
13 PT-6 MUSE_PUSH_TIMEOUT_SECONDS=0 (or missing) keeps the computed default.
14 PT-7 push_mpack_put timeout is independent of the global _TIMEOUT_SECONDS —
15 changing the global does not affect the PUT timeout.
16 """
17 from __future__ import annotations
18
19 import os
20 from unittest.mock import patch
21
22 import pytest
23
24 from muse.core.transport import (
25 HttpTransport,
26 TransportError,
27 _mpack_put_timeout,
28 )
29
30 _UPLOAD_URL = "https://r2.example.com/mpacks/sha256:abc?X-Amz-Signature=fake"
31
32
33 def _mock_urllib_do(captured: list[dict]) -> object:
34 def _side(method: str, url: str, headers: dict, data: bytes | None = None, **kw: object) -> bytes:
35 captured.append({"method": method, "url": url, "timeout": kw.get("timeout")})
36 return b""
37 return _side
38
39
40 # ── PT-1 ─────────────────────────────────────────────────────────────────────
41
42 def test_pt1_small_payload_gets_floor_timeout() -> None:
43 small = b"x" * 100
44 t = _mpack_put_timeout(len(small))
45 from muse.core.transport import _PUT_TIMEOUT_FLOOR
46 assert t >= _PUT_TIMEOUT_FLOOR
47
48
49 # ── PT-2 ─────────────────────────────────────────────────────────────────────
50
51 def test_pt2_large_payload_exceeds_floor() -> None:
52 # 100 MB / 50_000 bytes/sec = 2000s > _PUT_TIMEOUT_FLOOR (1800s)
53 large = 100 * 1024 * 1024 # 100 MB
54 from muse.core.transport import _PUT_TIMEOUT_FLOOR
55 assert _mpack_put_timeout(large) > _PUT_TIMEOUT_FLOOR
56
57
58 # ── PT-3 ─────────────────────────────────────────────────────────────────────
59
60 def test_pt3_timeout_never_exceeds_max() -> None:
61 enormous = 100 * 1024 * 1024 * 1024 # 100 GB (absurd)
62 from muse.core.transport import _PUT_TIMEOUT_MAX
63 assert _mpack_put_timeout(enormous) <= _PUT_TIMEOUT_MAX
64
65
66 # ── PT-4 ─────────────────────────────────────────────────────────────────────
67
68 def test_pt4_computed_timeout_passed_to_urllib_do() -> None:
69 mpack = b"m" * (40 * 1024 * 1024) # 40 MB
70 expected_timeout = _mpack_put_timeout(len(mpack))
71 calls: list[dict] = []
72
73 with patch("muse.core.transport._urllib_do", side_effect=_mock_urllib_do(calls)):
74 HttpTransport().push_mpack_put(_UPLOAD_URL, mpack)
75
76 assert calls, "no _urllib_do call recorded"
77 assert calls[0]["timeout"] == expected_timeout
78
79
80 # ── PT-5 ─────────────────────────────────────────────────────────────────────
81
82 def test_pt5_env_var_overrides_computed_timeout() -> None:
83 mpack = b"m" * (40 * 1024 * 1024)
84 calls: list[dict] = []
85
86 with patch.dict(os.environ, {"MUSE_PUSH_TIMEOUT_SECONDS": "9999"}):
87 with patch("muse.core.transport._urllib_do", side_effect=_mock_urllib_do(calls)):
88 HttpTransport().push_mpack_put(_UPLOAD_URL, mpack)
89
90 assert calls[0]["timeout"] == 9999
91
92
93 # ── PT-6 ─────────────────────────────────────────────────────────────────────
94
95 def test_pt6_env_var_absent_uses_computed_default() -> None:
96 mpack = b"m" * (40 * 1024 * 1024)
97 expected_timeout = _mpack_put_timeout(len(mpack))
98 calls: list[dict] = []
99
100 env = {k: v for k, v in os.environ.items() if k != "MUSE_PUSH_TIMEOUT_SECONDS"}
101 with patch.dict(os.environ, env, clear=True):
102 with patch("muse.core.transport._urllib_do", side_effect=_mock_urllib_do(calls)):
103 HttpTransport().push_mpack_put(_UPLOAD_URL, mpack)
104
105 assert calls[0]["timeout"] == expected_timeout
106
107
108 # ── PT-7 ─────────────────────────────────────────────────────────────────────
109
110 def test_pt7_global_timeout_does_not_affect_put_timeout() -> None:
111 import muse.core.transport as _t
112 mpack = b"m" * (40 * 1024 * 1024)
113 expected_timeout = _mpack_put_timeout(len(mpack))
114 calls: list[dict] = []
115
116 original = _t._TIMEOUT_SECONDS
117 try:
118 _t._TIMEOUT_SECONDS = 1 # change global to something tiny
119 with patch("muse.core.transport._urllib_do", side_effect=_mock_urllib_do(calls)):
120 HttpTransport().push_mpack_put(_UPLOAD_URL, mpack)
121 finally:
122 _t._TIMEOUT_SECONDS = original
123
124 assert calls[0]["timeout"] == expected_timeout
File History 1 commit
sha256:e6465e8a9b7fa8e6223ed4a3576e96c568c913ae2caeb9c31f15e7a81b250b40 docs: add | jq convention to --json section of agent-guide Sonnet 4.6 1 day ago