AIM-PIbd-32-Kurbanova-A-A/aimenv/Lib/site-packages/psutil/tests/runner.py

386 lines
11 KiB
Python
Raw Normal View History

2024-10-02 22:15:59 +04:00
#!/usr/bin/env python3
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit test runner, providing new features on top of unittest module:
- colourized output
- parallel run (UNIX only)
- print failures/tracebacks on CTRL+C
- re-run failed tests only (make test-failed).
Invocation examples:
- make test
- make test-failed
Parallel:
- make test-parallel
- make test-process ARGS=--parallel
"""
from __future__ import print_function
import atexit
import optparse
import os
import sys
import textwrap
import time
import unittest
try:
import ctypes
except ImportError:
ctypes = None
try:
import concurrencytest # pip install concurrencytest
except ImportError:
concurrencytest = None
import psutil
from psutil._common import hilite
from psutil._common import print_color
from psutil._common import term_supports_colors
from psutil._compat import super
from psutil.tests import CI_TESTING
from psutil.tests import import_module_by_path
from psutil.tests import print_sysinfo
from psutil.tests import reap_children
from psutil.tests import safe_rmpath
VERBOSITY = 2
FAILED_TESTS_FNAME = '.failed-tests.txt'
NWORKERS = psutil.cpu_count() or 1
USE_COLORS = not CI_TESTING and term_supports_colors()
HERE = os.path.abspath(os.path.dirname(__file__))
loadTestsFromTestCase = ( # noqa: N816
unittest.defaultTestLoader.loadTestsFromTestCase
)
def cprint(msg, color, bold=False, file=None):
if file is None:
file = sys.stderr if color == 'red' else sys.stdout
if USE_COLORS:
print_color(msg, color, bold=bold, file=file)
else:
print(msg, file=file)
class TestLoader:
testdir = HERE
skip_files = ['test_memleaks.py']
if "WHEELHOUSE_UPLOADER_USERNAME" in os.environ:
skip_files.extend(['test_osx.py', 'test_linux.py', 'test_posix.py'])
def _get_testmods(self):
return [
os.path.join(self.testdir, x)
for x in os.listdir(self.testdir)
if x.startswith('test_')
and x.endswith('.py')
and x not in self.skip_files
]
def _iter_testmod_classes(self):
"""Iterate over all test files in this directory and return
all TestCase classes in them.
"""
for path in self._get_testmods():
mod = import_module_by_path(path)
for name in dir(mod):
obj = getattr(mod, name)
if isinstance(obj, type) and issubclass(
obj, unittest.TestCase
):
yield obj
def all(self):
suite = unittest.TestSuite()
for obj in self._iter_testmod_classes():
test = loadTestsFromTestCase(obj)
suite.addTest(test)
return suite
def last_failed(self):
# ...from previously failed test run
suite = unittest.TestSuite()
if not os.path.isfile(FAILED_TESTS_FNAME):
return suite
with open(FAILED_TESTS_FNAME) as f:
names = f.read().split()
for n in names:
test = unittest.defaultTestLoader.loadTestsFromName(n)
suite.addTest(test)
return suite
def from_name(self, name):
if name.endswith('.py'):
name = os.path.splitext(os.path.basename(name))[0]
return unittest.defaultTestLoader.loadTestsFromName(name)
class ColouredResult(unittest.TextTestResult):
def addSuccess(self, test):
unittest.TestResult.addSuccess(self, test)
cprint("OK", "green")
def addError(self, test, err):
unittest.TestResult.addError(self, test, err)
cprint("ERROR", "red", bold=True)
def addFailure(self, test, err):
unittest.TestResult.addFailure(self, test, err)
cprint("FAIL", "red")
def addSkip(self, test, reason):
unittest.TestResult.addSkip(self, test, reason)
cprint("skipped: %s" % reason.strip(), "brown")
def printErrorList(self, flavour, errors):
flavour = hilite(flavour, "red", bold=flavour == 'ERROR')
super().printErrorList(flavour, errors)
class ColouredTextRunner(unittest.TextTestRunner):
"""A coloured text runner which also prints failed tests on
KeyboardInterrupt and save failed tests in a file so that they can
be re-run.
"""
resultclass = ColouredResult if USE_COLORS else unittest.TextTestResult
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.failed_tnames = set()
def _makeResult(self):
# Store result instance so that it can be accessed on
# KeyboardInterrupt.
self.result = super()._makeResult()
return self.result
def _write_last_failed(self):
if self.failed_tnames:
with open(FAILED_TESTS_FNAME, "w") as f:
for tname in self.failed_tnames:
f.write(tname + '\n')
def _save_result(self, result):
if not result.wasSuccessful():
for t in result.errors + result.failures:
tname = t[0].id()
self.failed_tnames.add(tname)
def _run(self, suite):
try:
result = super().run(suite)
except (KeyboardInterrupt, SystemExit):
result = self.runner.result
result.printErrors()
raise sys.exit(1)
else:
self._save_result(result)
return result
def _exit(self, success):
if success:
cprint("SUCCESS", "green", bold=True)
safe_rmpath(FAILED_TESTS_FNAME)
sys.exit(0)
else:
cprint("FAILED", "red", bold=True)
self._write_last_failed()
sys.exit(1)
def run(self, suite):
result = self._run(suite)
self._exit(result.wasSuccessful())
class ParallelRunner(ColouredTextRunner):
@staticmethod
def _parallelize(suite):
def fdopen(fd, mode, *kwds):
stream = orig_fdopen(fd, mode)
atexit.register(stream.close)
return stream
# Monkey patch concurrencytest lib bug (fdopen() stream not closed).
# https://github.com/cgoldberg/concurrencytest/issues/11
orig_fdopen = os.fdopen
concurrencytest.os.fdopen = fdopen
forker = concurrencytest.fork_for_tests(NWORKERS)
return concurrencytest.ConcurrentTestSuite(suite, forker)
@staticmethod
def _split_suite(suite):
serial = unittest.TestSuite()
parallel = unittest.TestSuite()
for test in suite:
if test.countTestCases() == 0:
continue
if isinstance(test, unittest.TestSuite):
test_class = test._tests[0].__class__
elif isinstance(test, unittest.TestCase):
test_class = test
else:
raise TypeError("can't recognize type %r" % test)
if getattr(test_class, '_serialrun', False):
serial.addTest(test)
else:
parallel.addTest(test)
return (serial, parallel)
def run(self, suite):
ser_suite, par_suite = self._split_suite(suite)
par_suite = self._parallelize(par_suite)
# run parallel
cprint(
"starting parallel tests using %s workers" % NWORKERS,
"green",
bold=True,
)
t = time.time()
par = self._run(par_suite)
par_elapsed = time.time() - t
# At this point we should have N zombies (the workers), which
# will disappear with wait().
orphans = psutil.Process().children()
_gone, alive = psutil.wait_procs(orphans, timeout=1)
if alive:
cprint("alive processes %s" % alive, "red")
reap_children()
# run serial
t = time.time()
ser = self._run(ser_suite)
ser_elapsed = time.time() - t
# print
if not par.wasSuccessful() and ser_suite.countTestCases() > 0:
par.printErrors() # print them again at the bottom
par_fails, par_errs, par_skips = map(
len, (par.failures, par.errors, par.skipped)
)
ser_fails, ser_errs, ser_skips = map(
len, (ser.failures, ser.errors, ser.skipped)
)
print(
textwrap.dedent(
"""
+----------+----------+----------+----------+----------+----------+
| | total | failures | errors | skipped | time |
+----------+----------+----------+----------+----------+----------+
| parallel | %3s | %3s | %3s | %3s | %.2fs |
+----------+----------+----------+----------+----------+----------+
| serial | %3s | %3s | %3s | %3s | %.2fs |
+----------+----------+----------+----------+----------+----------+
"""
% (
par.testsRun,
par_fails,
par_errs,
par_skips,
par_elapsed,
ser.testsRun,
ser_fails,
ser_errs,
ser_skips,
ser_elapsed,
)
)
)
print(
"Ran %s tests in %.3fs using %s workers"
% (
par.testsRun + ser.testsRun,
par_elapsed + ser_elapsed,
NWORKERS,
)
)
ok = par.wasSuccessful() and ser.wasSuccessful()
self._exit(ok)
def get_runner(parallel=False):
def warn(msg):
cprint(msg + " Running serial tests instead.", "red")
if parallel:
if psutil.WINDOWS:
warn("Can't run parallel tests on Windows.")
elif concurrencytest is None:
warn("concurrencytest module is not installed.")
elif NWORKERS == 1:
warn("Only 1 CPU available.")
else:
return ParallelRunner(verbosity=VERBOSITY)
return ColouredTextRunner(verbosity=VERBOSITY)
# Used by test_*,py modules.
def run_from_name(name):
if CI_TESTING:
print_sysinfo()
suite = TestLoader().from_name(name)
runner = get_runner()
runner.run(suite)
def setup():
psutil._set_debug(True)
def main():
setup()
usage = "python3 -m psutil.tests [opts] [test-name]"
parser = optparse.OptionParser(usage=usage, description="run unit tests")
parser.add_option(
"--last-failed",
action="store_true",
default=False,
help="only run last failed tests",
)
parser.add_option(
"--parallel",
action="store_true",
default=False,
help="run tests in parallel",
)
opts, args = parser.parse_args()
if not opts.last_failed:
safe_rmpath(FAILED_TESTS_FNAME)
# loader
loader = TestLoader()
if args:
if len(args) > 1:
parser.print_usage()
return sys.exit(1)
else:
suite = loader.from_name(args[0])
elif opts.last_failed:
suite = loader.last_failed()
else:
suite = loader.all()
if CI_TESTING:
print_sysinfo()
runner = get_runner(opts.parallel)
runner.run(suite)
if __name__ == '__main__':
main()