test_smoke_model.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. from __future__ import annotations
  2. import json
  3. import runpy
  4. from pathlib import Path
  5. import pytest
  6. ROOT = Path(__file__).resolve().parents[3]
  7. SMOKE = runpy.run_path(ROOT / "scripts" / "smoke-python-runtime.py")
  8. @pytest.mark.parametrize(
  9. ("prompt_name", "expected"),
  10. [
  11. ("SNAPSHOT_DIRECT_CHILD_PROMPT", "DIRECT_CHILD_OK"),
  12. ("SNAPSHOT_WORKFLOW_CHILD_PROMPT", "WORKFLOW_CHILD_OK"),
  13. ],
  14. )
  15. def test_child_prompt_precedes_runtime_context(prompt_name: str, expected: str) -> None:
  16. chunks = SMOKE["completion_chunks"]({
  17. "messages": [
  18. {"role": "user", "content": SMOKE[prompt_name]},
  19. {"role": "user", "content": "Current runtime context"},
  20. ],
  21. })
  22. assert any(
  23. choice.get("delta", {}).get("content") == expected
  24. for chunk in chunks
  25. for choice in chunk.get("choices", [])
  26. )
  27. def test_mcp_smoke_requests_the_discovered_tool() -> None:
  28. chunks = SMOKE["completion_chunks"]({
  29. "messages": [{"role": "user", "content": SMOKE["MCP_PROMPT"]}],
  30. "tools": [{"type": "function", "function": {"name": "mcp__fixture__add"}}],
  31. })
  32. calls = [
  33. call
  34. for chunk in chunks
  35. for choice in chunk.get("choices", [])
  36. for call in choice.get("delta", {}).get("tool_calls", [])
  37. ]
  38. assert calls[0]["function"] == {
  39. "name": "mcp__fixture__add",
  40. "arguments": '{"a": 19, "b": 23}',
  41. }
  42. def test_mcp_smoke_accepts_the_external_server_result() -> None:
  43. chunks = SMOKE["completion_chunks"]({
  44. "messages": [
  45. {"role": "user", "content": SMOKE["MCP_PROMPT"]},
  46. {
  47. "role": "assistant",
  48. "tool_calls": [{
  49. "id": "mcp-add",
  50. "type": "function",
  51. "function": {"name": "mcp__fixture__add", "arguments": '{}'},
  52. }],
  53. },
  54. {"role": "tool", "tool_call_id": "mcp-add", "content": "42"},
  55. ],
  56. })
  57. assert any(
  58. choice.get("delta", {}).get("content") == SMOKE["MCP_TEXT"]
  59. for chunk in chunks
  60. for choice in chunk.get("choices", [])
  61. )
  62. def test_snapshot_comparison_normalizes_only_session_generation_provenance() -> None:
  63. normalize = SMOKE["normalize_session_format_comparison"]
  64. expected = {
  65. "header": {"type": "session", "version": 0, "otherVersion": 7},
  66. "accepted": {
  67. "type": "session-log-deepseek/delivery-accepted",
  68. "data": {"sessionId": "s", "throughSeq": 4},
  69. },
  70. "source": {
  71. "kind": "session-reference",
  72. "references": [{"sessionId": "other", "capturedThroughSeq": 8}],
  73. },
  74. }
  75. actual = {
  76. "header": {"type": "session", "version": 1, "otherVersion": 7},
  77. "accepted": {
  78. "type": "session-log-deepseek/delivery-accepted",
  79. "data": {"sessionId": "s", "sessionFormatVersion": 1, "throughSeq": 4},
  80. },
  81. "source": {
  82. "kind": "session-reference",
  83. "references": [{
  84. "sessionId": "other",
  85. "capturedFormatVersion": 1,
  86. "capturedThroughSeq": 8,
  87. }],
  88. },
  89. }
  90. assert normalize(expected) == normalize(actual)
  91. assert normalize(expected)["header"]["otherVersion"] == 7
  92. def test_snapshot_value_normalizes_embedded_assistant_stream_timing() -> None:
  93. normalize = SMOKE["normalize_snapshot_value"]
  94. event = {
  95. "type": "assistant/message",
  96. "seq": 4,
  97. "time": 100,
  98. "data": {
  99. "stream": [
  100. {"type": "chunk", "time": 101, "chunk": {"type": "finish"}},
  101. {"type": "text-chunks", "time0": 102, "dt": [1, 2], "texts": ["a", "b", "c"]},
  102. ],
  103. },
  104. }
  105. normalized = normalize(event, [])
  106. assert normalized["time"] == 0
  107. assert normalized["data"]["stream"] == [
  108. {"type": "chunk", "time": 0, "chunk": {"type": "finish"}},
  109. {"type": "text-chunks", "time0": 0, "dt": [0, 0], "texts": ["a", "b", "c"]},
  110. ]
  111. def test_snapshot_comparison_expands_embedded_assistant_streams() -> None:
  112. normalize = SMOKE["normalize_session_format_comparison"]
  113. expected = [
  114. {
  115. "type": "assistant/chunk",
  116. "seq": 4,
  117. "time": 0,
  118. "data": {"turn": 1, "step": 1, "chunk": {
  119. "type": "text-delta", "index": 0, "text": "done",
  120. }},
  121. },
  122. {
  123. "type": "assistant/message",
  124. "seq": 5,
  125. "time": 0,
  126. "data": {"turn": 1, "step": 1, "message": {"role": "assistant"}},
  127. "sourceEventSeqs": [4],
  128. "surfaceOp": "append",
  129. },
  130. ]
  131. actual = [{
  132. "type": "assistant/message",
  133. "seq": 4,
  134. "time": 0,
  135. "data": {
  136. "turn": 1,
  137. "step": 1,
  138. "message": {"role": "assistant"},
  139. "stream": [{
  140. "type": "text-chunks", "time0": 0, "index": 0, "dt": [], "texts": ["done"],
  141. }],
  142. },
  143. "surfaceOp": "append",
  144. }]
  145. assert normalize(actual, 2) == normalize(expected, 1)
  146. tool_result = {
  147. "type": "tool/result",
  148. "data": {"turn": 1, "step": 1},
  149. "sourceEventSeqs": [4],
  150. }
  151. assert normalize(tool_result, 1)["sourceEventSeqs"] == [4]
  152. assert normalize(tool_result, 2)["sourceEventSeqs"] == [4]
  153. def test_snapshot_stream_expands_reasoning_and_tool_call_records() -> None:
  154. expand = SMOKE["expand_snapshot_stream_member"]
  155. assert expand({
  156. "type": "reasoning-chunks", "time0": 0, "index": 1,
  157. "dt": [], "texts": ["think"],
  158. }) == [{"type": "reasoning-delta", "index": 1, "text": "think"}]
  159. assert expand({
  160. "type": "tool-call-chunks", "time0": 0, "index": 2,
  161. "id": "call-1", "name": "read", "dt": [1], "args": ["{", "}"],
  162. }) == [
  163. {"type": "tool-call-delta", "index": 2, "id": "call-1", "name": "read", "argumentsDelta": "{"},
  164. {"type": "tool-call-delta", "index": 2, "id": "call-1", "name": "read", "argumentsDelta": "}"},
  165. ]
  166. def test_snapshot_file_builder_order_is_checked_outside_update_mode(tmp_path: Path) -> None:
  167. compare = SMOKE["compare_snapshot_files"]
  168. with pytest.raises(AssertionError, match="snapshot builder produced"):
  169. compare({}, False, tmp_path, ("result.json",))
  170. def test_snapshot_comparison_expands_sdk_wrapped_attempts() -> None:
  171. normalize = SMOKE["normalize_session_format_comparison"]
  172. actual = [{
  173. "method": "session.event",
  174. "payload": {
  175. "sessionId": "s",
  176. "event": {
  177. "type": "assistant/attempt",
  178. "seq": 7,
  179. "time": 0,
  180. "data": {
  181. "turn": 1,
  182. "step": 1,
  183. "stream": [{"type": "chunk", "time": 0, "chunk": {"type": "finish"}}],
  184. },
  185. },
  186. },
  187. }]
  188. assert normalize(actual) == [{
  189. "method": "session.event",
  190. "payload": {
  191. "sessionId": "s",
  192. "event": {
  193. "type": "assistant/chunk",
  194. "data": {"turn": 1, "step": 1, "chunk": {"type": "finish"}},
  195. },
  196. },
  197. }]
  198. def test_snapshot_generation_names_select_highest_role_without_double_counting(
  199. tmp_path: Path,
  200. ) -> None:
  201. render = SMOKE["snapshot_session_filename"]
  202. select = SMOKE["selected_snapshot_session_files"]
  203. assert render(0, 0) == "session.jsonl"
  204. assert render(0, 2) == "session.v2.jsonl"
  205. assert render(3, 0) == "session.3.jsonl"
  206. assert render(3, 2) == "session.3.v2.jsonl"
  207. (tmp_path / "session.jsonl").write_text(
  208. '{"type":"session","version":0}\n', encoding="utf-8",
  209. )
  210. (tmp_path / "session.v1.jsonl").write_text(
  211. '{"type":"session","version":1}\n', encoding="utf-8",
  212. )
  213. (tmp_path / "session.1.jsonl").write_text(
  214. '{"type":"session","version":0}\n', encoding="utf-8",
  215. )
  216. assert {index: path.name for index, path in select(tmp_path).items()} == {
  217. 0: "session.v1.jsonl",
  218. 1: "session.1.jsonl",
  219. }
  220. def test_snapshot_comparison_accepts_v3_output_against_v2_without_rewriting(tmp_path: Path) -> None:
  221. predecessor = '{"type":"session","version":2}\n'
  222. successor = '{"type":"session","version":3}\n'
  223. old_path = tmp_path / "session.v2.jsonl"
  224. old_path.write_text(predecessor, encoding="utf-8")
  225. files = {"session.v3.jsonl": successor}
  226. SMOKE["compare_snapshot_files"](files, False, tmp_path, ("session.v2.jsonl",))
  227. assert old_path.read_text(encoding="utf-8") == predecessor
  228. assert not (tmp_path / "session.v3.jsonl").exists()
  229. SMOKE["compare_snapshot_files"](files, True, tmp_path, ("session.v2.jsonl",))
  230. assert old_path.read_text(encoding="utf-8") == predecessor
  231. assert (tmp_path / "session.v3.jsonl").read_text(encoding="utf-8") == successor
  232. assert SMOKE["selected_snapshot_session_files"](tmp_path) == {0: tmp_path / "session.v3.jsonl"}
  233. @pytest.mark.parametrize("filenames", [
  234. ("session.1.v2.jsonl", "session.v2.jsonl"),
  235. ("session.v2.jsonl",),
  236. ("session.v2.jsonl", "session.2.v2.jsonl"),
  237. ])
  238. def test_snapshot_builder_checks_role_order_and_count_across_generations(
  239. tmp_path: Path, filenames: tuple[str, ...],
  240. ) -> None:
  241. files = {"session.v3.jsonl": "", "session.1.v3.jsonl": ""}
  242. with pytest.raises(AssertionError, match="snapshot builder produced"):
  243. SMOKE["compare_snapshot_files"](files, False, tmp_path, filenames)
  244. def test_snapshot_generation_comparison_rejects_changed_payload(tmp_path: Path) -> None:
  245. (tmp_path / "session.v2.jsonl").write_text(
  246. '{"type":"session","version":2,"id":"expected"}\n', encoding="utf-8",
  247. )
  248. with pytest.raises(AssertionError, match="executable snapshot mismatch"):
  249. SMOKE["compare_snapshot_files"](
  250. {"session.v3.jsonl": '{"type":"session","version":3,"id":"changed"}\n'},
  251. False, tmp_path, ("session.v2.jsonl",),
  252. )
  253. @pytest.mark.parametrize("version", [2, 4])
  254. @pytest.mark.parametrize("update", [False, True])
  255. def test_snapshot_comparison_rejects_noncurrent_writer(
  256. tmp_path: Path, version: int, update: bool,
  257. ) -> None:
  258. golden = '{"type":"session","version":2}\n'
  259. (tmp_path / "session.v2.jsonl").write_text(golden, encoding="utf-8")
  260. content = json.dumps({"type": "session", "version": version}) + "\n"
  261. with pytest.raises(AssertionError, match="expected current Session format v3"):
  262. SMOKE["compare_snapshot_files"](
  263. {f"session.v{version}.jsonl": content}, update, tmp_path, ("session.v2.jsonl",),
  264. )
  265. assert (tmp_path / "session.v2.jsonl").read_text(encoding="utf-8") == golden
  266. assert not (tmp_path / "session.v4.jsonl").exists()
  267. @pytest.mark.parametrize("version", [2, 3, 4])
  268. def test_persisted_session_requires_current_writer(version: int) -> None:
  269. content = json.dumps({"type": "session", "version": version}) + "\n"
  270. path = Path(f"session.v{version}.jsonl")
  271. if version == 3:
  272. assert SMOKE["assert_persisted_session_version"](path, content) == version
  273. else:
  274. with pytest.raises(AssertionError, match="expected current Session format v3"):
  275. SMOKE["assert_persisted_session_version"](path, content)
  276. def test_snapshot_generation_filename_must_match_header(tmp_path: Path) -> None:
  277. (tmp_path / "session.v1.jsonl").write_text(
  278. '{"type":"session","version":0}\n', encoding="utf-8",
  279. )
  280. with pytest.raises(AssertionError, match="filename declares Session format v1"):
  281. SMOKE["selected_snapshot_session_files"](tmp_path)