1*2cb6d6f1SJim Jagielski################################################################ 2*2cb6d6f1SJim Jagielski# 3*2cb6d6f1SJim Jagielski# Licensed to the Apache Software Foundation (ASF) under one 4*2cb6d6f1SJim Jagielski# or more contributor license agreements. See the NOTICE file 5*2cb6d6f1SJim Jagielski# distributed with this work for additional information 6*2cb6d6f1SJim Jagielski# regarding copyright ownership. The ASF licenses this file 7*2cb6d6f1SJim Jagielski# to you under the Apache License, Version 2.0 (the 8*2cb6d6f1SJim Jagielski# "License"); you may not use this file except in compliance 9*2cb6d6f1SJim Jagielski# with the License. You may obtain a copy of the License at 10*2cb6d6f1SJim Jagielski# 11*2cb6d6f1SJim Jagielski# http://www.apache.org/licenses/LICENSE-2.0 12*2cb6d6f1SJim Jagielski# 13*2cb6d6f1SJim Jagielski# Unless required by applicable law or agreed to in writing, 14*2cb6d6f1SJim Jagielski# software distributed under the License is distributed on an 15*2cb6d6f1SJim Jagielski# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16*2cb6d6f1SJim Jagielski# KIND, either express or implied. See the License for the 17*2cb6d6f1SJim Jagielski# specific language governing permissions and limitations 18*2cb6d6f1SJim Jagielski# under the License. 19*2cb6d6f1SJim Jagielski# 20*2cb6d6f1SJim Jagielski################################################################ 21*2cb6d6f1SJim Jagielski 22*2cb6d6f1SJim Jagielski"""client.py against the fake worker: normal replies, error replies, and every way a worker can go wrong.""" 23*2cb6d6f1SJim Jagielskiimport os 24*2cb6d6f1SJim Jagielskiimport time 25*2cb6d6f1SJim Jagielski 26*2cb6d6f1SJim Jagielskiimport pytest 27*2cb6d6f1SJim Jagielski 28*2cb6d6f1SJim Jagielskifrom aootest import client 29*2cb6d6f1SJim Jagielskifrom aootest.client import ProtocolError, WorkerCrash, WorkerError, WorkerHang 30*2cb6d6f1SJim Jagielski 31*2cb6d6f1SJim Jagielskipytestmark = pytest.mark.selftest 32*2cb6d6f1SJim JagielskiPOSIX = os.name == "posix" 33*2cb6d6f1SJim Jagielski 34*2cb6d6f1SJim Jagielski 35*2cb6d6f1SJim Jagielskidef gone(pid, wait=5.0): 36*2cb6d6f1SJim Jagielski end = time.time() + wait 37*2cb6d6f1SJim Jagielski while time.time() < end: 38*2cb6d6f1SJim Jagielski try: 39*2cb6d6f1SJim Jagielski os.kill(pid, 0) 40*2cb6d6f1SJim Jagielski except ProcessLookupError: 41*2cb6d6f1SJim Jagielski return True 42*2cb6d6f1SJim Jagielski time.sleep(0.05) 43*2cb6d6f1SJim Jagielski return False 44*2cb6d6f1SJim Jagielski 45*2cb6d6f1SJim Jagielski 46*2cb6d6f1SJim Jagielskidef test_round_trip(start_worker): 47*2cb6d6f1SJim Jagielski w = start_worker() 48*2cb6d6f1SJim Jagielski assert w.hello["protocol"] == 1 and w.hello["pid"] == w.pid 49*2cb6d6f1SJim Jagielski assert w.call("echo", {"x": 1, "y": [1, 2]}) == {"x": 1, "y": [1, 2]} 50*2cb6d6f1SJim Jagielski assert w.call("add", {"a": 2, "b": 3}) == {"sum": 5} 51*2cb6d6f1SJim Jagielski assert w.call("ping") == {"pong": True} 52*2cb6d6f1SJim Jagielski 53*2cb6d6f1SJim Jagielski 54*2cb6d6f1SJim Jagielskidef test_printing_in_the_worker_does_not_corrupt_the_protocol(start_worker): 55*2cb6d6f1SJim Jagielski w = start_worker() 56*2cb6d6f1SJim Jagielski assert w.call("chatter") == {"printed": True} 57*2cb6d6f1SJim Jagielski assert w.call("echo", {"after": True}) == {"after": True} 58*2cb6d6f1SJim Jagielski 59*2cb6d6f1SJim Jagielski 60*2cb6d6f1SJim Jagielskidef test_error_replies_keep_the_worker_usable(start_worker): 61*2cb6d6f1SJim Jagielski w = start_worker() 62*2cb6d6f1SJim Jagielski with pytest.raises(WorkerError) as e: 63*2cb6d6f1SJim Jagielski w.call("fail", {"kind": "uno", "message": "the document would not load"}) 64*2cb6d6f1SJim Jagielski assert (e.value.kind, e.value.message) == ("uno", "the document would not load") 65*2cb6d6f1SJim Jagielski with pytest.raises(WorkerError) as e: 66*2cb6d6f1SJim Jagielski w.call("boom") 67*2cb6d6f1SJim Jagielski assert e.value.kind == "python" and e.value.exc == "ZeroDivisionError" and "ZeroDivisionError" in e.value.traceback 68*2cb6d6f1SJim Jagielski with pytest.raises(WorkerError) as e: 69*2cb6d6f1SJim Jagielski w.call("no-such-op") 70*2cb6d6f1SJim Jagielski assert e.value.kind == "bad_args" 71*2cb6d6f1SJim Jagielski with pytest.raises(WorkerError) as e: 72*2cb6d6f1SJim Jagielski w.call("add", {"a": 1}) 73*2cb6d6f1SJim Jagielski assert e.value.kind == "bad_args" and "missing" in e.value.message 74*2cb6d6f1SJim Jagielski assert w.call("add", {"a": 1, "b": 1}) == {"sum": 2} 75*2cb6d6f1SJim Jagielski 76*2cb6d6f1SJim Jagielski 77*2cb6d6f1SJim Jagielskidef test_close_ends_the_worker(start_worker): 78*2cb6d6f1SJim Jagielski w = start_worker() 79*2cb6d6f1SJim Jagielski p = w._proc 80*2cb6d6f1SJim Jagielski w.close() 81*2cb6d6f1SJim Jagielski assert p.poll() is not None 82*2cb6d6f1SJim Jagielski 83*2cb6d6f1SJim Jagielski 84*2cb6d6f1SJim Jagielski# ---- HANG ---------------------------------------------------------------------------------------------------------- 85*2cb6d6f1SJim Jagielski 86*2cb6d6f1SJim Jagielskidef test_hang_is_reported_and_the_worker_killed(start_worker): 87*2cb6d6f1SJim Jagielski w = start_worker("hang") 88*2cb6d6f1SJim Jagielski t0 = time.time() 89*2cb6d6f1SJim Jagielski with pytest.raises(WorkerHang, match="no answer within 1s"): 90*2cb6d6f1SJim Jagielski w.call("echo", timeout=1) 91*2cb6d6f1SJim Jagielski assert time.time() - t0 < 6 92*2cb6d6f1SJim Jagielski assert w._proc.poll() is not None 93*2cb6d6f1SJim Jagielski with pytest.raises(WorkerHang): # a finished client stays finished 94*2cb6d6f1SJim Jagielski w.call("echo") 95*2cb6d6f1SJim Jagielski 96*2cb6d6f1SJim Jagielski 97*2cb6d6f1SJim Jagielski@pytest.mark.skipif(not POSIX, reason="child process ids are only checked on POSIX") 98*2cb6d6f1SJim Jagielskidef test_hang_kills_the_workers_children_too(start_worker): 99*2cb6d6f1SJim Jagielski w = start_worker() 100*2cb6d6f1SJim Jagielski child = w.call("spawn_child")["pid"] 101*2cb6d6f1SJim Jagielski os.kill(child, 0) 102*2cb6d6f1SJim Jagielski with pytest.raises(WorkerHang): 103*2cb6d6f1SJim Jagielski w.call("sleep", {"seconds": 60}, timeout=1) 104*2cb6d6f1SJim Jagielski assert gone(child) 105*2cb6d6f1SJim Jagielski 106*2cb6d6f1SJim Jagielski 107*2cb6d6f1SJim Jagielskidef test_worker_that_never_says_hello(start_worker): 108*2cb6d6f1SJim Jagielski with pytest.raises(WorkerHang, match="hello"): 109*2cb6d6f1SJim Jagielski start_worker("silent", hello_timeout=1) 110*2cb6d6f1SJim Jagielski 111*2cb6d6f1SJim Jagielski 112*2cb6d6f1SJim Jagielski# ---- CRASH --------------------------------------------------------------------------------------------------------- 113*2cb6d6f1SJim Jagielski 114*2cb6d6f1SJim Jagielskidef test_exit_139(start_worker): 115*2cb6d6f1SJim Jagielski w = start_worker("crash139") 116*2cb6d6f1SJim Jagielski with pytest.raises(WorkerCrash) as e: 117*2cb6d6f1SJim Jagielski w.call("echo") 118*2cb6d6f1SJim Jagielski assert e.value.returncode == 139 and "128 + SIGSEGV" in str(e.value) 119*2cb6d6f1SJim Jagielski 120*2cb6d6f1SJim Jagielski 121*2cb6d6f1SJim Jagielski@pytest.mark.skipif(not POSIX, reason="signals") 122*2cb6d6f1SJim Jagielskidef test_killed_by_a_signal(start_worker): 123*2cb6d6f1SJim Jagielski w = start_worker("sigkill") 124*2cb6d6f1SJim Jagielski with pytest.raises(WorkerCrash) as e: 125*2cb6d6f1SJim Jagielski w.call("echo") 126*2cb6d6f1SJim Jagielski assert e.value.returncode == -9 and "killed by SIGKILL" in str(e.value) 127*2cb6d6f1SJim Jagielski 128*2cb6d6f1SJim Jagielski 129*2cb6d6f1SJim Jagielskidef test_stderr_tail_is_kept(start_worker): 130*2cb6d6f1SJim Jagielski w = start_worker("stderr-then-crash") 131*2cb6d6f1SJim Jagielski with pytest.raises(WorkerCrash) as e: 132*2cb6d6f1SJim Jagielski w.call("echo") 133*2cb6d6f1SJim Jagielski assert e.value.returncode == 7 and "the office bridge fell over" in str(e.value) 134*2cb6d6f1SJim Jagielski 135*2cb6d6f1SJim Jagielski 136*2cb6d6f1SJim Jagielskidef test_office_dead_reply_is_a_crash_and_the_worker_is_finished(start_worker): 137*2cb6d6f1SJim Jagielski w = start_worker() 138*2cb6d6f1SJim Jagielski with pytest.raises(client.OfficeDead, match="ServiceManager") as e: 139*2cb6d6f1SJim Jagielski w.call("office-gone") 140*2cb6d6f1SJim Jagielski assert isinstance(e.value, WorkerCrash) 141*2cb6d6f1SJim Jagielski with pytest.raises(client.OfficeDead): 142*2cb6d6f1SJim Jagielski w.call("echo") 143*2cb6d6f1SJim Jagielski assert w._proc.poll() is not None 144*2cb6d6f1SJim Jagielski 145*2cb6d6f1SJim Jagielski 146*2cb6d6f1SJim Jagielskidef test_death_mid_call_in_the_good_worker(start_worker): 147*2cb6d6f1SJim Jagielski w = start_worker() 148*2cb6d6f1SJim Jagielski with pytest.raises(WorkerCrash) as e: 149*2cb6d6f1SJim Jagielski w.call("die139") 150*2cb6d6f1SJim Jagielski assert e.value.returncode == 139 151*2cb6d6f1SJim Jagielski with pytest.raises(WorkerCrash): 152*2cb6d6f1SJim Jagielski w.call("echo") 153*2cb6d6f1SJim Jagielski 154*2cb6d6f1SJim Jagielski 155*2cb6d6f1SJim Jagielskidef test_exit_before_hello(start_worker): 156*2cb6d6f1SJim Jagielski with pytest.raises(WorkerCrash) as e: 157*2cb6d6f1SJim Jagielski start_worker("exit-before-hello") 158*2cb6d6f1SJim Jagielski assert e.value.returncode == 3 159*2cb6d6f1SJim Jagielski 160*2cb6d6f1SJim Jagielski 161*2cb6d6f1SJim Jagielski# ---- garbage ------------------------------------------------------------------------------------------------------- 162*2cb6d6f1SJim Jagielski 163*2cb6d6f1SJim Jagielski@pytest.mark.parametrize("mode", ["garbage-reply", "not-an-object", "wrong-id", "binary-reply"]) 164*2cb6d6f1SJim Jagielskidef test_bad_replies_are_protocol_errors_and_the_worker_is_killed(start_worker, mode): 165*2cb6d6f1SJim Jagielski w = start_worker(mode) 166*2cb6d6f1SJim Jagielski with pytest.raises(ProtocolError): 167*2cb6d6f1SJim Jagielski w.call("echo", timeout=5) 168*2cb6d6f1SJim Jagielski assert w._proc.poll() is not None 169*2cb6d6f1SJim Jagielski 170*2cb6d6f1SJim Jagielski 171*2cb6d6f1SJim Jagielski@pytest.mark.parametrize("mode", ["garbage-hello", "wrong-protocol"]) 172*2cb6d6f1SJim Jagielskidef test_bad_hello(start_worker, mode): 173*2cb6d6f1SJim Jagielski with pytest.raises(ProtocolError): 174*2cb6d6f1SJim Jagielski start_worker(mode, hello_timeout=5) 175*2cb6d6f1SJim Jagielski 176*2cb6d6f1SJim Jagielski 177*2cb6d6f1SJim Jagielski@pytest.mark.parametrize("rc, text", [(0, "exit 0"), (1, "exit 1"), (7, "exit 7"), (139, "exit 139, 128 + SIGSEGV"), (-9, "killed by SIGKILL"), 178*2cb6d6f1SJim Jagielski (-11, "killed by SIGSEGV"), (None, "no exit status")]) 179*2cb6d6f1SJim Jagielskidef test_describe_exit(rc, text): 180*2cb6d6f1SJim Jagielski assert client.describe_exit(rc) == text 181