Changeset View
Changeset View
Standalone View
Standalone View
test/functional/test_runner.py
Show All 28 Lines | |||||
import logging | import logging | ||||
import xml.etree.ElementTree as ET | import xml.etree.ElementTree as ET | ||||
import json | import json | ||||
import threading | import threading | ||||
import multiprocessing | import multiprocessing | ||||
from queue import Queue, Empty | from queue import Queue, Empty | ||||
# Formatting. Default colors to empty strings. | # Formatting. Default colors to empty strings. | ||||
BOLD, BLUE, RED, GREY = ("", ""), ("", ""), ("", ""), ("", "") | BOLD, GREEN, RED, GREY = ("", ""), ("", ""), ("", ""), ("", "") | ||||
try: | try: | ||||
# Make sure python thinks it can write unicode to its stdout | # Make sure python thinks it can write unicode to its stdout | ||||
"\u2713".encode("utf_8").decode(sys.stdout.encoding) | "\u2713".encode("utf_8").decode(sys.stdout.encoding) | ||||
TICK = "✓ " | TICK = "✓ " | ||||
CROSS = "✖ " | CROSS = "✖ " | ||||
CIRCLE = "○ " | CIRCLE = "○ " | ||||
except UnicodeDecodeError: | except UnicodeDecodeError: | ||||
TICK = "P " | TICK = "P " | ||||
CROSS = "x " | CROSS = "x " | ||||
CIRCLE = "o " | CIRCLE = "o " | ||||
if os.name == 'posix': | if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393): | ||||
if os.name == 'nt': | |||||
import ctypes | |||||
kernel32 = ctypes.windll.kernel32 | |||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 | |||||
STD_OUTPUT_HANDLE = -11 | |||||
STD_ERROR_HANDLE = -12 | |||||
# Enable ascii color control to stdout | |||||
stdout = kernel32.GetStdHandle(STD_OUTPUT_HANDLE) | |||||
stdout_mode = ctypes.c_int32() | |||||
kernel32.GetConsoleMode(stdout, ctypes.byref(stdout_mode)) | |||||
kernel32.SetConsoleMode( | |||||
stdout, stdout_mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING) | |||||
# Enable ascii color control to stderr | |||||
stderr = kernel32.GetStdHandle(STD_ERROR_HANDLE) | |||||
stderr_mode = ctypes.c_int32() | |||||
kernel32.GetConsoleMode(stderr, ctypes.byref(stderr_mode)) | |||||
kernel32.SetConsoleMode( | |||||
stderr, stderr_mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING) | |||||
# primitive formatting on supported | # primitive formatting on supported | ||||
# terminal via ANSI escape sequences: | # terminal via ANSI escape sequences: | ||||
BOLD = ('\033[0m', '\033[1m') | BOLD = ('\033[0m', '\033[1m') | ||||
BLUE = ('\033[0m', '\033[0;34m') | GREEN = ('\033[0m', '\033[0;32m') | ||||
RED = ('\033[0m', '\033[0;31m') | RED = ('\033[0m', '\033[0;31m') | ||||
GREY = ('\033[0m', '\033[1;30m') | GREY = ('\033[0m', '\033[1;30m') | ||||
TEST_EXIT_PASSED = 0 | TEST_EXIT_PASSED = 0 | ||||
TEST_EXIT_SKIPPED = 77 | TEST_EXIT_SKIPPED = 77 | ||||
NON_SCRIPTS = [ | NON_SCRIPTS = [ | ||||
# These are python files that live in the functional tests directory, but | # These are python files that live in the functional tests directory, but | ||||
▲ Show 20 Lines • Show All 140 Lines • ▼ Show 20 Lines | def main(): | ||||
# Set up logging | # Set up logging | ||||
logging_level = logging.INFO if args.quiet else logging.DEBUG | logging_level = logging.INFO if args.quiet else logging.DEBUG | ||||
logging.basicConfig(format='%(message)s', level=logging_level) | logging.basicConfig(format='%(message)s', level=logging_level) | ||||
logging.info("Starting {}".format(args.testsuitename)) | logging.info("Starting {}".format(args.testsuitename)) | ||||
# Create base test directory | # Create base test directory | ||||
tmpdir = os.path.join("{}", "test_runner_₿_🏃_{:%Y%m%d_%H%M%S}").format( | tmpdir = os.path.join("{}", "test_runner_₿_🏃_{:%Y%m%d_%H%M%S}").format( | ||||
args.tmpdirprefix, datetime.datetime.now()) | args.tmpdirprefix, datetime.datetime.now()) | ||||
# If we fixed the command-line and filename encoding issue on Windows, | |||||
# these two lines could be removed | |||||
if config["environment"]["EXEEXT"] == ".exe": | |||||
tmpdir = os.path.join("{}", "test_runner_{:%Y%m%d_%H%M%S}").format( | |||||
args.tmpdirprefix, datetime.datetime.now()) | |||||
os.makedirs(tmpdir) | os.makedirs(tmpdir) | ||||
logging.debug("Temporary test directory at {}".format(tmpdir)) | logging.debug("Temporary test directory at {}".format(tmpdir)) | ||||
if not os.path.isabs(args.junitoutput): | if not os.path.isabs(args.junitoutput): | ||||
args.junitoutput = os.path.join(tmpdir, args.junitoutput) | args.junitoutput = os.path.join(tmpdir, args.junitoutput) | ||||
enable_bitcoind = config["components"].getboolean("ENABLE_BITCOIND") | enable_bitcoind = config["components"].getboolean("ENABLE_BITCOIND") | ||||
▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | else: | ||||
test_list = all_scripts | test_list = all_scripts | ||||
cutoff = args.cutoff | cutoff = args.cutoff | ||||
if args.extended: | if args.extended: | ||||
cutoff = sys.maxsize | cutoff = sys.maxsize | ||||
# Remove the test cases that the user has explicitly asked to exclude. | # Remove the test cases that the user has explicitly asked to exclude. | ||||
if args.exclude: | if args.exclude: | ||||
tests_excl = [re.sub(r"\.py$", "", t) | tests_excl = [re.sub(r"\.py$", "", t) | ||||
+ ".py" for t in args.exclude.split(',')] | + (".py" if ".py" not in t else "") for t in args.exclude.split(',')] | ||||
for exclude_test in tests_excl: | for exclude_test in tests_excl: | ||||
if exclude_test in test_list: | if exclude_test in test_list: | ||||
test_list.remove(exclude_test) | test_list.remove(exclude_test) | ||||
else: | else: | ||||
print("{}WARNING!{} Test '{}' not found in current test list.".format( | print("{}WARNING!{} Test '{}' not found in current test list.".format( | ||||
BOLD[1], BOLD[0], exclude_test)) | BOLD[1], BOLD[0], exclude_test)) | ||||
# Update timings from build_dir only if separate build directory is used. | # Update timings from build_dir only if separate build directory is used. | ||||
▲ Show 20 Lines • Show All 265 Lines • ▼ Show 20 Lines | for test_result in test_results: | ||||
testdir = test_result.testdir | testdir = test_result.testdir | ||||
if combined_logs_len and os.path.isdir(testdir): | if combined_logs_len and os.path.isdir(testdir): | ||||
# Print the final `combinedlogslen` lines of the combined logs | # Print the final `combinedlogslen` lines of the combined logs | ||||
print('{}Combine the logs and print the last {} lines ...{}'.format( | print('{}Combine the logs and print the last {} lines ...{}'.format( | ||||
BOLD[1], combined_logs_len, BOLD[0])) | BOLD[1], combined_logs_len, BOLD[0])) | ||||
print('\n============') | print('\n============') | ||||
print('{}Combined log for {}:{}'.format(BOLD[1], testdir, BOLD[0])) | print('{}Combined log for {}:{}'.format(BOLD[1], testdir, BOLD[0])) | ||||
print('============\n') | print('============\n') | ||||
combined_logs, _ = subprocess.Popen([sys.executable, os.path.join( | combined_logs_args = [ | ||||
tests_dir, 'combine_logs.py'), '-c', testdir], universal_newlines=True, stdout=subprocess.PIPE).communicate() | sys.executable, os.path.join( | ||||
tests_dir, 'combine_logs.py'), testdir] | |||||
if BOLD[0]: | |||||
combined_logs_args += ['--color'] | |||||
combined_logs, _ = subprocess.Popen( | |||||
combined_logs_args, universal_newlines=True, stdout=subprocess.PIPE).communicate() | |||||
print( | print( | ||||
"\n".join( | "\n".join( | ||||
deque( | deque( | ||||
combined_logs.splitlines(), | combined_logs.splitlines(), | ||||
combined_logs_len))) | combined_logs_len))) | ||||
status = TICK + "Passed" if all_passed else CROSS + "Failed" | status = TICK + "Passed" if all_passed else CROSS + "Failed" | ||||
if not all_passed: | if not all_passed: | ||||
Show All 26 Lines | def sort_key(self): | ||||
return 0, self.name.lower() | return 0, self.name.lower() | ||||
elif self.status == "Failed": | elif self.status == "Failed": | ||||
return 2, self.name.lower() | return 2, self.name.lower() | ||||
elif self.status == "Skipped": | elif self.status == "Skipped": | ||||
return 1, self.name.lower() | return 1, self.name.lower() | ||||
def __repr__(self): | def __repr__(self): | ||||
if self.status == "Passed": | if self.status == "Passed": | ||||
color = BLUE | color = GREEN | ||||
glyph = TICK | glyph = TICK | ||||
elif self.status == "Failed": | elif self.status == "Failed": | ||||
color = RED | color = RED | ||||
glyph = CROSS | glyph = CROSS | ||||
elif self.status == "Skipped": | elif self.status == "Skipped": | ||||
color = GREY | color = GREY | ||||
glyph = CIRCLE | glyph = CIRCLE | ||||
▲ Show 20 Lines • Show All 231 Lines • Show Last 20 Lines |