tests: switch to munet

Signed-off-by: Christian Hopps <chopps@labn.net>
This commit is contained in:
Christian Hopps 2023-03-24 13:06:38 +00:00 committed by Christian Hopps
parent 352ddc72b7
commit 60e037780e
8 changed files with 264 additions and 1448 deletions

View File

@ -7,21 +7,23 @@ import glob
import os
import pdb
import re
import resource
import subprocess
import sys
import time
import resource
import pytest
import lib.fixtures
from lib import topolog
from lib.micronet import Commander, proc_error
from lib.micronet_cli import cli
from lib.micronet_compat import Mininet, cleanup_current, cleanup_previous
from lib.micronet_compat import Mininet
from lib.topogen import diagnose_env, get_topogen
from lib.topolog import logger
from lib.topotest import g_extra_config as topotest_extra_config
from lib.topotest import json_cmp_result
from munet.base import Commander, proc_error
from munet.cleanup import cleanup_current, cleanup_previous
from munet import cli
def pytest_addoption(parser):
@ -501,7 +503,7 @@ def pytest_runtest_makereport(item, call):
# Really would like something better than using this global here.
# Not all tests use topogen though so get_topogen() won't work.
if Mininet.g_mnet_inst:
cli(Mininet.g_mnet_inst, title=title, background=False)
cli.cli(Mininet.g_mnet_inst, title=title, background=False)
else:
logger.error("Could not launch CLI b/c no mininet exists yet")
@ -515,7 +517,7 @@ def pytest_runtest_makereport(item, call):
user = user.strip()
if user == "cli":
cli(Mininet.g_mnet_inst)
cli.cli(Mininet.g_mnet_inst)
elif user == "pdb":
pdb.set_trace() # pylint: disable=forgotten-debug-statement
elif user:

View File

