196 lines
5.6 KiB
Python
196 lines
5.6 KiB
Python
#!/usr/bin/env python
|
|
|
|
# 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 colourized output and printing failures
|
|
on KeyboardInterrupt.
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
import atexit
|
|
import os
|
|
import sys
|
|
import unittest
|
|
from unittest import TestResult
|
|
from unittest import TextTestResult
|
|
from unittest import TextTestRunner
|
|
try:
|
|
import ctypes
|
|
except ImportError:
|
|
ctypes = None
|
|
|
|
import psutil
|
|
from psutil._common import memoize
|
|
from psutil.tests import safe_rmpath
|
|
from psutil.tests import TOX
|
|
|
|
|
|
HERE = os.path.abspath(os.path.dirname(__file__))
|
|
VERBOSITY = 1 if TOX else 2
|
|
FAILED_TESTS_FNAME = '.failed-tests.txt'
|
|
if os.name == 'posix':
|
|
GREEN = 1
|
|
RED = 2
|
|
BROWN = 94
|
|
else:
|
|
GREEN = 2
|
|
RED = 4
|
|
BROWN = 6
|
|
DEFAULT_COLOR = 7
|
|
|
|
|
|
def term_supports_colors(file=sys.stdout):
|
|
if os.name == 'nt':
|
|
return ctypes is not None
|
|
try:
|
|
import curses
|
|
assert file.isatty()
|
|
curses.setupterm()
|
|
assert curses.tigetnum("colors") > 0
|
|
except Exception:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
|
|
def hilite(s, color, bold=False):
|
|
"""Return an highlighted version of 'string'."""
|
|
attr = []
|
|
if color == GREEN:
|
|
attr.append('32')
|
|
elif color == RED:
|
|
attr.append('91')
|
|
elif color == BROWN:
|
|
attr.append('33')
|
|
else:
|
|
raise ValueError("unrecognized color")
|
|
if bold:
|
|
attr.append('1')
|
|
return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s)
|
|
|
|
|
|
@memoize
|
|
def _stderr_handle():
|
|
GetStdHandle = ctypes.windll.Kernel32.GetStdHandle
|
|
STD_ERROR_HANDLE_ID = ctypes.c_ulong(0xfffffff4)
|
|
GetStdHandle.restype = ctypes.c_ulong
|
|
handle = GetStdHandle(STD_ERROR_HANDLE_ID)
|
|
atexit.register(ctypes.windll.Kernel32.CloseHandle, handle)
|
|
return handle
|
|
|
|
|
|
def win_colorprint(printer, s, color, bold=False):
|
|
if bold and color <= 7:
|
|
color += 8
|
|
handle = _stderr_handle()
|
|
SetConsoleTextAttribute = ctypes.windll.Kernel32.SetConsoleTextAttribute
|
|
SetConsoleTextAttribute(handle, color)
|
|
try:
|
|
printer(s)
|
|
finally:
|
|
SetConsoleTextAttribute(handle, DEFAULT_COLOR)
|
|
|
|
|
|
class ColouredResult(TextTestResult):
|
|
|
|
def _color_print(self, s, color, bold=False):
|
|
if os.name == 'posix':
|
|
self.stream.writeln(hilite(s, color, bold=bold))
|
|
else:
|
|
win_colorprint(self.stream.writeln, s, color, bold=bold)
|
|
|
|
def addSuccess(self, test):
|
|
TestResult.addSuccess(self, test)
|
|
self._color_print("OK", GREEN)
|
|
|
|
def addError(self, test, err):
|
|
TestResult.addError(self, test, err)
|
|
self._color_print("ERROR", RED, bold=True)
|
|
|
|
def addFailure(self, test, err):
|
|
TestResult.addFailure(self, test, err)
|
|
self._color_print("FAIL", RED)
|
|
|
|
def addSkip(self, test, reason):
|
|
TestResult.addSkip(self, test, reason)
|
|
self._color_print("skipped: %s" % reason, BROWN)
|
|
|
|
def printErrorList(self, flavour, errors):
|
|
flavour = hilite(flavour, RED, bold=flavour == 'ERROR')
|
|
TextTestResult.printErrorList(self, flavour, errors)
|
|
|
|
|
|
class ColouredRunner(TextTestRunner):
|
|
resultclass = ColouredResult if term_supports_colors() else TextTestResult
|
|
|
|
def _makeResult(self):
|
|
# Store result instance so that it can be accessed on
|
|
# KeyboardInterrupt.
|
|
self.result = TextTestRunner._makeResult(self)
|
|
return self.result
|
|
|
|
|
|
def setup_tests():
|
|
if 'PSUTIL_TESTING' not in os.environ:
|
|
# This won't work on Windows but set_testing() below will do it.
|
|
os.environ['PSUTIL_TESTING'] = '1'
|
|
psutil._psplatform.cext.set_testing()
|
|
|
|
|
|
def get_suite(name=None):
|
|
suite = unittest.TestSuite()
|
|
if name is None:
|
|
testmods = [os.path.splitext(x)[0] for x in os.listdir(HERE)
|
|
if x.endswith('.py') and x.startswith('test_') and not
|
|
x.startswith('test_memory_leaks')]
|
|
if "WHEELHOUSE_UPLOADER_USERNAME" in os.environ:
|
|
testmods = [x for x in testmods if not x.endswith((
|
|
"osx", "posix", "linux"))]
|
|
for tm in testmods:
|
|
# ...so that the full test paths are printed on screen
|
|
tm = "psutil.tests.%s" % tm
|
|
suite.addTest(unittest.defaultTestLoader.loadTestsFromName(tm))
|
|
else:
|
|
name = os.path.splitext(os.path.basename(name))[0]
|
|
suite.addTest(unittest.defaultTestLoader.loadTestsFromName(name))
|
|
return suite
|
|
|
|
|
|
def get_suite_from_failed():
|
|
# ...from previously failed test run
|
|
suite = unittest.TestSuite()
|
|
if not os.path.isfile(FAILED_TESTS_FNAME):
|
|
return suite
|
|
with open(FAILED_TESTS_FNAME, 'rt') as f:
|
|
names = f.read().split()
|
|
for n in names:
|
|
suite.addTest(unittest.defaultTestLoader.loadTestsFromName(n))
|
|
return suite
|
|
|
|
|
|
def save_failed_tests(result):
|
|
if result.wasSuccessful():
|
|
return safe_rmpath(FAILED_TESTS_FNAME)
|
|
with open(FAILED_TESTS_FNAME, 'wt') as f:
|
|
for t in result.errors + result.failures:
|
|
tname = str(t[0])
|
|
f.write(tname + '\n')
|
|
|
|
|
|
def run(name=None, last_failed=False):
|
|
setup_tests()
|
|
runner = ColouredRunner(verbosity=VERBOSITY)
|
|
suite = get_suite_from_failed() if last_failed else get_suite(name)
|
|
try:
|
|
result = runner.run(suite)
|
|
except (KeyboardInterrupt, SystemExit) as err:
|
|
print("received %s" % err.__class__.__name__, file=sys.stderr)
|
|
runner.result.printErrors()
|
|
sys.exit(1)
|
|
else:
|
|
save_failed_tests(result)
|
|
success = result.wasSuccessful()
|
|
sys.exit(0 if success else 1)
|