test_repo_local_process_contract.mojo (11865B)
1 from std.testing import TestSuite, assert_equal, assert_true 2 from std.collections import List 3 from safe_tempdir import SafeTempDir 4 5 from json import Value 6 from fixture_assertions import load_scenario_request_json 7 from parent_lifecycle import ( 8 IO_DEADLINE_EXPIRED, 9 IO_FAULT_EINTR_UNBOUNDED, 10 POLLIN, 11 close_fd, 12 make_pipe, 13 now_ms, 14 read_fd, 15 write_fd_chunk, 16 write_raw, 17 ) 18 from stdio_process_helper import ( 19 HYF_PATHS_PROFILE_ENV, 20 HYF_PATHS_REPO_LOCAL_ROOT_ENV, 21 ScopedEnvVar, 22 drain_ready, 23 run_stdio_binary_with_deadline, 24 run_stdio_entrypoint, 25 run_stdio_entrypoint_with_deadline, 26 ) 27 28 29 def _assert_under_repo_local_root(repo_local_root: String, path: String) raises: 30 assert_true(path.startswith(repo_local_root + "/")) 31 32 33 def _assert_runtime_status_path_under_repo_local_root( 34 runtime_status: Value, key: String, repo_local_root: String 35 ) raises: 36 _assert_under_repo_local_root( 37 repo_local_root, 38 runtime_status["paths"][key].string_value(), 39 ) 40 41 42 def test_src_main_consumes_repo_local_env_without_outer_wrapper() raises: 43 with SafeTempDir() as repo_local_root: 44 with ScopedEnvVar(HYF_PATHS_PROFILE_ENV, "repo_local"): 45 with ScopedEnvVar(HYF_PATHS_REPO_LOCAL_ROOT_ENV, repo_local_root): 46 var response = run_stdio_entrypoint( 47 "src/main.mojo", 48 load_scenario_request_json("scenarios/status_ok.json"), 49 ) 50 51 var runtime_status = response["output"]["runtime"].clone() 52 assert_equal( 53 runtime_status["paths_profile"].string_value(), "repo_local" 54 ) 55 assert_equal( 56 runtime_status["repo_local_base_root"].string_value(), 57 repo_local_root, 58 ) 59 60 _assert_runtime_status_path_under_repo_local_root( 61 runtime_status, "config_dir", repo_local_root 62 ) 63 _assert_runtime_status_path_under_repo_local_root( 64 runtime_status, "config_path", repo_local_root 65 ) 66 _assert_runtime_status_path_under_repo_local_root( 67 runtime_status, "data_dir", repo_local_root 68 ) 69 _assert_runtime_status_path_under_repo_local_root( 70 runtime_status, "cache_dir", repo_local_root 71 ) 72 _assert_runtime_status_path_under_repo_local_root( 73 runtime_status, "logs_dir", repo_local_root 74 ) 75 _assert_runtime_status_path_under_repo_local_root( 76 runtime_status, "diagnostics_dir", repo_local_root 77 ) 78 _assert_runtime_status_path_under_repo_local_root( 79 runtime_status, "run_dir", repo_local_root 80 ) 81 _assert_runtime_status_path_under_repo_local_root( 82 runtime_status, "secrets_dir", repo_local_root 83 ) 84 _assert_runtime_status_path_under_repo_local_root( 85 runtime_status, "identity_path", repo_local_root 86 ) 87 _assert_under_repo_local_root( 88 repo_local_root, 89 runtime_status["config"]["artifact_path"].string_value(), 90 ) 91 assert_equal( 92 runtime_status["config"][ 93 "artifact_path_source" 94 ].string_value(), 95 "canonical_runtime_path", 96 ) 97 assert_equal( 98 runtime_status["config"]["artifact_present"].bool_value(), 99 False, 100 ) 101 assert_equal( 102 runtime_status["config"]["load_state"].string_value(), 103 "not_found", 104 ) 105 106 107 def test_run_stdio_entrypoint_reaps_stalled_child_under_deadline() raises: 108 # FX06/FX08: the parent deadline (not the child alarm) must terminate and 109 # reap the owned child, and the raised error must carry the reap result. 110 var message = "" 111 try: 112 _ = run_stdio_entrypoint_with_deadline( 113 "tests/stdio_stall_entrypoint.mojo", "{}", "", "", 3000 114 ) 115 except e: 116 message = String(e) 117 assert_true(message.find("stdio-entrypoint") >= 0) 118 assert_true(message.find("signal=") >= 0 or message.find("exited=") >= 0) 119 assert_true(message.find("cleanup_error=") >= 0) 120 121 122 def test_run_stdio_entrypoint_rejects_unread_request() raises: 123 # PC03: an incomplete request write is a cause-specific failure even though 124 # the child emits valid JSON and exits zero, and the failure still exposes 125 # the owned child's cleanup truth. 126 var request = String("") 127 for _ in range(150000): 128 request += "r" 129 var message = "" 130 try: 131 _ = run_stdio_entrypoint_with_deadline( 132 "tests/stdio_no_read_entrypoint.mojo", request, "", "", 10000 133 ) 134 except e: 135 message = String(e) 136 assert_true(message.find("write_") >= 0) 137 assert_true(message.find("cleanup_error=") >= 0) 138 139 140 def test_run_stdio_entrypoint_rejects_late_success() raises: 141 # PC03: the ordinary compile/run helper keeps its declared total budget, so 142 # a late exit is rejected inside it and the bounded cleanup grace is not 143 # extra successful work. The phase-isolated proof of the late-exit child is 144 # test_run_stdio_binary_rejects_late_exit_after_wait_phase below; this lane 145 # only characterizes the one-shot compile/run helper's own budget. 146 var start = now_ms() 147 var message = "" 148 try: 149 _ = run_stdio_entrypoint_with_deadline( 150 "tests/stdio_late_exit_entrypoint.mojo", "{}", "", "", 1200 151 ) 152 except e: 153 message = String(e) 154 var elapsed = now_ms() - start 155 assert_true(message.find("stdio-entrypoint") >= 0) 156 assert_true( 157 message.find("timeout") >= 0 158 or message.find("read_deadline_expired") >= 0 159 ) 160 assert_true(message.find("cleanup_error=") >= 0) 161 assert_true(message.find("child_failed") < 0) 162 assert_true(elapsed >= 1150) 163 assert_true(elapsed <= 3700) 164 165 166 comptime LATE_EXIT_SH_SCRIPT = String( 167 "printf '{\"ok\":true}\\n'; exec 1>&- 2>&-; exec sleep 3" 168 ) 169 170 171 def test_run_stdio_binary_rejects_late_exit_after_wait_phase() raises: 172 # RA03 phase isolation: an already-launched control child (compilation is 173 # entirely outside this assertion) must reach valid output, closed output 174 # and the wait phase *before* its late exit is rejected. A generic 175 # compile/startup/read timeout cannot satisfy these phase assertions. 176 var start = now_ms() 177 var message = "" 178 try: 179 _ = run_stdio_binary_with_deadline( 180 "/bin/sh", "{}", "-c", LATE_EXIT_SH_SCRIPT, 1200 181 ) 182 except e: 183 message = String(e) 184 var elapsed = now_ms() - start 185 assert_true(message.find("stdio-entrypoint") >= 0) 186 assert_true(message.find("phase=wait") >= 0) 187 assert_true(message.find("request_sent=true") >= 0) 188 assert_true(message.find("stdout_eof=true") >= 0) 189 assert_true(message.find("stderr_eof=true") >= 0) 190 assert_true(message.find("output_valid=true") >= 0) 191 assert_true(message.find("output_bytes=") >= 0) 192 assert_true(message.find("reason=timeout") >= 0) 193 assert_true(message.find("cleanup_error=") >= 0) 194 assert_true(message.find("child_failed") < 0) 195 # Work time stays inside the declared budget; only the bounded cleanup 196 # allowance is added afterwards. 197 assert_true(elapsed >= 1150) 198 assert_true(elapsed <= 1200 + 2500) 199 200 201 def test_run_stdio_binary_reports_success_within_budget() raises: 202 # RA03 positive control for the same launch seam: a child that emits valid 203 # output and exits inside the budget is accepted. 204 var response = run_stdio_binary_with_deadline( 205 "/bin/sh", "{}", "-c", "printf '{\"ok\":true}\\n'", 5000 206 ) 207 assert_true(response["ok"].bool_value()) 208 209 210 def test_run_stdio_entrypoint_classifies_loader_failure() raises: 211 var message = "" 212 try: 213 _ = run_stdio_entrypoint_with_deadline( 214 "tests/does_not_exist_entrypoint.mojo", "{}", "", "", 30000 215 ) 216 except e: 217 message = String(e) 218 assert_true(message.find("child_failed") >= 0) 219 assert_true(message.find("timeout") < 0) 220 221 222 def test_run_stdio_entrypoint_drains_stdout_concurrently() raises: 223 # LC04: a child that floods stdout before reading stdin must not deadlock 224 # the parent's large request write; a correct interleaving succeeds. 225 var request = String("") 226 for _ in range(150000): 227 request += "r" 228 var response = Value(None) 229 var message = "" 230 var failed = False 231 try: 232 response = run_stdio_entrypoint_with_deadline( 233 "tests/stdio_stdout_flood_entrypoint.mojo", 234 request, 235 "", 236 "", 237 30000, 238 ) 239 except e: 240 failed = True 241 message = String(e) 242 assert_true(not failed) 243 assert_equal(message, "") 244 assert_true(response["ok"].bool_value()) 245 246 247 def test_diagnostics_overflow_is_bounded_with_cause() raises: 248 # LC04: stderr diagnostics are capped and an overflow is a distinct cause. 249 var pipe = make_pipe() 250 var chunk = String("") 251 for _ in range(4096): 252 chunk += "e" 253 var out = List[UInt8]() 254 var overflow_reason = "" 255 for _ in range(20): 256 _ = write_raw(pipe.write_fd, chunk) 257 var d = drain_ready(pipe.read_fd, out, 65536, POLLIN, 2000) 258 if d.reason != "": 259 overflow_reason = d.reason 260 break 261 close_fd(pipe.read_fd) 262 close_fd(pipe.write_fd) 263 assert_equal(overflow_reason, "stream_overflow") 264 assert_true(len(out) <= 65536) 265 266 267 def test_diagnostics_read_failure_is_distinct_from_eof() raises: 268 # LC04: an unavailable descriptor is a read failure, not a clean EOF. 269 var out = List[UInt8]() 270 var d = drain_ready(-1, out, 16, POLLIN, 2000) 271 assert_true(d.eof) 272 assert_equal(d.reason, "read_error") 273 274 275 def test_diagnostics_retry_is_bounded_by_deadline() raises: 276 # RA03: an EINTR-style retry inside the stdio drain path must return to the 277 # deadline owner instead of retrying without bound. The retry count is a 278 # bounded test seam; no host signal state is involved. 279 var pipe = make_pipe() 280 var buf = InlineArray[Byte, 8](fill=0) 281 var start = now_ms() 282 var rc = read_fd( 283 pipe.read_fd, buf.unsafe_ptr(), 8, 60, IO_FAULT_EINTR_UNBOUNDED 284 ) 285 var elapsed = now_ms() - start 286 close_fd(pipe.read_fd) 287 close_fd(pipe.write_fd) 288 assert_equal(rc, IO_DEADLINE_EXPIRED) 289 assert_true(elapsed >= 50) 290 291 292 def test_diagnostics_write_retry_is_bounded_by_deadline() raises: 293 # RA03: the same bound holds for the stdio request-write retry loop. 294 var pipe = make_pipe() 295 var payload = String('{"ok":true}') 296 var start = now_ms() 297 var cw = write_fd_chunk( 298 pipe.write_fd, payload, 0, 60, IO_FAULT_EINTR_UNBOUNDED 299 ) 300 var elapsed = now_ms() - start 301 close_fd(pipe.read_fd) 302 close_fd(pipe.write_fd) 303 assert_equal(cw.reason, "write_deadline_expired") 304 assert_true(elapsed >= 50) 305 306 307 def test_run_stdio_entrypoint_drains_stderr_concurrently_and_bounds_it() raises: 308 # LC04: a child that floods stderr before producing output must not 309 # deadlock the parent, and the overflow is reported with its own cause. 310 var message = "" 311 try: 312 _ = run_stdio_entrypoint_with_deadline( 313 "tests/stdio_stderr_flood_entrypoint.mojo", "{}", "", "", 30000 314 ) 315 except e: 316 message = String(e) 317 assert_true(message.find("stderr_stream_overflow") >= 0) 318 assert_true(message.find("timeout") < 0) 319 320 321 def main() raises: 322 TestSuite.discover_tests[__functions_in_module()]().run()