regression: clean up testspec and run_tests.py
This commit is contained in:
parent
71d1d4143b
commit
2a2b3b9cbe
|
@ -25,7 +25,7 @@ import memusage
|
|||
import os
|
||||
try:
|
||||
import Queue
|
||||
except:
|
||||
except ImportError:
|
||||
import queue
|
||||
Queue = queue
|
||||
import signal
|
||||
|
@ -38,16 +38,10 @@ import traceback
|
|||
import warnings
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
try:
|
||||
import psutil
|
||||
if not hasattr(psutil.Process, "children") and hasattr(psutil.Process, "get_children"):
|
||||
# psutil API change
|
||||
psutil.Process.children = psutil.Process.get_children
|
||||
except ImportError:
|
||||
print("Error: failed to import psutil module.\n"
|
||||
"To install psutil, try:\n"
|
||||
" pip install --user psutil", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
import psutil
|
||||
if not hasattr(psutil.Process, "children"):
|
||||
# psutil API change
|
||||
psutil.Process.children = getattr(psutil.Process, "get_children")
|
||||
|
||||
ANSI_RESET = "\033[0m"
|
||||
ANSI_RED = "\033[31;1m"
|
||||
|
@ -63,9 +57,9 @@ def output_color(color, s):
|
|||
return s
|
||||
|
||||
# Find a command in the PATH.
|
||||
def which(file):
|
||||
def which(filename):
|
||||
for path in os.environ["PATH"].split(os.pathsep):
|
||||
candidate = os.path.join(path, file)
|
||||
candidate = os.path.join(path, filename)
|
||||
if os.path.exists(candidate) and os.access(candidate, os.X_OK):
|
||||
return candidate
|
||||
return None
|
||||
|
@ -161,7 +155,7 @@ def run_test(test, status_queue, kill_switch, verbose=False, stuck_timeout=None,
|
|||
# If we have a "pidspace" program, use that to ensure that programs
|
||||
# that double-fork can't continue running after the parent command
|
||||
# dies.
|
||||
if which("pidspace") != None:
|
||||
if which("pidspace") is not None:
|
||||
command = [which("pidspace"), "--"] + command
|
||||
|
||||
# Print command and path.
|
||||
|
@ -182,7 +176,6 @@ def run_test(test, status_queue, kill_switch, verbose=False, stuck_timeout=None,
|
|||
|
||||
# Start the command.
|
||||
peak_mem_usage = None
|
||||
cpu_usage = None
|
||||
try:
|
||||
process = subprocess.Popen(command,
|
||||
stdout=output, stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
|
||||
|
@ -236,25 +229,25 @@ def run_test(test, status_queue, kill_switch, verbose=False, stuck_timeout=None,
|
|||
|
||||
with cpuusage.process_poller(process.pid) as c:
|
||||
# Inactivity timeout
|
||||
low_cpu_usage = 0.05 # 5% -- FIXME: hardcoded
|
||||
low_cpu_usage = 0.05 # 5%
|
||||
cpu_history = collections.deque() # sliding window
|
||||
last_cpu_usage = 0
|
||||
cpu_usage_total = [0] # workaround for variable scope
|
||||
|
||||
# Also set a CPU timeout. We poll the cpu usage periodically.
|
||||
def cpu_timeout():
|
||||
last_cpu_usage = 0
|
||||
interval = min(0.5, test.cpu_timeout / 10.0)
|
||||
while test_status[0] is RUNNING:
|
||||
cpu_usage = c.cpu_usage()
|
||||
thread_cpu_usage = c.cpu_usage()
|
||||
|
||||
if stuck_timeout:
|
||||
# append to window
|
||||
now = time.time()
|
||||
if not cpu_history:
|
||||
cpu_history.append((time.time(), cpu_usage / interval))
|
||||
cpu_history.append((time.time(), thread_cpu_usage / interval))
|
||||
else:
|
||||
real_interval = now - cpu_history[-1][0]
|
||||
cpu_increment = cpu_usage - last_cpu_usage
|
||||
cpu_increment = thread_cpu_usage - last_cpu_usage
|
||||
cpu_history.append((now, cpu_increment / real_interval))
|
||||
cpu_usage_total[0] += cpu_history[-1][1]
|
||||
|
||||
|
@ -264,19 +257,20 @@ def run_test(test, status_queue, kill_switch, verbose=False, stuck_timeout=None,
|
|||
cpu_history.popleft()
|
||||
|
||||
if (now - cpu_history[0][0] >= stuck_timeout and
|
||||
cpu_usage_total[0] / len(cpu_history) < low_cpu_usage):
|
||||
cpu_usage_total[0] / len(cpu_history) < low_cpu_usage):
|
||||
test_status[0] = STUCK
|
||||
kill_family(grace_period, process.pid)
|
||||
break
|
||||
|
||||
if cpu_usage > test.cpu_timeout:
|
||||
if thread_cpu_usage > test.cpu_timeout:
|
||||
test_status[0] = CPU_TIMEOUT
|
||||
kill_family(grace_period, process.pid)
|
||||
break
|
||||
|
||||
last_cpu_usage = cpu_usage
|
||||
last_cpu_usage = thread_cpu_usage
|
||||
time.sleep(interval)
|
||||
|
||||
cpu_timer = None
|
||||
if test.cpu_timeout > 0:
|
||||
cpu_timer = threading.Thread(target=cpu_timeout)
|
||||
cpu_timer.daemon = True
|
||||
|
@ -294,17 +288,17 @@ def run_test(test, status_queue, kill_switch, verbose=False, stuck_timeout=None,
|
|||
# No special status, so assume it failed by itself
|
||||
test_status[0] = FAILED
|
||||
|
||||
if test.cpu_timeout > 0:
|
||||
if cpu_timer is not None:
|
||||
# prevent cpu_timer using c after it goes away
|
||||
cpu_timer.join()
|
||||
|
||||
# Cancel the timer. Small race here (if the timer fires just after the
|
||||
# process finished), but the returncode of our process should still be 0,
|
||||
# process finished), but the return code of our process should still be 0,
|
||||
# and hence we won't interpret the result as a timeout.
|
||||
if test_status[0] is not TIMEOUT:
|
||||
timer.cancel()
|
||||
|
||||
if output == None:
|
||||
if output is None:
|
||||
output = ""
|
||||
output = output.decode(encoding='utf8', errors='replace')
|
||||
|
||||
|
@ -430,7 +424,6 @@ def main():
|
|||
sys.exit(0)
|
||||
|
||||
# Calculate which tests should be run.
|
||||
tests_to_run = []
|
||||
if len(args.tests) == 0 and not os.environ.get('RUN_TESTS_DEFAULT'):
|
||||
tests_to_run = tests
|
||||
args.exclude = args.exclude + args.remove
|
||||
|
@ -440,7 +433,7 @@ def main():
|
|||
if len(bad_names) > 0:
|
||||
parser.error("Unknown test names: %s" % (", ".join(sorted(bad_names))))
|
||||
# Given a list of names return the corresponding set of Test objects.
|
||||
get_tests = lambda x: {t for t in tests if t.name in x}
|
||||
def get_tests(x): return {t for t in tests if t.name in x}
|
||||
# Given a list/set of Tests return a superset that includes all dependencies.
|
||||
def get_deps(x):
|
||||
x.update({t for w in x for t in get_deps(get_tests(w.depends))})
|
||||
|
@ -500,7 +493,7 @@ def main():
|
|||
# Non-blocked but depends on a failed test. Remove it.
|
||||
if (len(real_depends & failed_tests) > 0
|
||||
# --fail-fast triggered, fail all subsequent tests
|
||||
or kill_switch.is_set()):
|
||||
or kill_switch.is_set()):
|
||||
|
||||
wipe_tty_status()
|
||||
print_test_line(t.name, ANSI_YELLOW, SKIPPED, legacy=args.legacy_status)
|
||||
|
@ -516,7 +509,6 @@ def main():
|
|||
print_test_line_start(t.name, args.legacy_status)
|
||||
test_thread.start()
|
||||
current_jobs[t.name] = test_thread
|
||||
popped_test = True
|
||||
del tests_queue[i]
|
||||
break
|
||||
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
import os
|
||||
import copy
|
||||
import heapq
|
||||
import glob
|
||||
import subprocess
|
||||
import itertools
|
||||
import argparse
|
||||
import sys
|
||||
|
@ -26,7 +24,7 @@ REGRESSION_DTD = os.path.join(REGRESSION_DIR, "regression.dtd")
|
|||
class TestSpecParseException(Exception):
|
||||
pass
|
||||
|
||||
class TestEnv():
|
||||
class TestEnv:
|
||||
def __init__(self, pwd):
|
||||
self.pwd = pwd
|
||||
self.cwd = "."
|
||||
|
@ -34,31 +32,29 @@ class TestEnv():
|
|||
self.cpu_timeout = 0
|
||||
self.depends = set()
|
||||
|
||||
class Test():
|
||||
class Test:
|
||||
def __init__(self, name, command, timeout=0, cpu_timeout=0, cwd="", depends=None):
|
||||
self.name = name
|
||||
self.command = command
|
||||
self.timeout = timeout
|
||||
self.cpu_timeout = cpu_timeout
|
||||
self.cwd = cwd
|
||||
|
||||
if depends == None:
|
||||
depends = set([])
|
||||
self.depends = depends
|
||||
self.depends = depends if depends is not None else set([])
|
||||
|
||||
def parse_attributes(tag, env, strict=True):
|
||||
"""Parse attributes such as "timeout" in the given XML tag,
|
||||
updating the given "env" to reflect them."""
|
||||
|
||||
if tag.get("timeout"):
|
||||
try:
|
||||
env.timeout = int(tag.get("timeout"))
|
||||
except:
|
||||
except ValueError:
|
||||
if strict:
|
||||
raise
|
||||
if tag.get("cpu-timeout"):
|
||||
try:
|
||||
env.cpu_timeout = float(tag.get("cpu-timeout"))
|
||||
except:
|
||||
except ValueError:
|
||||
if strict:
|
||||
raise
|
||||
if tag.get("cwd"):
|
||||
|
@ -140,7 +136,7 @@ def find_cycle(keys, depends_on):
|
|||
active.add(n)
|
||||
for c in depends_on(n):
|
||||
x = do_dfs(c)
|
||||
if x != None:
|
||||
if x is not None:
|
||||
return [n] + x
|
||||
active.discard(n)
|
||||
safe.add(n)
|
||||
|
@ -148,8 +144,7 @@ def find_cycle(keys, depends_on):
|
|||
shortest_cycle = None
|
||||
for i in keys:
|
||||
x = dfs(i)
|
||||
if x != None and (shortest_cycle == None
|
||||
or len(x) < len(shortest_cycle)):
|
||||
if x is not None and (shortest_cycle is None or len(x) < len(shortest_cycle)):
|
||||
shortest_cycle = x
|
||||
return shortest_cycle
|
||||
|
||||
|
@ -241,7 +236,7 @@ def process_tests(tests, strict=False):
|
|||
raise TestSpecParseException("Duplicate test name detected: %s" % t.name)
|
||||
for x in itertools.count(2):
|
||||
proposed_name = "%s_%d" % (t.name, x)
|
||||
if not proposed_name in seen_names:
|
||||
if proposed_name not in seen_names:
|
||||
t.name = proposed_name
|
||||
break
|
||||
seen_names.add(t.name)
|
||||
|
@ -253,13 +248,13 @@ def process_tests(tests, strict=False):
|
|||
for test in tests:
|
||||
test_depends = sorted(test.depends)
|
||||
for dependency_name in test_depends:
|
||||
if not dependency_name in valid_names:
|
||||
if dependency_name not in valid_names:
|
||||
if strict:
|
||||
raise TestSpecParseException(
|
||||
"Dependency '%s' invalid." % dependency_name)
|
||||
test.depends.remove(dependency_name)
|
||||
|
||||
# Toposort.
|
||||
# Toposort.
|
||||
test_ordering = {}
|
||||
for (n, t) in enumerate(tests):
|
||||
test_ordering[t.name] = n
|
||||
|
@ -362,4 +357,3 @@ def main():
|
|||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
|
Loading…
Reference in New Issue