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