• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 gRPC authors.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Tests clean exit of server/client on Python Interpreter exit/sigint.
15
16The tests in this module spawn a subprocess for each test case, the
17test is considered successful if it doesn't hang/timeout.
18"""
19
20import atexit
21import os
22import signal
23import six
24import subprocess
25import sys
26import threading
27import datetime
28import time
29import unittest
30import logging
31
32from tests.unit import _exit_scenarios
33
34SCENARIO_FILE = os.path.abspath(
35    os.path.join(os.path.dirname(os.path.realpath(__file__)),
36                 '_exit_scenarios.py'))
37INTERPRETER = sys.executable
38BASE_COMMAND = [INTERPRETER, SCENARIO_FILE]
39BASE_SIGTERM_COMMAND = BASE_COMMAND + ['--wait_for_interrupt']
40
41INIT_TIME = datetime.timedelta(seconds=1)
42WAIT_CHECK_INTERVAL = datetime.timedelta(milliseconds=100)
43WAIT_CHECK_DEFAULT_TIMEOUT = datetime.timedelta(seconds=5)
44
45processes = []
46process_lock = threading.Lock()
47
48
49# Make sure we attempt to clean up any
50# processes we may have left running
51def cleanup_processes():
52    with process_lock:
53        for process in processes:
54            try:
55                process.kill()
56            except Exception:  # pylint: disable=broad-except
57                pass
58
59
60atexit.register(cleanup_processes)
61
62
63def _process_wait_with_timeout(process, timeout=WAIT_CHECK_DEFAULT_TIMEOUT):
64    """A funciton to mimic 3.3+ only timeout argument in process.wait."""
65    deadline = datetime.datetime.now() + timeout
66    while (process.poll() is None) and (datetime.datetime.now() < deadline):
67        time.sleep(WAIT_CHECK_INTERVAL.total_seconds())
68    if process.returncode is None:
69        raise RuntimeError('Process failed to exit within %s' % timeout)
70
71
72def interrupt_and_wait(process):
73    with process_lock:
74        processes.append(process)
75    time.sleep(INIT_TIME.total_seconds())
76    os.kill(process.pid, signal.SIGINT)
77    _process_wait_with_timeout(process)
78
79
80def wait(process):
81    with process_lock:
82        processes.append(process)
83    _process_wait_with_timeout(process)
84
85
86# TODO(lidiz) enable exit tests once the root cause found.
87@unittest.skip('https://github.com/grpc/grpc/issues/23982')
88@unittest.skip('https://github.com/grpc/grpc/issues/23028')
89class ExitTest(unittest.TestCase):
90
91    def test_unstarted_server(self):
92        process = subprocess.Popen(BASE_COMMAND +
93                                   [_exit_scenarios.UNSTARTED_SERVER],
94                                   stdout=sys.stdout,
95                                   stderr=sys.stderr)
96        wait(process)
97
98    def test_unstarted_server_terminate(self):
99        process = subprocess.Popen(BASE_SIGTERM_COMMAND +
100                                   [_exit_scenarios.UNSTARTED_SERVER],
101                                   stdout=sys.stdout)
102        interrupt_and_wait(process)
103
104    def test_running_server(self):
105        process = subprocess.Popen(BASE_COMMAND +
106                                   [_exit_scenarios.RUNNING_SERVER],
107                                   stdout=sys.stdout,
108                                   stderr=sys.stderr)
109        wait(process)
110
111    def test_running_server_terminate(self):
112        process = subprocess.Popen(BASE_SIGTERM_COMMAND +
113                                   [_exit_scenarios.RUNNING_SERVER],
114                                   stdout=sys.stdout,
115                                   stderr=sys.stderr)
116        interrupt_and_wait(process)
117
118    def test_poll_connectivity_no_server(self):
119        process = subprocess.Popen(
120            BASE_COMMAND + [_exit_scenarios.POLL_CONNECTIVITY_NO_SERVER],
121            stdout=sys.stdout,
122            stderr=sys.stderr)
123        wait(process)
124
125    def test_poll_connectivity_no_server_terminate(self):
126        process = subprocess.Popen(
127            BASE_SIGTERM_COMMAND +
128            [_exit_scenarios.POLL_CONNECTIVITY_NO_SERVER],
129            stdout=sys.stdout,
130            stderr=sys.stderr)
131        interrupt_and_wait(process)
132
133    def test_poll_connectivity(self):
134        process = subprocess.Popen(BASE_COMMAND +
135                                   [_exit_scenarios.POLL_CONNECTIVITY],
136                                   stdout=sys.stdout,
137                                   stderr=sys.stderr)
138        wait(process)
139
140    def test_poll_connectivity_terminate(self):
141        process = subprocess.Popen(BASE_SIGTERM_COMMAND +
142                                   [_exit_scenarios.POLL_CONNECTIVITY],
143                                   stdout=sys.stdout,
144                                   stderr=sys.stderr)
145        interrupt_and_wait(process)
146
147    @unittest.skipIf(os.name == 'nt',
148                     'os.kill does not have required permission on Windows')
149    def test_in_flight_unary_unary_call(self):
150        process = subprocess.Popen(BASE_COMMAND +
151                                   [_exit_scenarios.IN_FLIGHT_UNARY_UNARY_CALL],
152                                   stdout=sys.stdout,
153                                   stderr=sys.stderr)
154        interrupt_and_wait(process)
155
156    @unittest.skipIf(os.name == 'nt',
157                     'os.kill does not have required permission on Windows')
158    def test_in_flight_unary_stream_call(self):
159        process = subprocess.Popen(
160            BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_UNARY_STREAM_CALL],
161            stdout=sys.stdout,
162            stderr=sys.stderr)
163        interrupt_and_wait(process)
164
165    @unittest.skipIf(os.name == 'nt',
166                     'os.kill does not have required permission on Windows')
167    def test_in_flight_stream_unary_call(self):
168        process = subprocess.Popen(
169            BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_STREAM_UNARY_CALL],
170            stdout=sys.stdout,
171            stderr=sys.stderr)
172        interrupt_and_wait(process)
173
174    @unittest.skipIf(os.name == 'nt',
175                     'os.kill does not have required permission on Windows')
176    def test_in_flight_stream_stream_call(self):
177        process = subprocess.Popen(
178            BASE_COMMAND + [_exit_scenarios.IN_FLIGHT_STREAM_STREAM_CALL],
179            stdout=sys.stdout,
180            stderr=sys.stderr)
181        interrupt_and_wait(process)
182
183    @unittest.skipIf(os.name == 'nt',
184                     'os.kill does not have required permission on Windows')
185    def test_in_flight_partial_unary_stream_call(self):
186        process = subprocess.Popen(
187            BASE_COMMAND +
188            [_exit_scenarios.IN_FLIGHT_PARTIAL_UNARY_STREAM_CALL],
189            stdout=sys.stdout,
190            stderr=sys.stderr)
191        interrupt_and_wait(process)
192
193    @unittest.skipIf(os.name == 'nt',
194                     'os.kill does not have required permission on Windows')
195    def test_in_flight_partial_stream_unary_call(self):
196        process = subprocess.Popen(
197            BASE_COMMAND +
198            [_exit_scenarios.IN_FLIGHT_PARTIAL_STREAM_UNARY_CALL],
199            stdout=sys.stdout,
200            stderr=sys.stderr)
201        interrupt_and_wait(process)
202
203    @unittest.skipIf(os.name == 'nt',
204                     'os.kill does not have required permission on Windows')
205    def test_in_flight_partial_stream_stream_call(self):
206        process = subprocess.Popen(
207            BASE_COMMAND +
208            [_exit_scenarios.IN_FLIGHT_PARTIAL_STREAM_STREAM_CALL],
209            stdout=sys.stdout,
210            stderr=sys.stderr)
211        interrupt_and_wait(process)
212
213
214if __name__ == '__main__':
215    logging.basicConfig(level=logging.DEBUG)
216    unittest.main(verbosity=2)
217