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