summaryrefslogtreecommitdiffstats
path: root/tests/topotests/pim_basic
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
commit2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch)
treec05dc0f8e6aa3accc84e3e5cffc933ed94941383 /tests/topotests/pim_basic
parentInitial commit. (diff)
downloadfrr-upstream.tar.xz
frr-upstream.zip
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/topotests/pim_basic')
-rwxr-xr-xtests/topotests/pim_basic/mcast-rx.py92
-rwxr-xr-xtests/topotests/pim_basic/mcast-tx.py97
-rw-r--r--tests/topotests/pim_basic/r1/bgpd.conf5
-rw-r--r--tests/topotests/pim_basic/r1/pimd.conf18
-rw-r--r--tests/topotests/pim_basic/r1/rp-info.json9
-rw-r--r--tests/topotests/pim_basic/r1/zebra.conf14
-rw-r--r--tests/topotests/pim_basic/r2/pimd.conf1
-rw-r--r--tests/topotests/pim_basic/r2/zebra.conf8
-rw-r--r--tests/topotests/pim_basic/r3/pimd.conf1
-rw-r--r--tests/topotests/pim_basic/r3/zebra.conf8
-rw-r--r--tests/topotests/pim_basic/rp/bgpd.conf5
-rw-r--r--tests/topotests/pim_basic/rp/pimd.conf13
-rw-r--r--tests/topotests/pim_basic/rp/upstream.json17
-rw-r--r--tests/topotests/pim_basic/rp/zebra.conf8
-rw-r--r--tests/topotests/pim_basic/test_pim.py248
15 files changed, 544 insertions, 0 deletions
diff --git a/tests/topotests/pim_basic/mcast-rx.py b/tests/topotests/pim_basic/mcast-rx.py
new file mode 100755
index 0000000..8853376
--- /dev/null
+++ b/tests/topotests/pim_basic/mcast-rx.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+#
+# mcast-rx.py
+#
+# Copyright (c) 2018 Cumulus Networks, Inc.
+#
+# Permission to use, copy, modify, and/or distribute this software
+# for any purpose with or without fee is hereby granted, provided
+# that the above copyright notice and this permission notice appear
+# in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND Cumulus Networks DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+#
+"""
+Subscribe to a multicast group so that the kernel sends an IGMP JOIN
+for the multicast group we subscribed to.
+"""
+
+import argparse
+import logging
+import re
+import os
+import socket
+import subprocess
+import struct
+import sys
+import time
+
+
+def ifname_to_ifindex(ifname):
+ output = subprocess.check_output(
+ "ip link show %s" % ifname, shell=True, universal_newlines=True
+ )
+ first_line = output.split("\n")[0]
+ re_index = re.search("^(\d+):", first_line)
+
+ if re_index:
+ return int(re_index.group(1))
+
+ log.error("Could not parse the ifindex for %s out of\n%s" % (ifname, first_line))
+ return None
+
+
+# Thou shalt be root
+if os.geteuid() != 0:
+ sys.stderr.write("ERROR: You must have root privileges\n")
+ sys.exit(1)
+
+
+logging.basicConfig(
+ level=logging.DEBUG, format="%(asctime)s %(levelname)5s: %(message)s"
+)
+
+# Color the errors and warnings in red
+logging.addLevelName(
+ logging.ERROR, "\033[91m %s\033[0m" % logging.getLevelName(logging.ERROR)
+)
+logging.addLevelName(
+ logging.WARNING, "\033[91m%s\033[0m" % logging.getLevelName(logging.WARNING)
+)
+log = logging.getLogger(__name__)
+
+parser = argparse.ArgumentParser(description="Multicast RX utility")
+
+parser.add_argument("group", help="Multicast IP")
+parser.add_argument("ifname", help="Interface name")
+parser.add_argument("--port", help="UDP port", default=1000)
+parser.add_argument("--sleep", help="Time to sleep before we stop waiting", default=5)
+args = parser.parse_args()
+
+# Create the datagram socket
+sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.bind((args.group, args.port))
+
+newpid = os.fork()
+
+if newpid == 0:
+ ifindex = ifname_to_ifindex(args.ifname)
+ mreq = struct.pack(
+ "=4sLL", socket.inet_aton(args.group), socket.INADDR_ANY, ifindex
+ )
+ sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
+ time.sleep(float(args.sleep))
+ sock.close()
diff --git a/tests/topotests/pim_basic/mcast-tx.py b/tests/topotests/pim_basic/mcast-tx.py
new file mode 100755
index 0000000..88c2345
--- /dev/null
+++ b/tests/topotests/pim_basic/mcast-tx.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+#
+# mcast-tx.py
+#
+# Copyright (c) 2018 Cumulus Networks, Inc.
+#
+# Permission to use, copy, modify, and/or distribute this software
+# for any purpose with or without fee is hereby granted, provided
+# that the above copyright notice and this permission notice appear
+# in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND Cumulus Networks DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+#
+
+import argparse
+import logging
+import socket
+import struct
+import time
+import sys
+
+logging.basicConfig(
+ level=logging.DEBUG, format="%(asctime)s %(levelname)5s: %(message)s"
+)
+
+# Color the errors and warnings in red
+logging.addLevelName(
+ logging.ERROR, "\033[91m %s\033[0m" % logging.getLevelName(logging.ERROR)
+)
+logging.addLevelName(
+ logging.WARNING, "\033[91m%s\033[0m" % logging.getLevelName(logging.WARNING)
+)
+log = logging.getLogger(__name__)
+
+parser = argparse.ArgumentParser(description="Multicast packet generator")
+parser.add_argument("group", help="Multicast IP")
+parser.add_argument("ifname", help="Interface name")
+parser.add_argument("--port", type=int, help="UDP port number", default=1000)
+parser.add_argument("--ttl", type=int, help="time-to-live", default=20)
+parser.add_argument("--count", type=int, help="Packets to send", default=1)
+parser.add_argument("--interval", type=int, help="ms between packets", default=100)
+args = parser.parse_args()
+
+# Create the datagram socket
+sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+
+# IN.SO_BINDTODEVICE is not defined in some releases of python but it is 25
+# https://github.com/sivel/bonding/issues/10
+#
+# Bind our socket to ifname
+#
+# Note ugly python version incompatibility
+#
+if sys.version_info[0] > 2:
+ sock.setsockopt(
+ socket.SOL_SOCKET,
+ 25,
+ struct.pack("%ds" % len(args.ifname), args.ifname.encode("utf-8")),
+ )
+else:
+ sock.setsockopt(
+ socket.SOL_SOCKET, 25, struct.pack("%ds" % len(args.ifname), args.ifname)
+ )
+
+# We need to make sure our sendto() finishes before we close the socket
+sock.setblocking(1)
+
+# Set the time-to-live
+sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, struct.pack("b", args.ttl))
+
+ms = args.interval / 1000.0
+
+# Send data to the multicast group
+for x in range(args.count):
+ log.info(
+ "TX multicast UDP packet to %s:%d on %s" % (args.group, args.port, args.ifname)
+ )
+
+ #
+ # Note ugly python version incompatibility
+ #
+ if sys.version_info[0] > 2:
+ sent = sock.sendto(b"foobar %d" % x, (args.group, args.port))
+ else:
+ sent = sock.sendto("foobar %d" % x, (args.group, args.port))
+
+ if args.count > 1 and ms:
+ time.sleep(ms)
+
+sock.close()
diff --git a/tests/topotests/pim_basic/r1/bgpd.conf b/tests/topotests/pim_basic/r1/bgpd.conf
new file mode 100644
index 0000000..84d9598
--- /dev/null
+++ b/tests/topotests/pim_basic/r1/bgpd.conf
@@ -0,0 +1,5 @@
+router bgp 65001
+ no bgp ebgp-requires-policy
+ neighbor 10.0.30.3 remote-as external
+ neighbor 10.0.30.3 timers 3 10
+ redistribute connected
diff --git a/tests/topotests/pim_basic/r1/pimd.conf b/tests/topotests/pim_basic/r1/pimd.conf
new file mode 100644
index 0000000..737019f
--- /dev/null
+++ b/tests/topotests/pim_basic/r1/pimd.conf
@@ -0,0 +1,18 @@
+hostname r1
+!
+interface r1-eth0
+ ip igmp
+ ip pim
+!
+interface r1-eth1
+ ip pim
+!
+interface r1-eth2
+ ip igmp
+ ip pim
+!
+interface lo
+ ip pim
+!
+ip pim rp 10.254.0.3
+ip pim join-prune-interval 5
diff --git a/tests/topotests/pim_basic/r1/rp-info.json b/tests/topotests/pim_basic/r1/rp-info.json
new file mode 100644
index 0000000..1f713c2
--- /dev/null
+++ b/tests/topotests/pim_basic/r1/rp-info.json
@@ -0,0 +1,9 @@
+{
+ "10.254.0.3":[
+ {
+ "outboundInterface":"r1-eth1",
+ "group":"224.0.0.0\/4",
+ "source":"Static"
+ }
+ ]
+}
diff --git a/tests/topotests/pim_basic/r1/zebra.conf b/tests/topotests/pim_basic/r1/zebra.conf
new file mode 100644
index 0000000..e430417
--- /dev/null
+++ b/tests/topotests/pim_basic/r1/zebra.conf
@@ -0,0 +1,14 @@
+hostname r1
+!
+interface r1-eth0
+ ip address 10.0.20.1/24
+!
+interface r1-eth1
+ ip address 10.0.30.1/24
+!
+interface r1-eth2
+ ip address 10.0.40.1/24
+!
+interface lo
+ ip address 10.254.0.1/32
+!
diff --git a/tests/topotests/pim_basic/r2/pimd.conf b/tests/topotests/pim_basic/r2/pimd.conf
new file mode 100644
index 0000000..932cff6
--- /dev/null
+++ b/tests/topotests/pim_basic/r2/pimd.conf
@@ -0,0 +1 @@
+hostname r2
diff --git a/tests/topotests/pim_basic/r2/zebra.conf b/tests/topotests/pim_basic/r2/zebra.conf
new file mode 100644
index 0000000..cb30858
--- /dev/null
+++ b/tests/topotests/pim_basic/r2/zebra.conf
@@ -0,0 +1,8 @@
+hostname r2
+!
+interface r2-eth0
+ ip address 10.0.20.2/24
+!
+interface lo
+ ip address 10.254.0.2/32
+!
diff --git a/tests/topotests/pim_basic/r3/pimd.conf b/tests/topotests/pim_basic/r3/pimd.conf
new file mode 100644
index 0000000..f94ee99
--- /dev/null
+++ b/tests/topotests/pim_basic/r3/pimd.conf
@@ -0,0 +1 @@
+hostname r3
diff --git a/tests/topotests/pim_basic/r3/zebra.conf b/tests/topotests/pim_basic/r3/zebra.conf
new file mode 100644
index 0000000..8e58e8c
--- /dev/null
+++ b/tests/topotests/pim_basic/r3/zebra.conf
@@ -0,0 +1,8 @@
+hostname r3
+!
+interface r3-eth0
+ ip address 10.0.40.4/24
+!
+interface lo
+ ip address 10.254.0.4/32
+!
diff --git a/tests/topotests/pim_basic/rp/bgpd.conf b/tests/topotests/pim_basic/rp/bgpd.conf
new file mode 100644
index 0000000..1bfae60
--- /dev/null
+++ b/tests/topotests/pim_basic/rp/bgpd.conf
@@ -0,0 +1,5 @@
+router bgp 65003
+ no bgp ebgp-requires-policy
+ neighbor 10.0.30.1 remote-as external
+ neighbor 10.0.30.1 timers 3 10
+ redistribute connected
diff --git a/tests/topotests/pim_basic/rp/pimd.conf b/tests/topotests/pim_basic/rp/pimd.conf
new file mode 100644
index 0000000..fd26bc4
--- /dev/null
+++ b/tests/topotests/pim_basic/rp/pimd.conf
@@ -0,0 +1,13 @@
+hostname rp
+!
+interface rp-eth0
+ ip pim
+!
+interface lo
+ ip pim
+!
+ip pim join-prune-interval 5
+ip pim rp 10.254.0.3
+ip pim register-accept-list ACCEPT
+
+ip prefix-list ACCEPT seq 5 permit 10.0.20.0/24 le 32
diff --git a/tests/topotests/pim_basic/rp/upstream.json b/tests/topotests/pim_basic/rp/upstream.json
new file mode 100644
index 0000000..c33dea4
--- /dev/null
+++ b/tests/topotests/pim_basic/rp/upstream.json
@@ -0,0 +1,17 @@
+{
+ "229.1.1.1":{
+ "10.0.20.2":{
+ "sourceStream":true,
+ "inboundInterface":"rp-eth0",
+ "rpfAddress":"10.0.20.2",
+ "source":"10.0.20.2",
+ "group":"229.1.1.1",
+ "state":"NotJ",
+ "joinState":"NotJoined",
+ "regState":"RegNoInfo",
+ "resetTimer":"--:--:--",
+ "refCount":1,
+ "sptBit":0
+ }
+ }
+}
diff --git a/tests/topotests/pim_basic/rp/zebra.conf b/tests/topotests/pim_basic/rp/zebra.conf
new file mode 100644
index 0000000..0a1359e
--- /dev/null
+++ b/tests/topotests/pim_basic/rp/zebra.conf
@@ -0,0 +1,8 @@
+hostname rp
+!
+interface rp-eth0
+ ip address 10.0.30.3/24
+!
+interface lo
+ ip address 10.254.0.3/32
+!
diff --git a/tests/topotests/pim_basic/test_pim.py b/tests/topotests/pim_basic/test_pim.py
new file mode 100644
index 0000000..6cea521
--- /dev/null
+++ b/tests/topotests/pim_basic/test_pim.py
@@ -0,0 +1,248 @@
+#!/usr/bin/env python
+
+#
+# test_pim.py
+#
+# Copyright (c) 2018 Cumulus Networks, Inc.
+# Donald Sharp
+#
+# Permission to use, copy, modify, and/or distribute this software
+# for any purpose with or without fee is hereby granted, provided
+# that the above copyright notice and this permission notice appear
+# in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND Cumulus Networks DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+#
+
+"""
+test_pim.py: Test pim
+"""
+
+import os
+import sys
+import pytest
+import json
+from functools import partial
+
+pytestmark = pytest.mark.pimd
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+
+pytestmark = [pytest.mark.pimd]
+
+
+def build_topo(tgen):
+ "Build function"
+
+ for routern in range(1, 4):
+ tgen.add_router("r{}".format(routern))
+
+ tgen.add_router("rp")
+
+ # rp ------ r1 -------- r2
+ # \
+ # --------- r3
+ # r1 -> .1
+ # r2 -> .2
+ # rp -> .3
+ # r3 -> .4
+ # loopback network is 10.254.0.X/32
+ #
+ # r1 <- sw1 -> r2
+ # r1-eth0 <-> r2-eth0
+ # 10.0.20.0/24
+ sw = tgen.add_switch("sw1")
+ sw.add_link(tgen.gears["r1"])
+ sw.add_link(tgen.gears["r2"])
+
+ # r1 <- sw2 -> rp
+ # r1-eth1 <-> rp-eth0
+ # 10.0.30.0/24
+ sw = tgen.add_switch("sw2")
+ sw.add_link(tgen.gears["r1"])
+ sw.add_link(tgen.gears["rp"])
+
+ # 10.0.40.0/24
+ sw = tgen.add_switch("sw3")
+ sw.add_link(tgen.gears["r1"])
+ sw.add_link(tgen.gears["r3"])
+
+
+def setup_module(mod):
+ "Sets up the pytest environment"
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ # For all registered routers, load the zebra configuration file
+ for rname, router in tgen.routers().items():
+ router.load_config(
+ TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
+ )
+ router.load_config(
+ TopoRouter.RD_PIM, os.path.join(CWD, "{}/pimd.conf".format(rname))
+ )
+ router.load_config(
+ TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
+ )
+
+ # After loading the configurations, this function loads configured daemons.
+ tgen.start_router()
+ # tgen.mininet_cli()
+
+
+def teardown_module(mod):
+ "Teardown the pytest environment"
+ tgen = get_topogen()
+
+ # This function tears down the whole topology.
+ tgen.stop_topology()
+
+
+def test_pim_rp_setup():
+ "Ensure basic routing has come up and the rp has an outgoing interface"
+ # Ensure rp and r1 establish pim neighbor ship and bgp has come up
+ # Finally ensure that the rp has an outgoing interface on r1
+ tgen = get_topogen()
+
+ r1 = tgen.gears["r1"]
+ json_file = "{}/{}/rp-info.json".format(CWD, r1.name)
+ expected = json.loads(open(json_file).read())
+
+ test_func = partial(
+ topotest.router_json_cmp, r1, "show ip pim rp-info json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=15, wait=5)
+ assertmsg = '"{}" JSON output mismatches'.format(r1.name)
+ assert result is None, assertmsg
+ # tgen.mininet_cli()
+
+
+def test_pim_send_mcast_stream():
+ "Establish a Multicast stream from r2 -> r1 and then ensure S,G is created as appropriate"
+ logger.info("Establish a Mcast stream from r2->r1 and then ensure S,G created")
+
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ rp = tgen.gears["rp"]
+ r3 = tgen.gears["r3"]
+ r2 = tgen.gears["r2"]
+ r1 = tgen.gears["r1"]
+
+ # Let's establish a S,G stream from r2 -> r1
+ CWD = os.path.dirname(os.path.realpath(__file__))
+ r2.run(
+ "{}/mcast-tx.py --ttl 5 --count 5 --interval 10 229.1.1.1 r2-eth0 > /tmp/bar".format(
+ CWD
+ )
+ )
+ # And from r3 -> r1
+ r3.run(
+ "{}/mcast-tx.py --ttl 5 --count 5 --interval 10 229.1.1.1 r3-eth0 > /tmp/bar".format(
+ CWD
+ )
+ )
+
+ # Let's see that it shows up and we have established some basic state
+ out = r1.vtysh_cmd("show ip pim upstream json", isjson=True)
+ expected = {
+ "229.1.1.1": {
+ "10.0.20.2": {
+ "firstHopRouter": 1,
+ "joinState": "NotJoined",
+ "regState": "RegPrune",
+ "inboundInterface": "r1-eth0",
+ }
+ }
+ }
+
+ test_func = partial(
+ topotest.router_json_cmp, r1, "show ip pim upstream json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=20, wait=1)
+ assert result is None, "failed to converge pim"
+ # tgen.mininet_cli()
+
+
+def test_pim_rp_sees_stream():
+ "Ensure that the RP sees the stream and has acted accordingly"
+ tgen = get_topogen()
+
+ rp = tgen.gears["rp"]
+ json_file = "{}/{}/upstream.json".format(CWD, rp.name)
+ expected = json.loads(open(json_file).read())
+
+ test_func = partial(
+ topotest.router_json_cmp, rp, "show ip pim upstream json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=20, wait=0.5)
+ assertmsg = '"{}" JSON output mismatches'.format(rp.name)
+ assert result is None, assertmsg
+
+
+def test_pim_igmp_report():
+ "Send a igmp report from r2->r1 and ensure that the *,G state is created on r1"
+ logger.info("Send a igmp report from r2-r1 and ensure *,G created")
+
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r2 = tgen.gears["r2"]
+ r1 = tgen.gears["r1"]
+
+ # Let's send a igmp report from r2->r1
+ cmd = [os.path.join(CWD, "mcast-rx.py"), "229.1.1.2", "r2-eth0"]
+ p = r2.popen(cmd)
+ try:
+ expected = {
+ "229.1.1.2": {
+ "*": {
+ "sourceIgmp": 1,
+ "joinState": "Joined",
+ "regState": "RegNoInfo",
+ "sptBit": 0,
+ }
+ }
+ }
+ test_func = partial(
+ topotest.router_json_cmp, r1, "show ip pim upstream json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=5, wait=0.5)
+ assertmsg = '"{}" JSON output mismatches'.format(r1.name)
+ assert result is None, assertmsg
+ finally:
+ if p:
+ p.terminate()
+ p.wait()
+
+
+def test_memory_leak():
+ "Run the memory leak test and report results."
+ tgen = get_topogen()
+ if not tgen.is_memleak_enabled():
+ pytest.skip("Memory leak test/report is disabled")
+
+ tgen.report_memory_leaks()
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))