1# Copyright 2018 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 shutdown of server on various interpreter exit conditions.
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 subprocess
23import sys
24import threading
25import unittest
26import logging
27
28from tests.unit import _server_shutdown_scenarios
29
30SCENARIO_FILE = os.path.abspath(
31    os.path.join(os.path.dirname(os.path.realpath(__file__)),
32                 '_server_shutdown_scenarios.py'))
33INTERPRETER = sys.executable
34BASE_COMMAND = [INTERPRETER, SCENARIO_FILE]
35
36processes = []
37process_lock = threading.Lock()
38
39
40# Make sure we attempt to clean up any
41# processes we may have left running
42def cleanup_processes():
43    with process_lock:
44        for process in processes:
45            try:
46                process.kill()
47            except Exception:  # pylint: disable=broad-except
48                pass
49
50
51atexit.register(cleanup_processes)
52
53
54def wait(process):
55    with process_lock:
56        processes.append(process)
57    process.wait()
58
59
60class ServerShutdown(unittest.TestCase):
61
62    # Currently we shut down a server (if possible) after the Python server
63    # instance is garbage collected. This behavior may change in the future.
64    def test_deallocated_server_stops(self):
65        process = subprocess.Popen(
66            BASE_COMMAND + [_server_shutdown_scenarios.SERVER_DEALLOCATED],
67            stdout=sys.stdout,
68            stderr=sys.stderr)
69        wait(process)
70
71    def test_server_exception_exits(self):
72        process = subprocess.Popen(
73            BASE_COMMAND + [_server_shutdown_scenarios.SERVER_RAISES_EXCEPTION],
74            stdout=sys.stdout,
75            stderr=sys.stderr)
76        wait(process)
77
78    @unittest.skipIf(os.name == 'nt', 'fork not supported on windows')
79    def test_server_fork_can_exit(self):
80        process = subprocess.Popen(
81            BASE_COMMAND + [_server_shutdown_scenarios.SERVER_FORK_CAN_EXIT],
82            stdout=sys.stdout,
83            stderr=sys.stderr)
84        wait(process)
85
86
87if __name__ == '__main__':
88    logging.basicConfig()
89    unittest.main(verbosity=2)
90