diff options
author | Christian Hopps <chopps@labn.net> | 2021-06-12 11:07:24 +0200 |
---|---|---|
committer | Christian Hopps <chopps@labn.net> | 2021-06-16 08:56:15 +0200 |
commit | e58133a78e2d814cb23f9bd850f6551075a831ae (patch) | |
tree | ac2d8ec0a1552c719b44a1e7cd74ca0e86403d88 | |
parent | Merge pull request #8836 from ton31337/fix/generalize_bgp_dest_locks (diff) | |
download | frr-e58133a78e2d814cb23f9bd850f6551075a831ae.tar.xz frr-e58133a78e2d814cb23f9bd850f6551075a831ae.zip |
tests: add valgrind memleaks run options and detection
Signed-off-by: Christian Hopps <chopps@labn.net>
-rw-r--r-- | doc/developer/topotests.rst | 14 | ||||
-rwxr-xr-x | tests/topotests/conftest.py | 60 | ||||
-rw-r--r-- | tests/topotests/lib/topogen.py | 2 | ||||
-rw-r--r-- | tests/topotests/lib/topotest.py | 9 |
4 files changed, 85 insertions, 0 deletions
diff --git a/doc/developer/topotests.rst b/doc/developer/topotests.rst index 8885dcfce..ba03aa904 100644 --- a/doc/developer/topotests.rst +++ b/doc/developer/topotests.rst @@ -312,6 +312,20 @@ Here's an example of launching ``zebra`` and ``bgpd`` inside ``gdb`` on router --gdb-breakpoints=nb_config_diff \ all-protocol-startup +Detecting Memleaks with Valgrind +"""""""""""""""""""""""""""""""" + +Topotest can automatically launch all daemons with ``valgrind`` to check for +memleaks. This is enabled by specifying 1 or 2 CLI arguments. +``--valgrind-memleaks`` will enable general memleak detection, and +``--valgrind-extra`` enables extra functionality including generating a +suppression file. The suppression file ``tools/valgrind.supp`` is used when +memleak detection is enabled. + +.. code:: shell + + pytest --valgrind-memleaks all-protocol-startup + .. _topotests_docker: Running Tests with Docker diff --git a/tests/topotests/conftest.py b/tests/topotests/conftest.py index de5c584e9..e57db7471 100755 --- a/tests/topotests/conftest.py +++ b/tests/topotests/conftest.py @@ -2,8 +2,10 @@ Topotest conftest.py file. """ +import glob import os import pdb +import re import pytest from lib.topogen import get_topogen, diagnose_env @@ -11,6 +13,12 @@ from lib.topotest import json_cmp_result from lib.topotest import g_extra_config as topotest_extra_config from lib.topolog import logger +try: + from _pytest._code.code import ExceptionInfo + leak_check_ok = True +except ImportError: + leak_check_ok = False + def pytest_addoption(parser): """ @@ -67,6 +75,18 @@ def pytest_addoption(parser): ) parser.addoption( + "--valgrind-extra", + action="store_true", + help="Generate suppression file, and enable more precise (slower) valgrind checks", + ) + + parser.addoption( + "--valgrind-memleaks", + action="store_true", + help="Run all daemons under valgrind for memleak detection", + ) + + parser.addoption( "--vtysh", metavar="ROUTER[,ROUTER...]", help="Comma-separated list of routers to spawn vtysh on, or 'all'", @@ -79,6 +99,37 @@ def pytest_addoption(parser): ) +def check_for_memleaks(): + if not topotest_extra_config["valgrind_memleaks"]: + return + + leaks = [] + tgen = get_topogen() + latest = [] + existing = [] + if tgen is not None: + logdir = "/tmp/topotests/{}".format(tgen.modname) + if hasattr(tgen, "valgrind_existing_files"): + existing = tgen.valgrind_existing_files + latest = glob.glob(os.path.join(logdir, "*.valgrind.*")) + + for vfile in latest: + if vfile in existing: + continue + with open(vfile) as vf: + vfcontent = vf.read() + match = re.search(r"ERROR SUMMARY: (\d+) errors", vfcontent) + if match and match.group(1) != "0": + emsg = '{} in {}'.format(match.group(1), vfile) + leaks.append(emsg) + + if leaks: + if leak_check_ok: + pytest.fail("Memleaks found:\n\t" + "\n\t".join(leaks)) + else: + logger.error("Memleaks found:\n\t" + "\n\t".join(leaks)) + + def pytest_runtest_call(): """ This function must be run after setup_module(), it does standarized post @@ -139,6 +190,9 @@ def pytest_configure(config): shell_on_error = config.getoption("--shell-on-error") topotest_extra_config["shell_on_error"] = shell_on_error + topotest_extra_config["valgrind_extra"] = config.getoption("--valgrind-extra") + topotest_extra_config["valgrind_memleaks"] = config.getoption("--valgrind-memleaks") + vtysh = config.getoption("--vtysh") topotest_extra_config["vtysh"] = vtysh.split(",") if vtysh else [] @@ -159,6 +213,12 @@ def pytest_runtest_makereport(item, call): else: pause = False + if call.excinfo is None and call.when == "call": + try: + check_for_memleaks() + except: + call.excinfo = ExceptionInfo() + if call.excinfo is None: error = False else: diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py index 4b0f07eb1..ade593350 100644 --- a/tests/topotests/lib/topogen.py +++ b/tests/topotests/lib/topogen.py @@ -657,6 +657,8 @@ class TopoRouter(TopoGear): # Try to find relevant old logfiles in /tmp and delete them map(os.remove, glob.glob("{}/{}/*.log".format(self.logdir, self.name))) + # Remove old valgrind files + map(os.remove, glob.glob("{}/{}.valgrind.*".format(self.logdir, self.name))) # Remove old core files map(os.remove, glob.glob("{}/{}/*.dmp".format(self.logdir, self.name))) diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index 2a5bd1736..d1f60bfe0 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -1454,6 +1454,8 @@ class Router(Node): gdb_breakpoints = g_extra_config["gdb_breakpoints"] gdb_daemons = g_extra_config["gdb_daemons"] gdb_routers = g_extra_config["gdb_routers"] + valgrind_extra = g_extra_config["valgrind_extra"] + valgrind_memleaks = g_extra_config["valgrind_memleaks"] bundle_data = "" @@ -1503,7 +1505,14 @@ class Router(Node): ) + "/var/run/{}/snmpd.pid -x /etc/frr/agentx".format(self.routertype) else: binary = os.path.join(self.daemondir, daemon) + cmdenv = "ASAN_OPTIONS=log_path={0}.asan".format(daemon) + if valgrind_memleaks: + this_dir = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) + supp_file = os.path.abspath(os.path.join(this_dir, "../../../tools/valgrind.supp")) + cmdenv += " /usr/bin/valgrind --num-callers=50 --log-file={1}/{2}.valgrind.{0}.%p --leak-check=full --suppressions={3}".format(daemon, self.logdir, self.name, supp_file) + if valgrind_extra: + cmdenv += "--gen-suppressions=all --expensive-definedness-checks=yes" cmdopt = "{} --log file:{}.log --log-level debug".format( daemon_opts, daemon ) |