tomato-testing/other/docker/coverage/run_mallocfail
Green Sky b2ae9530a4 Squashed 'external/toxcore/c-toxcore/' changes from e29e185c03..f1df709b87
f1df709b87 feat: add ngc events
1b6c907235 refactor: Make event dispatch ordered by receive time.
b7f9367f6f test: Upgrade cppcheck, fix some warnings.
766e62bc89 chore: Use `pkg_search_module` directly in cmake.
00ff078f91 cleanup: Use target_link_libraries directly in cmake.
c58928cc89 chore: Add `IMPORTED_TARGET` to pkg-config packages.
895a6af122 cleanup: Remove NaCl support.
41dfb1c1c0 fix: unpack enum function names in event impl generator
447666d1a1 chore: Disable targets for cross-compilation.
572924e924 chore: Build a docker image with coverage info in it.
415cb78f5e cleanup: Some portability/warning fixes for Windows builds.
425216d9ec fix: Correct a use-after-free and fix some memory leaks.
4b1cfa3e08 refactor: Change all enum-like `#define` sequences into enums.
d3c2704fa9 chore: Fix make_single_file to support core-only.
0ce46b644e refactor: Change the `TCP_PACKET_*` defines into an enum.
22cd38ad50 adopt event impl generation tool to #2392
f31ea1088a add the event impl generation tool
4e603bb613 refactor: Use `enum-from-int` rule from tokstyle.
19d8f180d6 chore: Update github actions `uses`.
6a895be0c7 test: Make esp32 build actually try to instantiate tox.
65d09c9bfb cleanup: Remove test net support.
REVERT: e29e185c03 feat: add ngc events

git-subtree-dir: external/toxcore/c-toxcore
git-subtree-split: f1df709b8792da4c0e946d826b11df77d565064d
2023-12-27 12:37:22 +01:00

173 lines
5.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""Run a test repeatedly with mallocfail.
Usage: run_mallocfail [--ctest=<cost>] [<exe>...]
This runs the programs with mallocfail until there are no more additional
stack hashes for mallocfail to try out.
Passing "--ctest" additionally runs all the tests with a cost lower than the
given flag value. Cost is measured in seconds of runtime.
You need to build mallocfail (https://github.com/ralight/mallocfail) and install
it to /usr/local/lib/mallocfail. Change _MALLOCFAIL_SO below if you want it
elsewhere.
"""
import glob
import multiprocessing
import os
import shutil
import subprocess
import sys
import tempfile
import time
from typing import Dict
from typing import List
from typing import NoReturn
from typing import Optional
from typing import Tuple
_PRIMER = "./unit_util_test"
_MALLOCFAIL_SO = "/usr/local/lib/mallocfail.so"
_HASHES = "mallocfail_hashes"
_HASHES_PREV = "mallocfail_hashes.prev"
_TIMEOUT = 3.0
_ENV = {
"LD_PRELOAD": _MALLOCFAIL_SO,
"UBSAN_OPTIONS": "color=always,print_stacktrace=1,exitcode=11",
}
def run_mallocfail(tmpdir: str, timeout: float, exe: str, iteration: int,
keep_going: bool) -> bool:
"""Run a program with mallocfail."""
print(f"\x1b[1;33mmallocfail '{exe}' run #{iteration}\x1b[0m")
hashes = os.path.join(tmpdir, _HASHES)
hashes_prev = os.path.join(tmpdir, _HASHES_PREV)
if os.path.exists(hashes):
shutil.copy(hashes, hashes_prev)
try:
proc = subprocess.run([exe], timeout=timeout, env=_ENV, cwd=tmpdir)
except subprocess.TimeoutExpired:
print(f"\x1b[1;34mProgram {exe} timed out\x1b[0m")
return True
finally:
assert os.path.exists(hashes)
if os.path.exists(hashes_prev):
with open(hashes_prev, "r") as prev:
with open(hashes, "r") as cur:
if prev.read() == cur.read():
# Done: no new stack hashes.
return False
if proc.returncode in (0, 1):
# Process exited cleanly (success or failure).
pass
elif proc.returncode == -6:
# Assertion failed.
pass
elif proc.returncode == -14:
print(f"\x1b[0;34mProgram '{exe}' timed out\x1b[0m")
else:
print(
f"\x1b[1;32mProgram '{exe}' failed to handle OOM situation cleanly\x1b[0m"
)
if not keep_going:
raise Exception("Aborting test")
return True
def ctest_costs() -> Dict[str, float]:
with open("Testing/Temporary/CTestCostData.txt", "r") as fh:
costs = {}
for line in fh.readlines():
if line.startswith("---"):
break
prog, _, cost = line.rstrip().split(" ")
costs[prog] = float(cost)
return costs
def find_prog(name: str) -> Tuple[Optional[str], ...]:
def attempt(path: str) -> Optional[str]:
if os.path.exists(path):
return path
return None
return (attempt(f"./unit_{name}_test"),
attempt(f"auto_tests/auto_{name}_test"))
def parse_flags(args: List[str]) -> Tuple[Dict[str, str], List[str]]:
flags: Dict[str, str] = {}
exes: List[str] = []
for arg in args:
if arg.startswith("--"):
flag, value = arg.split("=", 1)
flags[flag] = value
else:
exes.append(arg)
return flags, exes
def loop_mallocfail(tmpdir: str,
timeout: float,
exe: str,
keep_going: bool = False) -> None:
i = 1
while run_mallocfail(tmpdir, timeout, exe, i, keep_going):
i += 1
def isolated_mallocfail(timeout: int, exe: str) -> None:
with tempfile.TemporaryDirectory(prefix="mallocfail") as tmpdir:
print(f"\x1b[1;33mRunning for {exe} in isolated path {tmpdir}\x1b[0m")
os.mkdir(os.path.join(tmpdir, "auto_tests"))
shutil.copy(exe, os.path.join(tmpdir, exe))
shutil.copy(_HASHES, os.path.join(tmpdir, _HASHES))
loop_mallocfail(tmpdir, timeout, exe)
def main(args: List[str]) -> None:
"""Run a program repeatedly under mallocfail."""
if len(args) == 1:
print(f"Usage: {args[0]} <exe>")
sys.exit(1)
flags, exes = parse_flags(args[1:])
timeout = _TIMEOUT
if "--ctest" in flags:
costs = ctest_costs()
max_cost = float(flags["--ctest"])
timeout = max(max_cost + 1, timeout)
exes.extend(prog for test in costs.keys() for prog in find_prog(test)
if costs[test] <= max_cost and prog)
if "--jobs" in flags:
jobs = int(flags["--jobs"])
else:
jobs = 1
# Start by running util_test, which allocates no memory of its own, just
# to prime the mallocfail hashes so it doesn't make global initialisers
# such as llvm_gcov_init fail.
if os.path.exists(_PRIMER):
print(f"\x1b[1;33mPriming hashes with unit_util_test\x1b[0m")
loop_mallocfail(".", timeout, _PRIMER, keep_going=True)
print(f"\x1b[1;33m--------------------------------\x1b[0m")
print(f"\x1b[1;33mStarting mallocfail for {len(exes)} programs:\x1b[0m")
print(f"\x1b[1;33m{exes}\x1b[0m")
print(f"\x1b[1;33m--------------------------------\x1b[0m")
time.sleep(1)
with multiprocessing.Pool(jobs) as p:
done = tuple(
p.starmap(isolated_mallocfail, ((timeout, exe) for exe in exes)))
print(f"\x1b[1;32mCompleted {len(done)} programs\x1b[0m")
if __name__ == "__main__":
main(sys.argv)