diff options
Diffstat (limited to 'test/unit/main.py')
-rw-r--r-- | test/unit/main.py | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/test/unit/main.py b/test/unit/main.py new file mode 100644 index 00000000..247f3fbf --- /dev/null +++ b/test/unit/main.py @@ -0,0 +1,311 @@ +import os +import re +import sys +import time +import shutil +import argparse +import platform +import tempfile +import unittest +import subprocess +from multiprocessing import Process + + +class TestUnit(unittest.TestCase): + + current_dir = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir) + ) + pardir = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir, os.pardir) + ) + architecture = platform.architecture()[0] + maxDiff = None + + detailed = False + save_log = False + + def __init__(self, methodName='runTest'): + super().__init__(methodName) + + if re.match(r'.*\/run\.py$', sys.argv[0]): + args, rest = TestUnit._parse_args() + + TestUnit._set_args(args) + + @classmethod + def main(cls): + args, rest = TestUnit._parse_args() + + for i, arg in enumerate(rest): + if arg[:5] == 'test_': + rest[i] = cls.__name__ + '.' + arg + + sys.argv = sys.argv[:1] + rest + + TestUnit._set_args(args) + + unittest.main() + + def setUp(self): + self._run() + + def tearDown(self): + self.stop() + + # detect errors and failures for current test + + def list2reason(exc_list): + if exc_list and exc_list[-1][0] is self: + return exc_list[-1][1] + + if hasattr(self, '_outcome'): + result = self.defaultTestResult() + self._feedErrorsToResult(result, self._outcome.errors) + else: + result = getattr( + self, '_outcomeForDoCleanups', self._resultForDoCleanups + ) + + success = not list2reason(result.errors) and not list2reason( + result.failures + ) + + # check unit.log for alerts + + unit_log = self.testdir + '/unit.log' + + with open(unit_log, 'r', encoding='utf-8', errors='ignore') as f: + self._check_alerts(f.read()) + + # remove unit.log + + if not TestUnit.save_log and success: + shutil.rmtree(self.testdir) + + else: + self._print_path_to_log() + + def check_modules(self, *modules): + self._run() + + for i in range(50): + with open(self.testdir + '/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: + self.stop() + exit("Unit is writing log too long") + + missed_module = '' + for module in modules: + if module == 'go': + env = os.environ.copy() + env['GOPATH'] = self.pardir + '/go' + + try: + process = subprocess.Popen( + [ + 'go', + 'build', + '-o', + self.testdir + '/go/check_module', + self.current_dir + '/go/empty/app.go', + ], + env=env, + ) + process.communicate() + + m = module if process.returncode == 0 else None + + except: + m = None + + elif module == 'node': + if os.path.isdir(self.pardir + '/node/node_modules'): + m = module + else: + m = None + + elif module == 'openssl': + try: + subprocess.check_output(['which', 'openssl']) + + output = subprocess.check_output( + [self.pardir + '/build/unitd', '--version'], + stderr=subprocess.STDOUT, + ) + + m = re.search('--openssl', output.decode()) + + except: + m = None + + else: + m = re.search('module: ' + module, log) + + if m is None: + missed_module = module + break + + self.stop() + self._check_alerts(log) + shutil.rmtree(self.testdir) + + if missed_module: + raise unittest.SkipTest('Unit has no ' + missed_module + ' module') + + def stop(self): + if self._started: + self._stop() + + def _run(self): + self.testdir = tempfile.mkdtemp(prefix='unit-test-') + + os.mkdir(self.testdir + '/state') + + print() + + def _run_unit(): + subprocess.call( + [ + self.pardir + '/build/unitd', + '--no-daemon', + '--modules', self.pardir + '/build', + '--state', self.testdir + '/state', + '--pid', self.testdir + '/unit.pid', + '--log', self.testdir + '/unit.log', + '--control', 'unix:' + self.testdir + '/control.unit.sock', + ] + ) + + self._p = Process(target=_run_unit) + self._p.start() + + if not self.waitforfiles( + self.testdir + '/unit.pid', + self.testdir + '/unit.log', + self.testdir + '/control.unit.sock', + ): + exit("Could not start unit") + + self._started = True + + self.skip_alerts = [ + r'read signalfd\(4\) failed', + r'sendmsg.+failed', + r'recvmsg.+failed', + ] + self.skip_sanitizer = False + + def _stop(self): + with open(self.testdir + '/unit.pid', 'r') as f: + pid = f.read().rstrip() + + subprocess.call(['kill', '-s', 'QUIT', pid]) + + for i in range(50): + if not os.path.exists(self.testdir + '/unit.pid'): + break + time.sleep(0.1) + + if os.path.exists(self.testdir + '/unit.pid'): + exit("Could not terminate unit") + + self._started = False + + self._p.join(timeout=1) + self._terminate_process(self._p) + + def _terminate_process(self, process): + if process.is_alive(): + process.terminate() + process.join(timeout=5) + + if process.is_alive(): + exit("Could not terminate process " + process.pid) + + if process.exitcode: + exit("Child process terminated with code " + str(process.exitcode)) + + def _check_alerts(self, log): + found = False + + alerts = re.findall('.+\[alert\].+', log) + + if alerts: + print('All alerts/sanitizer errors found in log:') + [print(alert) for alert in alerts] + found = True + + if self.skip_alerts: + for skip in self.skip_alerts: + alerts = [al for al in alerts if re.search(skip, al) is None] + + if alerts: + self._print_path_to_log() + self.assertFalse(alerts, 'alert(s)') + + if not self.skip_sanitizer: + sanitizer_errors = re.findall('.+Sanitizer.+', log) + + if sanitizer_errors: + self._print_path_to_log() + self.assertFalse(sanitizer_errors, 'sanitizer error(s)') + + if found: + print('skipped.') + + def waitforfiles(self, *files): + for i in range(50): + wait = False + ret = False + + for f in files: + if not os.path.exists(f): + wait = True + break + + if wait: + time.sleep(0.1) + + else: + ret = True + break + + return ret + + @staticmethod + def _parse_args(): + parser = argparse.ArgumentParser(add_help=False) + + parser.add_argument( + '-d', + '--detailed', + dest='detailed', + action='store_true', + help='Detailed output for tests', + ) + parser.add_argument( + '-l', + '--log', + dest='save_log', + action='store_true', + help='Save unit.log after the test execution', + ) + + return parser.parse_known_args() + + @staticmethod + def _set_args(args): + TestUnit.detailed = args.detailed + TestUnit.save_log = args.save_log + + def _print_path_to_log(self): + print('Path to unit.log:\n' + self.testdir + '/unit.log') |