@ -21,7 +21,8 @@ try:
import grpc
import grpc_tools
from micronet import commander
sys.path.append(os.path.dirname(CWD))
from munet.base import commander
commander.cmd_raises(f"cp {CWD}/../../../grpc/frr-northbound.proto .")
commander.cmd_raises(

File diff suppressed because it is too large Load Diff

View File

@ -1,306 +0,0 @@
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
# SPDX-License-Identifier: GPL-2.0-or-later
#
# July 24 2021, Christian Hopps <chopps@labn.net>
#
# Copyright (c) 2021, LabN Consulting, L.L.C.
#
import argparse
import logging
import os
import pty
import re
import readline
import select
import socket
import subprocess
import sys
import tempfile
import termios
import tty
ENDMARKER = b"\x00END\x00"
def lineiter(sock):
s = ""
while True:
sb = sock.recv(256)
if not sb:
return
s += sb.decode("utf-8")
i = s.find("\n")
if i != -1:
yield s[:i]
s = s[i + 1 :]
def spawn(unet, host, cmd):
if sys.stdin.isatty():
old_tty = termios.tcgetattr(sys.stdin)
tty.setraw(sys.stdin.fileno())
try:
master_fd, slave_fd = pty.openpty()
# use os.setsid() make it run in a new process group, or bash job
# control will not be enabled
p = unet.hosts[host].popen(
cmd,
preexec_fn=os.setsid,
stdin=slave_fd,
stdout=slave_fd,
stderr=slave_fd,
universal_newlines=True,
)
while p.poll() is None:
r, w, e = select.select([sys.stdin, master_fd], [], [], 0.25)
if sys.stdin in r:
d = os.read(sys.stdin.fileno(), 10240)
os.write(master_fd, d)
elif master_fd in r:
o = os.read(master_fd, 10240)
if o:
os.write(sys.stdout.fileno(), o)
finally:
# restore tty settings back
if sys.stdin.isatty():
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
def doline(unet, line, writef):
def host_cmd_split(unet, cmd):
csplit = cmd.split()
for i, e in enumerate(csplit):
if e not in unet.hosts:
break
hosts = csplit[:i]
if not hosts:
hosts = sorted(unet.hosts.keys())
cmd = " ".join(csplit[i:])
return hosts, cmd
line = line.strip()
m = re.match(r"^(\S+)(?:\s+(.*))?$", line)
if not m:
return True
cmd = m.group(1)
oargs = m.group(2) if m.group(2) else ""
if cmd == "q" or cmd == "quit":
return False
if cmd == "hosts":
writef("%% hosts: %s\n" % " ".join(sorted(unet.hosts.keys())))
elif cmd in ["term", "vtysh", "xterm"]:
args = oargs.split()
if not args or (len(args) == 1 and args[0] == "*"):
args = sorted(unet.hosts.keys())
hosts = [unet.hosts[x] for x in args if x in unet.hosts]
for host in hosts:
if cmd == "t" or cmd == "term":
host.run_in_window("bash", title="sh-%s" % host)
elif cmd == "v" or cmd == "vtysh":
host.run_in_window("vtysh", title="vt-%s" % host)
elif cmd == "x" or cmd == "xterm":
host.run_in_window("bash", title="sh-%s" % host, forcex=True)
elif cmd == "sh":
hosts, cmd = host_cmd_split(unet, oargs)
for host in hosts:
if sys.stdin.isatty():
spawn(unet, host, cmd)
else:
if len(hosts) > 1:
writef("------ Host: %s ------\n" % host)
output = unet.hosts[host].cmd_legacy(cmd)
writef(output)
if len(hosts) > 1:
writef("------- End: %s ------\n" % host)
writef("\n")
elif cmd == "h" or cmd == "help":
writef(
"""
Commands:
help :: this help
sh [hosts] <shell-command> :: execute <shell-command> on <host>
term [hosts] :: open shell terminals for hosts
vtysh [hosts] :: open vtysh terminals for hosts
[hosts] <vtysh-command> :: execute vtysh-command on hosts\n\n"""
)
else:
hosts, cmd = host_cmd_split(unet, line)
for host in hosts:
if len(hosts) > 1:
writef("------ Host: %s ------\n" % host)
output = unet.hosts[host].cmd_legacy('vtysh -c "{}"'.format(cmd))
writef(output)
if len(hosts) > 1:
writef("------- End: %s ------\n" % host)
writef("\n")
return True
def cli_server_setup(unet):
sockdir = tempfile.mkdtemp("-sockdir", "pyt")
sockpath = os.path.join(sockdir, "cli-server.sock")
try:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(10)
sock.bind(sockpath)
sock.listen(1)
return sock, sockdir, sockpath
except Exception:
unet.cmd_status("rm -rf " + sockdir)
raise
def cli_server(unet, server_sock):
sock, addr = server_sock.accept()
# Go into full non-blocking mode now
sock.settimeout(None)
for line in lineiter(sock):
line = line.strip()
def writef(x):
xb = x.encode("utf-8")
sock.send(xb)
if not doline(unet, line, writef):
return
sock.send(ENDMARKER)
def cli_client(sockpath, prompt="unet> "):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(10)
sock.connect(sockpath)
# Go into full non-blocking mode now
sock.settimeout(None)
print("\n--- Micronet CLI Starting ---\n\n")
while True:
if sys.version_info[0] == 2:
line = raw_input(prompt) # pylint: disable=E0602
else:
line = input(prompt)
if line is None:
return
# Need to put \n back
line += "\n"
# Send the CLI command
sock.send(line.encode("utf-8"))
def bendswith(b, sentinel):
slen = len(sentinel)
return len(b) >= slen and b[-slen:] == sentinel
# Collect the output
rb = b""
while not bendswith(rb, ENDMARKER):
lb = sock.recv(4096)
if not lb:
return
rb += lb
# Remove the marker
rb = rb[: -len(ENDMARKER)]
# Write the output
sys.stdout.write(rb.decode("utf-8"))
def local_cli(unet, outf, prompt="unet> "):
print("\n--- Micronet CLI Starting ---\n\n")
while True:
if sys.version_info[0] == 2:
line = raw_input(prompt) # pylint: disable=E0602
else:
line = input(prompt)
if line is None:
return
if not doline(unet, line, outf.write):
return
def cli(
unet,
histfile=None,
sockpath=None,
force_window=False,
title=None,
prompt=None,
background=True,
):
logger = logging.getLogger("cli-client")
if prompt is None:
prompt = "unet> "
if force_window or not sys.stdin.isatty():
# Run CLI in another window b/c we have no tty.
sock, sockdir, sockpath = cli_server_setup(unet)
python_path = unet.get_exec_path(["python3", "python"])
us = os.path.realpath(__file__)
cmd = "{} {}".format(python_path, us)
if histfile:
cmd += " --histfile=" + histfile
if title:
cmd += " --prompt={}".format(title)
cmd += " " + sockpath
try:
unet.run_in_window(cmd, new_window=True, title=title, background=background)
return cli_server(unet, sock)
finally:
unet.cmd_status("rm -rf " + sockdir)
if not unet:
logger.debug("client-cli using sockpath %s", sockpath)
try:
if histfile is None:
histfile = os.path.expanduser("~/.micronet-history.txt")
if not os.path.exists(histfile):
if unet:
unet.cmd("touch " + histfile)
else:
subprocess.run("touch " + histfile)
if histfile:
readline.read_history_file(histfile)
except Exception:
pass
try:
if sockpath:
cli_client(sockpath, prompt=prompt)
else:
local_cli(unet, sys.stdout, prompt=prompt)
except EOFError:
pass
except Exception as ex:
logger.critical("cli: got exception: %s", ex, exc_info=True)
raise
finally:
readline.write_history_file(histfile)
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG, filename="/tmp/topotests/cli-client.log")
logger = logging.getLogger("cli-client")
logger.info("Start logging cli-client")
parser = argparse.ArgumentParser()
parser.add_argument("--histfile", help="file to user for history")
parser.add_argument("--prompt-text", help="prompt string to use")
parser.add_argument("socket", help="path to pair of sockets to communicate over")
args = parser.parse_args()
prompt = "{}> ".format(args.prompt_text) if args.prompt_text else "unet> "
cli(None, args.histfile, args.socket, prompt=prompt)

