tint2/test/regression.py

385 lines
11 KiB
Python
Raw Normal View History

2017-08-30 13:13:25 +00:00
#!/usr/bin/env python2
2017-08-30 13:29:52 +00:00
from __future__ import print_function
import __builtin__
2017-08-30 13:13:25 +00:00
import sys
reload(sys)
sys.setdefaultencoding('utf8')
2017-08-30 14:00:01 +00:00
import argparse
2017-08-30 13:13:25 +00:00
import datetime
import os
import signal
import subprocess
import time
display = "99"
devnull = open(os.devnull, "r+")
ok = ":white_check_mark:"
warning = ":warning:"
error = ":negative_squared_cross_mark:"
2017-08-30 13:30:06 +00:00
stress_duration = 10
2017-08-31 09:42:19 +00:00
repeats = 1
2017-08-30 13:13:25 +00:00
2017-08-30 13:29:52 +00:00
def print(*args, **kwargs):
2017-08-30 20:01:46 +00:00
if "end" not in kwargs:
kwargs["end"] = ""
r = __builtin__.print(*args, **kwargs)
__builtin__.print(" ")
else:
r = __builtin__.print(*args, **kwargs)
__builtin__.print("\n", end="")
2017-08-30 13:29:52 +00:00
return r
2017-09-28 13:08:26 +00:00
def print_err(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
2017-08-30 13:13:25 +00:00
def run(cmd, output=False):
return subprocess.Popen(cmd,
stdin=devnull,
stdout=devnull if not output else subprocess.PIPE,
stderr=devnull if not output else subprocess.STDOUT,
shell=isinstance(cmd, basestring),
close_fds=True,
preexec_fn=os.setsid)
def stop(p):
os.killpg(os.getpgid(p.pid), signal.SIGTERM)
def sleep(n):
while n > 0:
sys.stderr.write(".")
sys.stderr.flush()
time.sleep(1)
n -= 1
2017-09-28 13:08:26 +00:00
def install_deps_ubuntu():
2017-09-28 13:43:12 +00:00
print_err("Installing dependencies...")
2017-09-28 13:15:00 +00:00
p = run(["sudo", "bash", "-c", "apt-get update; apt-get -y build-dep tint2; apt-get install -y git xvfb xsettingsd openbox compton x11-utils gnome-calculator"])
2017-09-28 13:08:26 +00:00
out, _ = p.communicate()
if p.returncode != 0:
print_err("Process exited with code:", p.returncode, "and output:", out)
raise RuntimeError("install_deps() failed!")
2017-08-30 13:13:25 +00:00
def start_xvfb():
stop_xvfb()
xvfb = run(["Xvfb", ":{0}".format(display), "-screen", "0", "1280x720x24", "-nolisten", "tcp", "-dpi", "96"])
if xvfb.poll() != None:
raise RuntimeError("Xvfb failed to start")
os.environ["DISPLAY"] = ":{0}".format(display)
return xvfb
def stop_xvfb():
run("kill $(netstat -ap 2>/dev/null | grep X{0} | grep LISTENING | grep -o '[0-9]*/Xvfb' | head -n 1 | cut -d / -f 1) 1>/dev/null 2>/dev/null ".format(display)).wait()
def start_xsettings():
return run(["xsettingsd", "-c", "./configs/xsettingsd.conf"])
def start_wm():
return run(["openbox", "--replace", "--config-file", "./configs/openbox.xml"])
def start_compositor():
return run(["compton", "--config", "./configs/compton.conf"])
def start_stressors():
stressors = []
stressors.append(run(["./workspaces-stress.sh"]))
return stressors
def stop_stressors(stressors):
for s in stressors:
stop(s)
def compute_min_med_fps(out):
samples = []
for line in out.split("\n"):
if "fps = " in line:
fps = float(line.split("fps = ", 1)[-1].split(" ")[0])
2017-08-30 14:10:07 +00:00
if fps > 0:
samples.append(fps)
2017-08-30 13:13:25 +00:00
samples.sort()
return min(samples), samples[len(samples)/2]
def get_mem_usage(pid):
value = None
with open("/proc/{0}/status".format(pid)) as f:
for line in f:
if line.startswith("VmRSS:"):
rss = line.split(":", 1)[-1].strip()
value, multiplier = rss.split(" ")
value = float(value)
if multiplier == "kB":
value *= 1024
else:
raise RuntimeError("Could not parse /proc/[pid]/status")
if not value:
2017-09-28 17:39:30 +00:00
value = 0
2017-09-28 13:59:29 +00:00
result = value * 1.0e-6
p = run("./meminfo.py --detailed --private $(pidof tint2)", output=True)
detailed, _ = p.communicate()
return result, detailed
2017-08-30 13:13:25 +00:00
def find_asan_leaks(out):
traces = []
trace = None
for line in out.split("\n"):
line = line.strip()
if " leak of " in line and " allocated from:" in line:
trace = []
if trace != None:
if line.startswith("#"):
trace.append(line)
else:
if any([ "tint2" in frame for frame in trace ]):
traces.append(trace)
trace = None
return traces
2017-09-28 17:18:34 +00:00
def test(tint2path, config, use_asan):
2017-08-30 13:13:25 +00:00
start_xvfb()
sleep(1)
start_xsettings()
start_wm()
sleep(1)
os.environ["DEBUG_FPS"] = "1"
2017-11-05 18:51:09 +00:00
os.environ["ASAN_OPTIONS"] = "detect_leaks=1:exitcode=0"
2017-09-28 13:19:51 +00:00
tint2 = run([tint2path, "-c", config], True)
2017-08-30 13:13:25 +00:00
if tint2.poll() != None:
raise RuntimeError("tint2 failed to start")
sleep(1)
# Handle late compositor start
compton = start_compositor()
sleep(2)
# Stress test with compositor on
stressors = start_stressors()
sleep(stress_duration)
stop_stressors(stressors)
# Handle compositor stopping
stop(compton)
# Stress test with compositor off
stressors = start_stressors()
sleep(stress_duration)
stop_stressors(stressors)
# Handle WM restart
start_wm()
# Stress test with new WM
stressors = start_stressors()
sleep(stress_duration)
stop_stressors(stressors)
# Collect info
2017-09-28 13:59:29 +00:00
mem, mem_detail = get_mem_usage(tint2.pid)
2017-08-30 13:13:25 +00:00
stop(tint2)
out, _ = tint2.communicate()
exitcode = tint2.returncode
2017-09-28 13:39:10 +00:00
if exitcode != 0 and exitcode != 23:
2017-08-30 13:29:52 +00:00
print("tint2 crashed with exit code {0}!".format(exitcode))
print("Output:")
2017-08-31 09:42:19 +00:00
print("```\n" + out.strip() + "\n```")
2017-08-30 13:13:25 +00:00
return
min_fps, med_fps = compute_min_med_fps(out)
leaks = find_asan_leaks(out)
sys.stderr.write("\n")
2017-09-28 17:18:34 +00:00
if use_asan:
2017-09-28 18:06:39 +00:00
mem_status = ok
2017-09-28 17:18:34 +00:00
else:
mem_status = ok if mem < 20 else warning if mem < 40 else error
2017-08-30 13:29:52 +00:00
print("Memory usage: %.1f %s %s" % (mem, "MB", mem_status))
2017-08-30 13:13:25 +00:00
leak_status = ok if not leaks else error
2017-08-30 13:29:52 +00:00
print("Memory leak count:", len(leaks), leak_status)
2017-08-30 13:13:25 +00:00
for leak in leaks:
2017-08-30 13:29:52 +00:00
print("Memory leak:")
2017-08-30 13:13:25 +00:00
for line in leak:
2017-08-30 13:29:52 +00:00
print(line)
2017-09-28 17:18:34 +00:00
if mem_status != ok:
print("Memory usage details:")
print("```\n" + mem_detail.strip() + "\n```")
if use_asan:
2017-09-28 18:06:39 +00:00
fps_status = ok
2017-09-28 17:18:34 +00:00
else:
2017-11-05 18:54:17 +00:00
fps_status = ok if min_fps > 30 else warning if min_fps > 20 else error
2017-08-30 13:29:52 +00:00
print("FPS:", "min:", min_fps, "median:", med_fps, fps_status)
2017-08-30 13:13:25 +00:00
if mem_status != ok or leak_status != ok or fps_status != ok:
2017-08-30 13:29:52 +00:00
print("Output:")
2017-08-31 09:42:19 +00:00
print("```\n" + out.strip() + "\n```")
2017-08-30 13:13:25 +00:00
stop_xvfb()
2017-08-30 14:00:01 +00:00
def show_timestamp():
2017-08-30 13:13:25 +00:00
utc_datetime = datetime.datetime.utcnow()
2017-08-30 13:29:52 +00:00
print("Last updated:", utc_datetime.strftime("%Y-%m-%d %H:%M UTC"))
2017-08-30 14:00:01 +00:00
def show_git_info(src_dir):
2017-08-30 14:55:40 +00:00
out, _ = run("cd {0}; git show -s '--format=[%ci] %h %s %d'".format(src_dir), True).communicate()
2017-08-30 13:29:52 +00:00
print("Last commit:", out.strip())
2017-08-30 14:00:01 +00:00
diff, _ = run("cd {0}; git diff".format(src_dir), True).communicate()
2017-08-30 13:13:25 +00:00
diff = diff.strip()
2017-08-30 14:00:01 +00:00
diff_staged, _ = run("cd {0}; git diff --staged".format(src_dir), True).communicate()
2017-08-30 13:13:25 +00:00
diff_staged = diff_staged.strip()
if diff or diff_staged:
2017-08-30 13:29:52 +00:00
print("Repository not clean", warning)
2017-08-30 13:13:25 +00:00
if diff:
2017-08-30 13:29:52 +00:00
print("Diff:")
2017-08-30 20:01:46 +00:00
print("```\n" + diff + "\n```")
2017-08-30 13:13:25 +00:00
if diff_staged:
2017-08-30 13:29:52 +00:00
print("Diff staged:")
2017-08-30 20:01:46 +00:00
print("```\n" + diff_staged + "\n```")
2017-08-30 14:00:01 +00:00
def show_system_info():
2017-08-30 13:39:34 +00:00
out, _ = run("lsb_release -sd", True).communicate()
2017-08-30 20:01:46 +00:00
out = out.strip()
2017-08-30 13:39:10 +00:00
print("System:", out)
2017-09-28 17:10:10 +00:00
out, _ = run("echo \"$(cat /proc/cpuinfo | grep 'model name' | head -n1 | cut -d ':' -f2) with $(cat /proc/cpuinfo | grep processor | wc -l) cores\"", True).communicate()
2017-08-30 20:01:46 +00:00
out = out.strip()
2017-08-30 13:39:10 +00:00
print("Hardware:", out)
2017-08-30 15:42:02 +00:00
out, _ = run("cc --version | head -n1", True).communicate()
2017-08-30 20:01:46 +00:00
out = out.strip()
2017-08-30 15:42:02 +00:00
print("Compiler:", out)
2017-08-30 14:00:01 +00:00
2017-09-28 17:18:34 +00:00
def compile_and_report(src_dir, use_asan):
2017-09-28 13:43:12 +00:00
print_err("Compiling...")
2017-08-30 13:29:52 +00:00
print("# Compilation")
2017-09-28 17:18:34 +00:00
if use_asan:
cmake_flags = "-DCMAKE_BUILD_TYPE=Debug -DENABLE_ASAN=ON '-DCMAKE_CXX_FLAGS_DEBUG=-O0 -g3 -gdwarf-2 -fsanitize=address -fno-common -fno-omit-frame-pointer -rdynamic -Wshadow' '-DCMAKE_EXE_LINKER_FLAGS=-O0 -g3 -gdwarf-2 -fsanitize=address -fno-common -fno-omit-frame-pointer -rdynamic -fuse-ld=gold'"
else:
cmake_flags = ""
print("Flags:", "`" + cmake_flags + "`")
2017-08-30 13:13:25 +00:00
start = time.time()
2017-08-30 14:00:01 +00:00
c = run("rm -rf build; mkdir build; cd build; cmake {0} {1} ; make -j7".format(cmake_flags, src_dir), True)
2017-08-30 13:13:25 +00:00
out, _ = c.communicate()
duration = time.time() - start
if c.returncode != 0:
2017-08-30 13:29:52 +00:00
print("Status: Failed!", error)
print("Output:")
2017-08-31 09:42:19 +00:00
print("```\n" + out.strip() + "\n```")
2017-08-30 15:27:21 +00:00
raise RuntimeError("compilation failed")
2017-08-30 13:13:25 +00:00
if "warning:" in out:
2017-08-30 13:29:52 +00:00
print("Status: Succeeded with warnings!", warning)
print("Warnings:")
2017-08-30 15:27:21 +00:00
print("```", end="")
2017-08-30 13:13:25 +00:00
for line in out.split("\n"):
if "warning:" in line:
2017-08-30 15:27:21 +00:00
print(line, end="")
print("```", end="")
2017-08-30 13:13:25 +00:00
else:
2017-08-30 13:29:52 +00:00
print("Status: Succeeded in %.1f seconds" % (duration,), ok)
2017-08-30 14:00:01 +00:00
def compile_remotely_and_report(host):
print_err("Compiling on {0}...".format(host))
start = time.time()
c = run("ssh worker@{0} 'cd tint2 && git pull && mkdir -p build && rm -rf build && mkdir -p build && cd build && cmake .. && make && ./tint2 --version'".format(host), True)
out, _ = c.communicate()
duration = time.time() - start
if c.returncode != 0:
print("Status: Failed!", error)
print("Output:")
print("```\n" + out.strip() + "\n```")
if "warning:" in out:
print("Status: Succeeded with warnings!", warning)
print("Warnings:")
print("```", end="")
for line in out.split("\n"):
if "warning:" in line:
print(line, end="")
print("```", end="")
else:
print("Status: Succeeded in %.1f seconds" % (duration,), ok)
2017-09-28 17:18:34 +00:00
def run_test(config, index, use_asan):
2017-09-28 13:43:12 +00:00
print_err("Running test", index, "for config", config)
2017-09-28 17:18:34 +00:00
print("# Test", index, "(ASAN on)" if use_asan else "")
2017-08-30 14:00:01 +00:00
print("Config: [{0}]({1})".format(config.split("/")[-1].replace(".tint2rc", ""), "https://gitlab.com/o9000/tint2/blob/master/test/" + config))
2017-08-30 20:01:46 +00:00
for i in range(repeats):
2017-09-28 17:18:34 +00:00
test("./build/tint2", config, use_asan)
2017-08-30 14:00:01 +00:00
2017-09-28 17:18:34 +00:00
def run_tests(use_asan):
2017-09-28 13:43:12 +00:00
print_err("Running tests...")
2017-08-30 13:13:25 +00:00
configs = []
2017-09-29 08:32:24 +00:00
configs += ["../themes/" + s for s in os.listdir("../themes") if s.endswith("tint2rc")]
2017-08-30 13:13:25 +00:00
index = 0
for config in configs:
index += 1
2017-09-28 17:18:34 +00:00
run_test(config, index, use_asan)
2017-08-30 20:01:46 +00:00
print("")
2017-08-30 14:00:01 +00:00
2017-08-30 14:25:47 +00:00
def get_default_src_dir():
return os.path.realpath(os.path.dirname(os.path.realpath(__file__)) + "/../")
2017-08-30 14:31:01 +00:00
def check_busy():
2017-09-28 13:43:12 +00:00
print_err("Checking if system is busy...")
2017-08-30 14:40:59 +00:00
out, _ = run("top -bn5 | grep 'Cpu(s)' | grep -o '[0-9\.]* id' | cut -d ' ' -f 1", True).communicate()
load_samples = []
2017-08-30 14:31:01 +00:00
for line in out.split("\n"):
2017-08-30 14:40:59 +00:00
line = line.strip()
if line:
load_samples.append(100. - float(line))
load_samples.sort()
load = load_samples[len(load_samples)/2]
2017-08-30 14:31:01 +00:00
if load > 10.0:
2017-08-30 14:40:59 +00:00
raise RuntimeError("The system appears busy. Load: %f.1%%." % (load,))
2017-08-30 14:31:01 +00:00
2017-08-30 15:27:21 +00:00
def checkout(version):
2017-09-28 13:43:12 +00:00
print_err("Checking out tint2 version", version, "...")
2017-08-30 15:27:21 +00:00
p = run("rm -rf tmpclone; git clone https://gitlab.com/o9000/tint2.git tmpclone; cd tmpclone; git checkout {0}".format(version), True)
out, _ = p.communicate()
if p.returncode != 0:
2017-09-28 13:08:26 +00:00
print_err(out)
2017-08-30 15:27:21 +00:00
raise RuntimeError("git clone failed!")
2017-08-30 14:00:01 +00:00
def main():
parser = argparse.ArgumentParser()
2017-08-30 14:25:47 +00:00
parser.add_argument("--src_dir", default=get_default_src_dir())
2017-08-30 15:27:21 +00:00
parser.add_argument("--for_version", default="HEAD")
2017-09-28 13:08:26 +00:00
parser.add_argument("--install_deps", dest="install_deps", action="store_true")
parser.set_defaults(install_deps=False)
2017-08-30 14:00:01 +00:00
args = parser.parse_args()
2017-09-28 13:08:26 +00:00
if args.install_deps:
install_deps_ubuntu()
2017-09-28 13:39:10 +00:00
return
2017-08-30 15:27:21 +00:00
if args.for_version != "HEAD":
checkout(args.for_version)
args.src_dir = "./tmpclone"
args.src_dir = os.path.realpath(args.src_dir)
2017-08-30 14:31:01 +00:00
stop_xvfb()
2017-08-30 15:38:13 +00:00
check_busy()
2017-08-30 14:00:01 +00:00
show_timestamp()
show_git_info(args.src_dir)
show_system_info()
compile_remotely_and_report("freebsd")
compile_remotely_and_report("openbsd")
2017-09-28 17:18:34 +00:00
for use_asan in [True, False]:
compile_and_report(args.src_dir, use_asan)
run_tests(use_asan)
2017-08-30 14:00:01 +00:00
2017-08-30 13:13:25 +00:00
if __name__ == "__main__":
2017-08-30 15:38:13 +00:00
sys.stdout = os.fdopen(sys.stdout.fileno(), "w", 0)
2017-08-30 13:13:25 +00:00
main()