diff options
author | Andrei Belov <defan@nginx.com> | 2020-11-19 21:19:57 +0300 |
---|---|---|
committer | Andrei Belov <defan@nginx.com> | 2020-11-19 21:19:57 +0300 |
commit | 7f9079a3cd4cdb6ac3fea53f10bd34fe8b82fe9c (patch) | |
tree | c79dc48a3260156f3f824ecd299e5a4934d749c5 /test/unit | |
parent | 646d047e5d12515ceac02279b373601ce0752982 (diff) | |
parent | 806a9b2515c60b12a68cd97af04f7fa5cb4dffed (diff) | |
download | unit-1.21.0-1.tar.gz unit-1.21.0-1.tar.bz2 |
Merged with the default branch.1.21.0-1
Diffstat (limited to '')
-rw-r--r-- | test/unit/applications/lang/go.py | 16 | ||||
-rw-r--r-- | test/unit/applications/lang/java.py | 29 | ||||
-rw-r--r-- | test/unit/applications/lang/node.py | 13 | ||||
-rw-r--r-- | test/unit/applications/lang/perl.py | 6 | ||||
-rw-r--r-- | test/unit/applications/lang/php.py | 18 | ||||
-rw-r--r-- | test/unit/applications/lang/python.py | 25 | ||||
-rw-r--r-- | test/unit/applications/lang/ruby.py | 6 | ||||
-rw-r--r-- | test/unit/applications/proto.py | 11 | ||||
-rw-r--r-- | test/unit/applications/tls.py | 14 | ||||
-rw-r--r-- | test/unit/check/go.py | 3 | ||||
-rw-r--r-- | test/unit/control.py | 3 | ||||
-rw-r--r-- | test/unit/feature/isolation.py | 134 | ||||
-rw-r--r-- | test/unit/http.py | 28 | ||||
-rw-r--r-- | test/unit/main.py | 179 |
14 files changed, 207 insertions, 278 deletions
diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py index 7715bd6c..866dec47 100644 --- a/test/unit/applications/lang/go.py +++ b/test/unit/applications/lang/go.py @@ -7,8 +7,8 @@ from unit.applications.proto import TestApplicationProto class TestApplicationGo(TestApplicationProto): def prepare_env(self, script, name, static=False): - if not os.path.exists(self.temp_dir + '/go'): - os.mkdir(self.temp_dir + '/go') + if not os.path.exists(option.temp_dir + '/go'): + os.mkdir(option.temp_dir + '/go') env = os.environ.copy() env['GOPATH'] = option.current_dir + '/build/go' @@ -22,7 +22,7 @@ class TestApplicationGo(TestApplicationProto): '-ldflags', '-extldflags "-static"', '-o', - self.temp_dir + '/go/' + name, + option.temp_dir + '/go/' + name, option.test_dir + '/go/' + script + '/' + name + '.go', ] else: @@ -30,14 +30,20 @@ class TestApplicationGo(TestApplicationProto): 'go', 'build', '-o', - self.temp_dir + '/go/' + name, + option.temp_dir + '/go/' + name, option.test_dir + '/go/' + script + '/' + name + '.go', ] + if option.detailed: + print("\n$ GOPATH=" + env['GOPATH'] + " " + " ".join(args)) + try: process = subprocess.Popen(args, env=env) process.communicate() + except KeyboardInterrupt: + raise + except: return None @@ -47,7 +53,7 @@ class TestApplicationGo(TestApplicationProto): static_build = False wdir = option.test_dir + "/go/" + script - executable = self.temp_dir + "/go/" + name + executable = option.temp_dir + "/go/" + name if 'isolation' in kwargs and 'rootfs' in kwargs['isolation']: wdir = "/go/" diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index 01cbfa0b..0ff85187 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -9,8 +9,10 @@ from unit.applications.proto import TestApplicationProto class TestApplicationJava(TestApplicationProto): - def load(self, script, name='app', **kwargs): - app_path = self.temp_dir + '/java' + application_type = "java" + + def prepare_env(self, script): + app_path = option.temp_dir + '/java' web_inf_path = app_path + '/WEB-INF/' classes_path = web_inf_path + 'classes/' script_path = option.test_dir + '/java/' + script + '/' @@ -50,7 +52,7 @@ class TestApplicationJava(TestApplicationProto): os.makedirs(classes_path) classpath = ( - option.current_dir + '/build/tomcat-servlet-api-9.0.13.jar' + option.current_dir + '/build/tomcat-servlet-api-9.0.39.jar' ) ws_jars = glob.glob( @@ -62,18 +64,28 @@ class TestApplicationJava(TestApplicationProto): javac = [ 'javac', + '-target', '8', '-source', '8', '-nowarn', '-encoding', 'utf-8', '-d', classes_path, '-classpath', classpath + ':' + ws_jars[0], ] javac.extend(src) + if option.detailed: + print("\n$ " + " ".join(javac)) + try: process = subprocess.Popen(javac, stderr=subprocess.STDOUT) process.communicate() + except KeyboardInterrupt: + raise + except: - pytest.fail('Cann\'t run javac process.') + pytest.fail('Can\'t run javac process.') + + def load(self, script, **kwargs): + self.prepare_env(script) self._load_conf( { @@ -81,10 +93,13 @@ class TestApplicationJava(TestApplicationProto): "applications": { script: { "unit_jars": option.current_dir + '/build', - "type": 'java', + "type": self.get_application_type(), "processes": {"spare": 0}, - "working_directory": script_path, - "webapp": app_path, + "working_directory": option.test_dir + + '/java/' + + script + + '/', + "webapp": option.temp_dir + '/java', } }, }, diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py index 877fc461..98fd9ffc 100644 --- a/test/unit/applications/lang/node.py +++ b/test/unit/applications/lang/node.py @@ -7,21 +7,24 @@ from unit.applications.proto import TestApplicationProto class TestApplicationNode(TestApplicationProto): - def load(self, script, name='app.js', **kwargs): + def prepare_env(self, script): # copy application shutil.copytree( - option.test_dir + '/node/' + script, self.temp_dir + '/node' + option.test_dir + '/node/' + script, option.temp_dir + '/node' ) # copy modules shutil.copytree( option.current_dir + '/node/node_modules', - self.temp_dir + '/node/node_modules', + option.temp_dir + '/node/node_modules', ) - public_dir(self.temp_dir + '/node') + public_dir(option.temp_dir + '/node') + + def load(self, script, name='app.js', **kwargs): + self.prepare_env(script) self._load_conf( { @@ -32,7 +35,7 @@ class TestApplicationNode(TestApplicationProto): script: { "type": "external", "processes": {"spare": 0}, - "working_directory": self.temp_dir + '/node', + "working_directory": option.temp_dir + '/node', "executable": name, } }, diff --git a/test/unit/applications/lang/perl.py b/test/unit/applications/lang/perl.py index a27c7649..9dc24ace 100644 --- a/test/unit/applications/lang/perl.py +++ b/test/unit/applications/lang/perl.py @@ -7,17 +7,13 @@ class TestApplicationPerl(TestApplicationProto): def load(self, script, name='psgi.pl', **kwargs): script_path = option.test_dir + '/perl/' + script - appication_type = self.get_appication_type() - - if appication_type is None: - appication_type = self.application_type self._load_conf( { "listeners": {"*:7080": {"pass": "applications/" + script}}, "applications": { script: { - "type": appication_type, + "type": self.get_application_type(), "processes": {"spare": 0}, "working_directory": script_path, "script": script_path + '/' + name, diff --git a/test/unit/applications/lang/php.py b/test/unit/applications/lang/php.py index 2d50df2e..3dbb32f5 100644 --- a/test/unit/applications/lang/php.py +++ b/test/unit/applications/lang/php.py @@ -1,4 +1,7 @@ from conftest import option +import os +import shutil + from unit.applications.proto import TestApplicationProto @@ -7,17 +10,24 @@ class TestApplicationPHP(TestApplicationProto): def load(self, script, index='index.php', **kwargs): script_path = option.test_dir + '/php/' + script - appication_type = self.get_appication_type() - if appication_type is None: - appication_type = self.application_type + if kwargs.get('isolation') and kwargs['isolation'].get('rootfs'): + rootfs = kwargs['isolation']['rootfs'] + + if not os.path.exists(rootfs + '/app/php/'): + os.makedirs(rootfs + '/app/php/') + + if not os.path.exists(rootfs + '/app/php/' + script): + shutil.copytree(script_path, rootfs + '/app/php/' + script) + + script_path = '/app/php/' + script self._load_conf( { "listeners": {"*:7080": {"pass": "applications/" + script}}, "applications": { script: { - "type": appication_type, + "type": self.get_application_type(), "processes": {"spare": 0}, "root": script_path, "working_directory": script_path, diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py index 47b95dac..792a86fa 100644 --- a/test/unit/applications/lang/python.py +++ b/test/unit/applications/lang/python.py @@ -12,7 +12,6 @@ class TestApplicationPython(TestApplicationProto): load_module = "wsgi" def load(self, script, name=None, module=None, **kwargs): - print() if name is None: name = script @@ -35,25 +34,25 @@ class TestApplicationPython(TestApplicationProto): script_path = '/app/python/' + name - appication_type = self.get_appication_type() + app = { + "type": self.get_application_type(), + "processes": kwargs.pop('processes', {"spare": 0}), + "path": script_path, + "working_directory": script_path, + "module": module, + } - if appication_type is None: - appication_type = self.application_type + for attr in ('callable', 'home', 'limits', 'path', 'protocol', + 'threads'): + if attr in kwargs: + app[attr] = kwargs.pop(attr) self._load_conf( { "listeners": { "*:7080": {"pass": "applications/" + quote(name, '')} }, - "applications": { - name: { - "type": appication_type, - "processes": {"spare": 0}, - "path": script_path, - "working_directory": script_path, - "module": module, - } - }, + "applications": {name: app}, }, **kwargs ) diff --git a/test/unit/applications/lang/ruby.py b/test/unit/applications/lang/ruby.py index bc3cefc6..82d66e65 100644 --- a/test/unit/applications/lang/ruby.py +++ b/test/unit/applications/lang/ruby.py @@ -7,17 +7,13 @@ class TestApplicationRuby(TestApplicationProto): def load(self, script, name='config.ru', **kwargs): script_path = option.test_dir + '/ruby/' + script - appication_type = self.get_appication_type() - - if appication_type is None: - appication_type = self.application_type self._load_conf( { "listeners": {"*:7080": {"pass": "applications/" + script}}, "applications": { script: { - "type": appication_type, + "type": self.get_application_type(), "processes": {"spare": 0}, "working_directory": script_path, "script": script_path + '/' + name, diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py index 2f748c21..6e760c70 100644 --- a/test/unit/applications/proto.py +++ b/test/unit/applications/proto.py @@ -7,6 +7,8 @@ from unit.control import TestControl class TestApplicationProto(TestControl): + application_type = None + def sec_epoch(self): return time.mktime(time.gmtime()) @@ -14,7 +16,7 @@ class TestApplicationProto(TestControl): return time.mktime(time.strptime(date, template)) def search_in_log(self, pattern, name='unit.log'): - with open(self.temp_dir + '/' + name, 'r', errors='ignore') as f: + with open(option.temp_dir + '/' + name, 'r', errors='ignore') as f: return re.search(pattern, f.read()) def wait_for_record(self, pattern, name='unit.log'): @@ -28,15 +30,12 @@ class TestApplicationProto(TestControl): return found - def get_appication_type(self): + def get_application_type(self): current_test = ( os.environ.get('PYTEST_CURRENT_TEST').split(':')[-1].split(' ')[0] ) - if current_test in option.generated_tests: - return option.generated_tests[current_test] - - return None + return option.generated_tests.get(current_test, self.application_type) def _load_conf(self, conf, **kwargs): if 'applications' in conf: diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index fdf681ae..fb1b112c 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -8,8 +8,6 @@ from unit.applications.proto import TestApplicationProto class TestApplicationTLS(TestApplicationProto): def setup_method(self): - super().setup_method() - self.context = ssl.create_default_context() self.context.check_hostname = False self.context.verify_mode = ssl.CERT_NONE @@ -24,9 +22,9 @@ class TestApplicationTLS(TestApplicationProto): '-x509', '-new', '-subj', '/CN=' + name + '/', - '-config', self.temp_dir + '/openssl.conf', - '-out', self.temp_dir + '/' + name + '.crt', - '-keyout', self.temp_dir + '/' + name + '.key', + '-config', option.temp_dir + '/openssl.conf', + '-out', option.temp_dir + '/' + name + '.crt', + '-keyout', option.temp_dir + '/' + name + '.key', ], stderr=subprocess.STDOUT, ) @@ -38,8 +36,8 @@ class TestApplicationTLS(TestApplicationProto): if key is None: key = crt - key_path = self.temp_dir + '/' + key + '.key' - crt_path = self.temp_dir + '/' + crt + '.crt' + key_path = option.temp_dir + '/' + key + '.key' + crt_path = option.temp_dir + '/' + crt + '.crt' with open(key_path, 'rb') as k, open(crt_path, 'rb') as c: return self.conf(k.read() + c.read(), '/certificates/' + crt) @@ -66,7 +64,7 @@ class TestApplicationTLS(TestApplicationProto): return ssl.get_server_certificate(addr, ssl_version=ssl_version) def openssl_conf(self): - conf_path = self.temp_dir + '/openssl.conf' + conf_path = option.temp_dir + '/openssl.conf' if os.path.exists(conf_path): return diff --git a/test/unit/check/go.py b/test/unit/check/go.py index dd2150eb..35b0c2d5 100644 --- a/test/unit/check/go.py +++ b/test/unit/check/go.py @@ -25,5 +25,8 @@ def check_go(current_dir, temp_dir, test_dir): if process.returncode == 0: return True + except KeyboardInterrupt: + raise + except: return None diff --git a/test/unit/control.py b/test/unit/control.py index 6fd350f4..f05aa827 100644 --- a/test/unit/control.py +++ b/test/unit/control.py @@ -1,5 +1,6 @@ import json +from conftest import option from unit.http import TestHTTP @@ -53,7 +54,7 @@ class TestControl(TestHTTP): args = { 'url': url, 'sock_type': 'unix', - 'addr': self.temp_dir + '/control.unit.sock', + 'addr': option.temp_dir + '/control.unit.sock', } if conf is not None: diff --git a/test/unit/feature/isolation.py b/test/unit/feature/isolation.py index c6f6f3c0..7877c03a 100644 --- a/test/unit/feature/isolation.py +++ b/test/unit/feature/isolation.py @@ -3,11 +3,8 @@ import os from unit.applications.lang.go import TestApplicationGo from unit.applications.lang.java import TestApplicationJava from unit.applications.lang.node import TestApplicationNode -from unit.applications.lang.perl import TestApplicationPerl -from unit.applications.lang.php import TestApplicationPHP -from unit.applications.lang.python import TestApplicationPython -from unit.applications.lang.ruby import TestApplicationRuby from unit.applications.proto import TestApplicationProto +from conftest import option class TestFeatureIsolation(TestApplicationProto): @@ -16,40 +13,119 @@ class TestFeatureIsolation(TestApplicationProto): def check(self, available, temp_dir): test_conf = {"namespaces": {"credential": True}} - module = '' - app = 'empty' + conf = '' if 'go' in available['modules']: - module = TestApplicationGo() + TestApplicationGo().prepare_env('empty', 'app') + + conf = { + "listeners": {"*:7080": {"pass": "applications/empty"}}, + "applications": { + "empty": { + "type": "external", + "processes": {"spare": 0}, + "working_directory": option.test_dir + "/go/empty", + "executable": option.temp_dir + "/go/app", + "isolation": {"namespaces": {"credential": True}}, + }, + }, + } - elif 'java' in available['modules']: - module = TestApplicationJava() - - elif 'node' in available['modules']: - module = TestApplicationNode() - app = 'basic' - - elif 'perl' in available['modules']: - module = TestApplicationPerl() - app = 'body_empty' + elif 'python' in available['modules']: + conf = { + "listeners": {"*:7080": {"pass": "applications/empty"}}, + "applications": { + "empty": { + "type": "python", + "processes": {"spare": 0}, + "path": option.test_dir + "/python/empty", + "working_directory": option.test_dir + "/python/empty", + "module": "wsgi", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } elif 'php' in available['modules']: - module = TestApplicationPHP() - app = 'phpinfo' - - elif 'python' in available['modules']: - module = TestApplicationPython() + conf = { + "listeners": {"*:7080": {"pass": "applications/phpinfo"}}, + "applications": { + "phpinfo": { + "type": "php", + "processes": {"spare": 0}, + "root": option.test_dir + "/php/phpinfo", + "working_directory": option.test_dir + "/php/phpinfo", + "index": "index.php", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } elif 'ruby' in available['modules']: - module = TestApplicationRuby() + conf = { + "listeners": {"*:7080": {"pass": "applications/empty"}}, + "applications": { + "empty": { + "type": "ruby", + "processes": {"spare": 0}, + "working_directory": option.test_dir + "/ruby/empty", + "script": option.test_dir + "/ruby/empty/config.ru", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } - if not module: - return + elif 'java' in available['modules']: + TestApplicationJava().prepare_env('empty') + + conf = { + "listeners": {"*:7080": {"pass": "applications/empty"}}, + "applications": { + "empty": { + "unit_jars": option.current_dir + "/build", + "type": "java", + "processes": {"spare": 0}, + "working_directory": option.test_dir + "/java/empty/", + "webapp": option.temp_dir + "/java", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } - module.temp_dir = temp_dir - module.load(app) + elif 'node' in available['modules']: + TestApplicationNode().prepare_env('basic') + + conf = { + "listeners": {"*:7080": {"pass": "applications/basic"}}, + "applications": { + "basic": { + "type": "external", + "processes": {"spare": 0}, + "working_directory": option.temp_dir + "/node", + "executable": "app.js", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + elif 'perl' in available['modules']: + conf = { + "listeners": {"*:7080": {"pass": "applications/body_empty"}}, + "applications": { + "body_empty": { + "type": "perl", + "processes": {"spare": 0}, + "working_directory": option.test_dir + + "/perl/body_empty", + "script": option.test_dir + "/perl/body_empty/psgi.pl", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + else: + return - resp = module.conf(test_conf, 'applications/' + app + '/isolation') - if 'success' not in resp: + if 'success' not in self.conf(conf): return userns = self.getns('user') diff --git a/test/unit/http.py b/test/unit/http.py index 7845f9a8..8d964978 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -5,7 +5,6 @@ import os import re import select import socket -import time import pytest from conftest import option @@ -188,6 +187,10 @@ class TestHTTP(TestUnit): try: part = sock.recv(buff_size) + + except KeyboardInterrupt: + raise + except: break @@ -243,7 +246,8 @@ class TestHTTP(TestUnit): try: last_size = int(chunks[-2], 16) - except: + + except ValueError: pytest.fail('Invalid zero size chunk') if last_size != 0 or chunks[-1] != b'': @@ -253,7 +257,8 @@ class TestHTTP(TestUnit): while len(chunks) >= 2: try: size = int(chunks.pop(0), 16) - except: + + except ValueError: pytest.fail('Invalid chunk size %s' % str(size)) if size == 0: @@ -283,23 +288,6 @@ class TestHTTP(TestUnit): def getjson(self, **kwargs): return self.get(json=True, **kwargs) - def waitforsocket(self, port): - ret = False - - for i in range(50): - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect(('127.0.0.1', port)) - ret = True - break - except: - sock.close() - time.sleep(0.1) - - sock.close() - - assert ret, 'socket connected' - def form_encode(self, fields): is_multipart = False diff --git a/test/unit/main.py b/test/unit/main.py index d5940995..488b3f4d 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -1,55 +1,19 @@ -import atexit -import os -import re -import shutil -import signal -import stat -import subprocess -import tempfile -import time -from multiprocessing import Process - import pytest -from conftest import _check_alerts -from conftest import _print_log from conftest import option -from conftest import public_dir -from conftest import waitforfiles class TestUnit(): @classmethod def setup_class(cls, complete_check=True): - cls.available = option.available - unit = TestUnit() - - unit._run() - - # read unit.log - - for i in range(50): - with open(unit.temp_dir + '/unit.log', 'r') as f: - log = f.read() - m = re.search('controller started', log) - - if m is None: - time.sleep(0.1) - else: - break - - if m is None: - _print_log(path=unit.temp_dir + '/unit.log') - exit("Unit is writing log too long") - - def check(available, prerequisites): + def check(): missed = [] # check modules - if 'modules' in prerequisites: - available_modules = list(available['modules'].keys()) + if 'modules' in cls.prerequisites: + available_modules = list(option.available['modules'].keys()) - for module in prerequisites['modules']: + for module in cls.prerequisites['modules']: if module in available_modules: continue @@ -60,10 +24,10 @@ class TestUnit(): # check features - if 'features' in prerequisites: - available_features = list(available['features'].keys()) + if 'features' in cls.prerequisites: + available_features = list(option.available['features'].keys()) - for feature in prerequisites['features']: + for feature in cls.prerequisites['features']: if feature in available_features: continue @@ -72,132 +36,7 @@ class TestUnit(): if missed: pytest.skip(', '.join(missed) + ' feature(s) not supported') - def destroy(): - unit.stop() - _check_alerts(log) - shutil.rmtree(unit.temp_dir) - - def complete(): - destroy() - check(cls.available, cls.prerequisites) - if complete_check: - complete() - else: - unit.complete = complete - return unit - - def setup_method(self): - self._run() - - def _run(self): - build_dir = option.current_dir + '/build' - self.unitd = build_dir + '/unitd' - - if not os.path.isfile(self.unitd): - exit("Could not find unit") - - self.temp_dir = tempfile.mkdtemp(prefix='unit-test-') - - public_dir(self.temp_dir) - - if oct(stat.S_IMODE(os.stat(build_dir).st_mode)) != '0o777': - public_dir(build_dir) - - os.mkdir(self.temp_dir + '/state') - - with open(self.temp_dir + '/unit.log', 'w') as log: - self._p = subprocess.Popen( - [ - self.unitd, - '--no-daemon', - '--modules', build_dir, - '--state', self.temp_dir + '/state', - '--pid', self.temp_dir + '/unit.pid', - '--log', self.temp_dir + '/unit.log', - '--control', 'unix:' + self.temp_dir + '/control.unit.sock', - '--tmp', self.temp_dir, - ], - stderr=log, - ) - - atexit.register(self.stop) - - if not waitforfiles(self.temp_dir + '/control.unit.sock'): - _print_log(path=self.temp_dir + '/unit.log') - exit("Could not start unit") - - self._started = True - - def teardown_method(self): - self.stop() - - # check unit.log for alerts - - unit_log = self.temp_dir + '/unit.log' - - with open(unit_log, 'r', encoding='utf-8', errors='ignore') as f: - _check_alerts(f.read()) - - # remove unit.log - - if not option.save_log: - shutil.rmtree(self.temp_dir) + check() else: - _print_log(path=self.temp_dir) - - assert self.stop_errors == [None, None], 'stop errors' - - def stop(self): - if not self._started: - return - - self.stop_errors = [] - - self.stop_errors.append(self._stop()) - - self.stop_errors.append(self.stop_processes()) - - atexit.unregister(self.stop) - - self._started = False - - def _stop(self): - if self._p.poll() is not None: - return - - with self._p as p: - p.send_signal(signal.SIGQUIT) - - try: - retcode = p.wait(15) - if retcode: - return 'Child process terminated with code ' + str(retcode) - except: - p.kill() - return 'Could not terminate unit' - - def run_process(self, target, *args): - if not hasattr(self, '_processes'): - self._processes = [] - - process = Process(target=target, args=args) - process.start() - - self._processes.append(process) - - def stop_processes(self): - if not hasattr(self, '_processes'): - return - - fail = False - for process in self._processes: - if process.is_alive(): - process.terminate() - process.join(timeout=15) - - if process.is_alive(): - fail = True - - if fail: - return 'Fail to stop process' + return check |