View File

@ -3,140 +3,43 @@
#
# July 11 2021, Christian Hopps <chopps@labn.net>
#
# Copyright (c) 2021, LabN Consulting, L.L.C
# Copyright (c) 2021-2023, LabN Consulting, L.L.C
#
import glob
import logging
import ipaddress
import os
import signal
import time
from lib.micronet import LinuxNamespace, Micronet
from lib.micronet_cli import cli
def get_pids_with_env(has_var, has_val=None):
result = {}
for pidenv in glob.iglob("/proc/*/environ"):
pid = pidenv.split("/")[2]
try:
with open(pidenv, "rb") as rfb:
envlist = [
x.decode("utf-8").split("=", 1) for x in rfb.read().split(b"\0")
]
envlist = [[x[0], ""] if len(x) == 1 else x for x in envlist]
envdict = dict(envlist)
if has_var not in envdict:
continue
if has_val is None:
result[pid] = envdict
elif envdict[has_var] == str(has_val):
result[pid] = envdict
except Exception:
# E.g., process exited and files are gone
pass
return result
def _kill_piddict(pids_by_upid, sig):
for upid, pids in pids_by_upid:
logging.info(
"Sending %s to (%s) of micronet pid %s", sig, ", ".join(pids), upid
)
for pid in pids:
try:
os.kill(int(pid), sig)
except Exception:
pass
def _get_our_pids():
ourpid = str(os.getpid())
piddict = get_pids_with_env("MICRONET_PID", ourpid)
pids = [x for x in piddict if x != ourpid]
if pids:
return {ourpid: pids}
return {}
def _get_other_pids():
piddict = get_pids_with_env("MICRONET_PID")
unet_pids = {d["MICRONET_PID"] for d in piddict.values()}
pids_by_upid = {p: set() for p in unet_pids}
for pid, envdict in piddict.items():
pids_by_upid[envdict["MICRONET_PID"]].add(pid)
# Filter out any child pid sets whos micronet pid is still running
return {x: y for x, y in pids_by_upid.items() if x not in y}
def _get_pids_by_upid(ours):
if ours:
return _get_our_pids()
return _get_other_pids()
def _cleanup_pids(ours):
pids_by_upid = _get_pids_by_upid(ours).items()
if not pids_by_upid:
return
_kill_piddict(pids_by_upid, signal.SIGTERM)
# Give them 5 second to exit cleanly
logging.info("Waiting up to 5s to allow for clean exit of abandon'd pids")
for _ in range(0, 5):
pids_by_upid = _get_pids_by_upid(ours).items()
if not pids_by_upid:
return
time.sleep(1)
pids_by_upid = _get_pids_by_upid(ours).items()
_kill_piddict(pids_by_upid, signal.SIGKILL)
def cleanup_current():
"""Attempt to cleanup preview runs.
Currently this only scans for old processes.
"""
logging.info("reaping current micronet processes")
_cleanup_pids(True)
def cleanup_previous():
"""Attempt to cleanup preview runs.
Currently this only scans for old processes.
"""
logging.info("reaping past micronet processes")
_cleanup_pids(False)
from munet import cli
from munet.base import BaseMunet, LinuxNamespace
class Node(LinuxNamespace):
"""Node (mininet compat)."""
def __init__(self, name, **kwargs):
"""
Create a Node.
"""
self.params = kwargs
def __init__(self, name, rundir=None, **kwargs):
nkwargs = {}
if "unet" in kwargs:
nkwargs["unet"] = kwargs["unet"]
if "private_mounts" in kwargs:
private_mounts = kwargs["private_mounts"]
else:
private_mounts = kwargs.get("privateDirs", [])
nkwargs["private_mounts"] = kwargs["private_mounts"]
logger = kwargs.get("logger")
# This is expected by newer munet CLI code
self.config_dirname = ""
self.config = {"kind": "frr"}
self.mgmt_ip = None
self.mgmt_ip6 = None
super(Node, self).__init__(name, logger=logger, private_mounts=private_mounts)
super().__init__(name, **nkwargs)
self.rundir = self.unet.rundir.joinpath(self.name)
def cmd(self, cmd, **kwargs):
"""Execute a command, joins stdout, stderr, ignores exit status."""
return super(Node, self).cmd_legacy(cmd, **kwargs)
def config(self, lo="up", **params):
def config_host(self, lo="up", **params):
"""Called by Micronet when topology is built (but not started)."""
# mininet brings up loopback here.
del params
@ -148,20 +51,76 @@ class Node(LinuxNamespace):
def terminate(self):
return
def add_vlan(self, vlanname, linkiface, vlanid):
self.logger.debug("Adding VLAN interface: %s (%s)", vlanname, vlanid)
ip_path = self.get_exec_path("ip")
assert ip_path, "XXX missing ip command!"
self.cmd_raises(
[
ip_path,
"link",
"add",
"link",
linkiface,
"name",
vlanname,
"type",
"vlan",
"id",
vlanid,
]
)
self.cmd_raises([ip_path, "link", "set", "dev", vlanname, "up"])
def add_loop(self, loopname):
self.logger.debug("Adding Linux iface: %s", loopname)
ip_path = self.get_exec_path("ip")
assert ip_path, "XXX missing ip command!"
self.cmd_raises([ip_path, "link", "add", loopname, "type", "dummy"])
self.cmd_raises([ip_path, "link", "set", "dev", loopname, "up"])
def add_l3vrf(self, vrfname, tableid):
self.logger.debug("Adding Linux VRF: %s", vrfname)
ip_path = self.get_exec_path("ip")
assert ip_path, "XXX missing ip command!"
self.cmd_raises(
[ip_path, "link", "add", vrfname, "type", "vrf", "table", tableid]
)
self.cmd_raises([ip_path, "link", "set", "dev", vrfname, "up"])
def del_iface(self, iface):
self.logger.debug("Removing Linux Iface: %s", iface)
ip_path = self.get_exec_path("ip")
assert ip_path, "XXX missing ip command!"
self.cmd_raises([ip_path, "link", "del", iface])
def attach_iface_to_l3vrf(self, ifacename, vrfname):
self.logger.debug("Attaching Iface %s to Linux VRF %s", ifacename, vrfname)
ip_path = self.get_exec_path("ip")
assert ip_path, "XXX missing ip command!"
if vrfname:
self.cmd_raises(
[ip_path, "link", "set", "dev", ifacename, "master", vrfname]
)
else:
self.cmd_raises([ip_path, "link", "set", "dev", ifacename, "nomaster"])
set_cwd = LinuxNamespace.set_ns_cwd
class Topo(object): # pylint: disable=R0205
def __init__(self, *args, **kwargs):
raise Exception("Remove Me")
class Mininet(Micronet):
class Mininet(BaseMunet):
"""
Mininet using Micronet.
"""
g_mnet_inst = None
def __init__(self):
def __init__(self, rundir=None):
"""
Create a Micronet.
"""
@ -179,7 +138,146 @@ class Mininet(Micronet):
# to set permissions to root:frr 770 to make this unneeded in that case
# os.umask(0)
super(Mininet, self).__init__()
super(Mininet, self).__init__(pid=False, rundir=rundir)
# From munet/munet/native.py
with open(os.path.join(self.rundir, "nspid"), "w", encoding="ascii") as f:
f.write(f"{self.pid}\n")
with open(os.path.join(self.rundir, "nspids"), "w", encoding="ascii") as f:
f.write(f'{" ".join([str(x) for x in self.pids])}\n')
hosts_file = os.path.join(self.rundir, "hosts.txt")
with open(hosts_file, "w", encoding="ascii") as hf:
hf.write(
f"""127.0.0.1\tlocalhost {self.name}
::1\tip6-localhost ip6-loopback
fe00::0\tip6-localnet
ff00::0\tip6-mcastprefix
ff02::1\tip6-allnodes
ff02::2\tip6-allrouters
"""
)
self.bind_mount(hosts_file, "/etc/hosts")
# Common CLI commands for any topology
cdict = {
"commands": [
#
# Window commands.
#
{
"name": "pcap",
"format": "pcap NETWORK",
"help": (
"capture packets from NETWORK into file capture-NETWORK.pcap"
" the command is run within a new window which also shows"
" packet summaries. NETWORK can also be an interface specified"
" as HOST:INTF. To capture inside the host namespace."
),
"exec": "tshark -s 9200 -i {0} -P -w capture-{0}.pcap",
"top-level": True,
"new-window": {"background": True},
},
{
"name": "term",
"format": "term HOST [HOST ...]",
"help": "open terminal[s] (TMUX or XTerm) on HOST[S], * for all",
"exec": "bash",
"new-window": True,
},
{
"name": "vtysh",
"exec": "/usr/bin/vtysh",
"format": "vtysh ROUTER [ROUTER ...]",
"new-window": True,
"kinds": ["frr"],
},
{
"name": "xterm",
"format": "xterm HOST [HOST ...]",
"help": "open XTerm[s] on HOST[S], * for all",
"exec": "bash",
"new-window": {
"forcex": True,
},
},
{
"name": "logd",
"exec": "tail -F %RUNDIR%/{}.log",
"format": "logd HOST [HOST ...] DAEMON",
"help": (
"tail -f on the logfile of the given "
"DAEMON for the given HOST[S]"
),
"new-window": True,
},
{
"name": "stdlog",
"exec": (
"[ -e %RUNDIR%/frr.log ] && tail -F %RUNDIR%/frr.log "
"|| tail -F /var/log/frr.log"
),
"format": "stdlog HOST [HOST ...]",
"help": "tail -f on the `frr.log` for the given HOST[S]",
"new-window": True,
},
{
"name": "stdout",
"exec": "tail -F %RUNDIR%/{0}.err",
"format": "stdout HOST [HOST ...] DAEMON",
"help": (
"tail -f on the stdout of the given DAEMON for the given HOST[S]"
),
"new-window": True,
},
{
"name": "stderr",
"exec": "tail -F %RUNDIR%/{0}.out",
"format": "stderr HOST [HOST ...] DAEMON",
"help": (
"tail -f on the stderr of the given DAEMON for the given HOST[S]"
),
"new-window": True,
},
#
# Non-window commands.
#
{
"name": "",
"exec": "vtysh -c '{}'",
"format": "[ROUTER ...] COMMAND",
"help": "execute vtysh COMMAND on the router[s]",
"kinds": ["frr"],
},
{
"name": "sh",
"format": "[HOST ...] sh <SHELL-COMMAND>",
"help": "execute <SHELL-COMMAND> on hosts",
"exec": "{}",
},
{
"name": "shi",
"format": "[HOST ...] shi <INTERACTIVE-COMMAND>",
"help": "execute <INTERACTIVE-COMMAND> on HOST[s]",
"exec": "{}",
"interactive": True,
},
]
}
cli.add_cli_config(self, cdict)
# shellopt = (
# self.pytest_config.getoption("--shell") if self.pytest_config else None
# )
# shellopt = shellopt if shellopt is not None else ""
# if shellopt == "all" or "." in shellopt.split(","):
# self.run_in_window("bash")
# This is expected by newer munet CLI code
self.config_dirname = ""
self.config = {}
self.logger.debug("%s: Creating", self)
@ -217,12 +315,15 @@ class Mininet(Micronet):
host.cmd_raises("ip addr add {}/{} dev {}".format(ip, plen, first_intf))
# can be used by munet cli
host.mgmt_ip = ipaddress.ip_address(ip)
if "defaultRoute" in params:
host.cmd_raises(
"ip route add default {}".format(params["defaultRoute"])
)
host.config()
host.config_host()
self.configured_hosts.add(name)
@ -248,4 +349,4 @@ class Mininet(Micronet):
Mininet.g_mnet_inst = None
def cli(self):
cli(self)
cli.cli(self)

View File

@ -212,7 +212,7 @@ class Topogen(object):
# Mininet(Micronet) to build the actual topology.
assert not inspect.isclass(topodef)
self.net = Mininet()
self.net = Mininet(rundir=self.logdir)
# Adjust the parent namespace
topotest.fix_netns_limits(self.net)
@ -752,8 +752,8 @@ class TopoRouter(TopoGear):
"""
super(TopoRouter, self).__init__(tgen, name, **params)
self.routertype = params.get("routertype", "frr")
if "privateDirs" not in params:
params["privateDirs"] = self.PRIVATE_DIRS
if "private_mounts" not in params:
params["private_mounts"] = self.PRIVATE_DIRS
# Propagate the router log directory
logfile = self._setup_tmpdir()
@ -1100,7 +1100,7 @@ class TopoHost(TopoGear):
* `ip`: the IP address (string) for the host interface
* `defaultRoute`: the default route that will be installed
(e.g. 'via 10.0.0.1')
* `privateDirs`: directories that will be mounted on a different domain
* `private_mounts`: directories that will be mounted on a different domain
(e.g. '/etc/important_dir').
"""
super(TopoHost, self).__init__(tgen, name, **params)
@ -1120,10 +1120,10 @@ class TopoHost(TopoGear):
def __str__(self):
gear = super(TopoHost, self).__str__()
gear += ' TopoHost<ip="{}",defaultRoute="{}",privateDirs="{}">'.format(
gear += ' TopoHost<ip="{}",defaultRoute="{}",private_mounts="{}">'.format(
self.params["ip"],
self.params["defaultRoute"],
str(self.params["privateDirs"]),
str(self.params["private_mounts"]),
)
return gear
@ -1146,10 +1146,10 @@ class TopoExaBGP(TopoHost):
(e.g. 'via 10.0.0.1')
Note: the different between a host and a ExaBGP peer is that this class
has a privateDirs already defined and contains functions to handle ExaBGP
things.
has a private_mounts already defined and contains functions to handle
ExaBGP things.
"""
params["privateDirs"] = self.PRIVATE_DIRS
params["private_mounts"] = self.PRIVATE_DIRS
super(TopoExaBGP, self).__init__(tgen, name, **params)
def __str__(self):

View File

@ -1318,7 +1318,7 @@ def setup_node_tmpdir(logdir, name):
class Router(Node):
"A Node with IPv4/IPv6 forwarding enabled"
def __init__(self, name, **params):
def __init__(self, name, *posargs, **params):
# Backward compatibility:
# Load configuration defaults like topogen.
@ -1347,7 +1347,7 @@ class Router(Node):
l = topolog.get_logger(name, log_level="debug", target=logfile)
params["logger"] = l
super(Router, self).__init__(name, **params)
super(Router, self).__init__(name, *posargs, **params)
self.daemondir = None
self.hasmpls = False
@ -1407,8 +1407,8 @@ class Router(Node):
# pylint: disable=W0221
# Some params are only meaningful for the parent class.
def config(self, **params):
super(Router, self).config(**params)
def config_host(self, **params):
super(Router, self).config_host(**params)
# User did not specify the daemons directory, try to autodetect it.
self.daemondir = params.get("daemondir")

View File

@ -24,7 +24,7 @@ log_file_date_format = %Y-%m-%d %H:%M:%S
junit_logging = all
junit_log_passing_tests = true
norecursedirs = .git example_test example_topojson_test lib docker
norecursedirs = .git example_test example_topojson_test lib munet docker
# Directory to store test results and run logs in, default shown
# rundir = /tmp/topotests