]>
Commit | Line | Data |
---|---|---|
249ac6f0 | 1 | # -*- coding: utf-8 eval: (blacken-mode 1) -*- |
1fca63c1 RZ |
2 | """ |
3 | Topotest conftest.py file. | |
4 | """ | |
1623dc4c | 5 | # pylint: disable=consider-using-f-string |
1fca63c1 | 6 | |
e58133a7 | 7 | import glob |
0def198c | 8 | import logging |
3f950192 | 9 | import os |
e58133a7 | 10 | import re |
60e03778 | 11 | import resource |
1726edc3 | 12 | import subprocess |
49581587 CH |
13 | import sys |
14 | import time | |
3f950192 | 15 | |
49581587 | 16 | import lib.fixtures |
7592b2cc | 17 | import pytest |
1a68b138 | 18 | from lib.micronet_compat import Mininet |
49581587 | 19 | from lib.topogen import diagnose_env, get_topogen |
0def198c | 20 | from lib.topolog import get_test_logdir, logger |
49581587 | 21 | from lib.topotest import json_cmp_result |
7592b2cc | 22 | from munet import cli |
60e03778 CH |
23 | from munet.base import Commander, proc_error |
24 | from munet.cleanup import cleanup_current, cleanup_previous | |
1a68b138 | 25 | from munet.config import ConfigOptionsProxy |
249ac6f0 | 26 | from munet.testing.util import pause_test |
7592b2cc | 27 | |
249ac6f0 | 28 | from lib import topolog, topotest |
787e7624 | 29 | |
0def198c CH |
30 | try: |
31 | # Used by munet native tests | |
32 | from munet.testing.fixtures import event_loop, unet # pylint: disable=all # noqa | |
33 | ||
34 | @pytest.fixture(scope="module") | |
35 | def rundir_module(pytestconfig): | |
36 | d = os.path.join(pytestconfig.option.rundir, get_test_logdir()) | |
37 | logging.debug("rundir_module: test module rundir %s", d) | |
38 | return d | |
39 | ||
40 | except (AttributeError, ImportError): | |
41 | pass | |
42 | ||
0b25370e | 43 | |
1fca63c1 RZ |
44 | def pytest_addoption(parser): |
45 | """ | |
46 | Add topology-only option to the topology tester. This option makes pytest | |
47 | only run the setup_module() to setup the topology without running any tests. | |
48 | """ | |
0ba1d257 CH |
49 | parser.addoption( |
50 | "--asan-abort", | |
51 | action="store_true", | |
52 | help="Configure address sanitizer to abort process on error", | |
53 | ) | |
54 | ||
49581587 CH |
55 | parser.addoption( |
56 | "--cli-on-error", | |
57 | action="store_true", | |
58 | help="Mininet cli on test failure", | |
59 | ) | |
60 | ||
3f950192 CH |
61 | parser.addoption( |
62 | "--gdb-breakpoints", | |
63 | metavar="SYMBOL[,SYMBOL...]", | |
64 | help="Comma-separated list of functions to set gdb breakpoints on", | |
65 | ) | |
66 | ||
67 | parser.addoption( | |
68 | "--gdb-daemons", | |
69 | metavar="DAEMON[,DAEMON...]", | |
70 | help="Comma-separated list of daemons to spawn gdb on, or 'all'", | |
71 | ) | |
72 | ||
73 | parser.addoption( | |
74 | "--gdb-routers", | |
75 | metavar="ROUTER[,ROUTER...]", | |
76 | help="Comma-separated list of routers to spawn gdb on, or 'all'", | |
77 | ) | |
78 | ||
79 | parser.addoption( | |
ff28990e CH |
80 | "--logd", |
81 | action="append", | |
82 | metavar="DAEMON[,ROUTER[,...]", | |
83 | help=( | |
e6079f4f CH |
84 | "Tail-F the DAEMON log file on all or a subset of ROUTERs." |
85 | " Option can be given multiple times." | |
ff28990e CH |
86 | ), |
87 | ) | |
88 | ||
d4977708 LB |
89 | parser.addoption( |
90 | "--memleaks", | |
91 | action="store_true", | |
92 | help="Report memstat results as errors", | |
93 | ) | |
94 | ||
ff28990e | 95 | parser.addoption( |
49581587 | 96 | "--pause", |
3f950192 | 97 | action="store_true", |
49581587 | 98 | help="Pause after each test", |
3f950192 CH |
99 | ) |
100 | ||
773fd82e CH |
101 | parser.addoption( |
102 | "--pause-at-end", | |
103 | action="store_true", | |
104 | help="Pause before taking munet down", | |
105 | ) | |
106 | ||
3f950192 | 107 | parser.addoption( |
49581587 | 108 | "--pause-on-error", |
3f950192 | 109 | action="store_true", |
49581587 | 110 | help="Do not pause after (disables default when --shell or -vtysh given)", |
3f950192 CH |
111 | ) |
112 | ||
49581587 CH |
113 | parser.addoption( |
114 | "--no-pause-on-error", | |
115 | dest="pause_on_error", | |
116 | action="store_false", | |
117 | help="Do not pause after (disables default when --shell or -vtysh given)", | |
118 | ) | |
119 | ||
773fd82e CH |
120 | parser.addoption( |
121 | "--pcap", | |
122 | default="", | |
123 | metavar="NET[,NET...]", | |
124 | help="Comma-separated list of networks to capture packets on, or 'all'", | |
125 | ) | |
126 | ||
e6079f4f CH |
127 | parser.addoption( |
128 | "--perf", | |
129 | action="append", | |
130 | metavar="DAEMON[,ROUTER[,...]", | |
131 | help=( | |
132 | "Collect performance data from given DAEMON on all or a subset of ROUTERs." | |
133 | " Option can be given multiple times." | |
134 | ), | |
135 | ) | |
136 | ||
137 | parser.addoption( | |
138 | "--perf-options", | |
139 | metavar="OPTS", | |
140 | default="-g", | |
141 | help="Options to pass to `perf record`.", | |
142 | ) | |
143 | ||
a53c08bc | 144 | rundir_help = "directory for running in and log files" |
49581587 CH |
145 | parser.addini("rundir", rundir_help, default="/tmp/topotests") |
146 | parser.addoption("--rundir", metavar="DIR", help=rundir_help) | |
147 | ||
3f950192 CH |
148 | parser.addoption( |
149 | "--shell", | |
150 | metavar="ROUTER[,ROUTER...]", | |
151 | help="Comma-separated list of routers to spawn shell on, or 'all'", | |
152 | ) | |
153 | ||
154 | parser.addoption( | |
155 | "--shell-on-error", | |
156 | action="store_true", | |
157 | help="Spawn shell on all routers on test failure", | |
158 | ) | |
159 | ||
0ba1d257 CH |
160 | parser.addoption( |
161 | "--strace-daemons", | |
162 | metavar="DAEMON[,DAEMON...]", | |
163 | help="Comma-separated list of daemons to strace, or 'all'", | |
164 | ) | |
165 | ||
787e7624 | 166 | parser.addoption( |
167 | "--topology-only", | |
168 | action="store_true", | |
c287dfd2 | 169 | default=False, |
787e7624 | 170 | help="Only set up this topology, don't run tests", |
171 | ) | |
172 | ||
e58133a7 CH |
173 | parser.addoption( |
174 | "--valgrind-extra", | |
175 | action="store_true", | |
176 | help="Generate suppression file, and enable more precise (slower) valgrind checks", | |
177 | ) | |
178 | ||
179 | parser.addoption( | |
180 | "--valgrind-memleaks", | |
181 | action="store_true", | |
182 | help="Run all daemons under valgrind for memleak detection", | |
183 | ) | |
184 | ||
3f950192 CH |
185 | parser.addoption( |
186 | "--vtysh", | |
187 | metavar="ROUTER[,ROUTER...]", | |
188 | help="Comma-separated list of routers to spawn vtysh on, or 'all'", | |
189 | ) | |
190 | ||
191 | parser.addoption( | |
192 | "--vtysh-on-error", | |
193 | action="store_true", | |
194 | help="Spawn vtysh on all routers on test failure", | |
195 | ) | |
196 | ||
1fca63c1 | 197 | |
d4977708 | 198 | def check_for_valgrind_memleaks(): |
249ac6f0 | 199 | assert topotest.g_pytest_config.option.valgrind_memleaks |
e58133a7 CH |
200 | |
201 | leaks = [] | |
1623dc4c | 202 | tgen = get_topogen() # pylint: disable=redefined-outer-name |
e58133a7 CH |
203 | latest = [] |
204 | existing = [] | |
205 | if tgen is not None: | |
49581587 | 206 | logdir = tgen.logdir |
e58133a7 CH |
207 | if hasattr(tgen, "valgrind_existing_files"): |
208 | existing = tgen.valgrind_existing_files | |
209 | latest = glob.glob(os.path.join(logdir, "*.valgrind.*")) | |
1623dc4c | 210 | latest = [x for x in latest if "core" not in x] |
e58133a7 | 211 | |
a15e5ac0 | 212 | daemons = set() |
e58133a7 CH |
213 | for vfile in latest: |
214 | if vfile in existing: | |
215 | continue | |
1623dc4c CH |
216 | # do not consider memleaks from parent fork (i.e., owned by root) |
217 | if os.stat(vfile).st_uid == 0: | |
218 | existing.append(vfile) # do not check again | |
219 | logger.debug("Skipping valgrind file %s owned by root", vfile) | |
220 | continue | |
221 | logger.debug("Checking valgrind file %s not owned by root", vfile) | |
a15e5ac0 | 222 | with open(vfile, encoding="ascii") as vf: |
e58133a7 CH |
223 | vfcontent = vf.read() |
224 | match = re.search(r"ERROR SUMMARY: (\d+) errors", vfcontent) | |
1623dc4c CH |
225 | if match: |
226 | existing.append(vfile) # have summary don't check again | |
e58133a7 | 227 | if match and match.group(1) != "0": |
49581587 | 228 | emsg = "{} in {}".format(match.group(1), vfile) |
e58133a7 | 229 | leaks.append(emsg) |
1623dc4c CH |
230 | daemon = re.match(r".*\.valgrind\.(.*)\.\d+", vfile).group(1) |
231 | daemons.add("{}({})".format(daemon, match.group(1))) | |
a15e5ac0 CH |
232 | |
233 | if tgen is not None: | |
234 | tgen.valgrind_existing_files = existing | |
e58133a7 CH |
235 | |
236 | if leaks: | |
a15e5ac0 CH |
237 | logger.error("valgrind memleaks found:\n\t%s", "\n\t".join(leaks)) |
238 | pytest.fail("valgrind memleaks found for daemons: " + " ".join(daemons)) | |
e58133a7 CH |
239 | |
240 | ||
d4977708 LB |
241 | def check_for_memleaks(): |
242 | leaks = [] | |
243 | tgen = get_topogen() # pylint: disable=redefined-outer-name | |
244 | latest = [] | |
245 | existing = [] | |
246 | if tgen is not None: | |
247 | logdir = tgen.logdir | |
248 | if hasattr(tgen, "memstat_existing_files"): | |
249 | existing = tgen.memstat_existing_files | |
250 | latest = glob.glob(os.path.join(logdir, "*/*.err")) | |
251 | ||
252 | daemons = [] | |
253 | for vfile in latest: | |
254 | if vfile in existing: | |
255 | continue | |
256 | with open(vfile, encoding="ascii") as vf: | |
257 | vfcontent = vf.read() | |
258 | num = vfcontent.count("memstats:") | |
259 | if num: | |
260 | existing.append(vfile) # have summary don't check again | |
261 | emsg = "{} types in {}".format(num, vfile) | |
262 | leaks.append(emsg) | |
263 | daemon = re.match(r".*test[a-z_A-Z0-9\+]*/(.*)\.err", vfile).group(1) | |
264 | daemons.append("{}({})".format(daemon, num)) | |
265 | ||
266 | if tgen is not None: | |
267 | tgen.memstat_existing_files = existing | |
268 | ||
269 | if leaks: | |
270 | logger.error("memleaks found:\n\t%s", "\n\t".join(leaks)) | |
271 | pytest.fail("memleaks found for daemons: " + " ".join(daemons)) | |
272 | ||
273 | ||
1623dc4c CH |
274 | @pytest.fixture(autouse=True, scope="module") |
275 | def module_check_memtest(request): | |
1623dc4c | 276 | yield |
249ac6f0 | 277 | if request.config.option.valgrind_memleaks: |
d4977708 LB |
278 | if get_topogen() is not None: |
279 | check_for_valgrind_memleaks() | |
280 | if request.config.option.memleaks: | |
1623dc4c CH |
281 | if get_topogen() is not None: |
282 | check_for_memleaks() | |
283 | ||
284 | ||
49581587 CH |
285 | def pytest_runtest_logstart(nodeid, location): |
286 | # location is (filename, lineno, testname) | |
249ac6f0 | 287 | topolog.logstart(nodeid, location, topotest.g_pytest_config.option.rundir) |
49581587 CH |
288 | |
289 | ||
290 | def pytest_runtest_logfinish(nodeid, location): | |
291 | # location is (filename, lineno, testname) | |
292 | topolog.logfinish(nodeid, location) | |
293 | ||
294 | ||
a15e5ac0 CH |
295 | @pytest.hookimpl(hookwrapper=True) |
296 | def pytest_runtest_call(item: pytest.Item) -> None: | |
297 | "Hook the function that is called to execute the test." | |
298 | ||
299 | # For topology only run the CLI then exit | |
249ac6f0 | 300 | if item.config.option.topology_only: |
a15e5ac0 CH |
301 | get_topogen().cli() |
302 | pytest.exit("exiting after --topology-only") | |
8833a838 | 303 | |
a15e5ac0 CH |
304 | # Let the default pytest_runtest_call execute the test function |
305 | yield | |
306 | ||
307 | # Check for leaks if requested | |
249ac6f0 | 308 | if item.config.option.valgrind_memleaks: |
d4977708 LB |
309 | check_for_valgrind_memleaks() |
310 | if item.config.option.memleaks: | |
a15e5ac0 | 311 | check_for_memleaks() |
787e7624 | 312 | |
3668ed8d RZ |
313 | |
314 | def pytest_assertrepr_compare(op, left, right): | |
315 | """ | |
316 | Show proper assertion error message for json_cmp results. | |
317 | """ | |
3f950192 CH |
318 | del op |
319 | ||
3668ed8d RZ |
320 | json_result = left |
321 | if not isinstance(json_result, json_cmp_result): | |
322 | json_result = right | |
323 | if not isinstance(json_result, json_cmp_result): | |
324 | return None | |
325 | ||
849224d4 | 326 | return json_result.gen_report() |
007e7313 | 327 | |
787e7624 | 328 | |
007e7313 | 329 | def pytest_configure(config): |
3f950192 CH |
330 | """ |
331 | Assert that the environment is correctly configured, and get extra config. | |
332 | """ | |
249ac6f0 | 333 | topotest.g_pytest_config = ConfigOptionsProxy(config) |
80cb48d2 | 334 | |
3d80bd11 DL |
335 | if config.getoption("--collect-only"): |
336 | return | |
337 | ||
49581587 CH |
338 | if "PYTEST_XDIST_WORKER" not in os.environ: |
339 | os.environ["PYTEST_XDIST_MODE"] = config.getoption("dist", "no") | |
340 | os.environ["PYTEST_TOPOTEST_WORKER"] = "" | |
341 | is_xdist = os.environ["PYTEST_XDIST_MODE"] != "no" | |
342 | is_worker = False | |
343 | else: | |
344 | os.environ["PYTEST_TOPOTEST_WORKER"] = os.environ["PYTEST_XDIST_WORKER"] | |
345 | is_xdist = True | |
346 | is_worker = True | |
347 | ||
e2e677f6 DS |
348 | resource.setrlimit( |
349 | resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY) | |
350 | ) | |
49581587 CH |
351 | # ----------------------------------------------------- |
352 | # Set some defaults for the pytest.ini [pytest] section | |
353 | # --------------------------------------------------- | |
354 | ||
249ac6f0 | 355 | rundir = config.option.rundir |
49581587 CH |
356 | if not rundir: |
357 | rundir = config.getini("rundir") | |
358 | if not rundir: | |
359 | rundir = "/tmp/topotests" | |
249ac6f0 CH |
360 | config.option.rundir = rundir |
361 | ||
49581587 CH |
362 | if not config.getoption("--junitxml"): |
363 | config.option.xmlpath = os.path.join(rundir, "topotests.xml") | |
364 | xmlpath = config.option.xmlpath | |
365 | ||
366 | # Save an existing topotest.xml | |
367 | if os.path.exists(xmlpath): | |
368 | fmtime = time.localtime(os.path.getmtime(xmlpath)) | |
369 | suffix = "-" + time.strftime("%Y%m%d%H%M%S", fmtime) | |
370 | commander = Commander("pytest") | |
371 | mv_path = commander.get_exec_path("mv") | |
372 | commander.cmd_status([mv_path, xmlpath, xmlpath + suffix]) | |
373 | ||
49581587 CH |
374 | # Set the log_file (exec) to inside the rundir if not specified |
375 | if not config.getoption("--log-file") and not config.getini("log_file"): | |
376 | config.option.log_file = os.path.join(rundir, "exec.log") | |
377 | ||
378 | # Turn on live logging if user specified verbose and the config has a CLI level set | |
379 | if config.getoption("--verbose") and not is_xdist and not config.getini("log_cli"): | |
380 | if config.getoption("--log-cli-level", None) is None: | |
381 | # By setting the CLI option to the ini value it enables log_cli=1 | |
382 | cli_level = config.getini("log_cli_level") | |
383 | if cli_level is not None: | |
384 | config.option.log_cli_level = cli_level | |
02547745 | 385 | |
1726edc3 CH |
386 | have_tmux = bool(os.getenv("TMUX", "")) |
387 | have_screen = not have_tmux and bool(os.getenv("STY", "")) | |
388 | have_xterm = not have_tmux and not have_screen and bool(os.getenv("DISPLAY", "")) | |
389 | have_windows = have_tmux or have_screen or have_xterm | |
390 | have_windows_pause = have_tmux or have_xterm | |
391 | xdist_no_windows = is_xdist and not is_worker and not have_windows_pause | |
392 | ||
393 | def assert_feature_windows(b, feature): | |
394 | if b and xdist_no_windows: | |
395 | pytest.exit( | |
396 | "{} use requires byobu/TMUX/XTerm under dist {}".format( | |
397 | feature, os.environ["PYTEST_XDIST_MODE"] | |
398 | ) | |
399 | ) | |
400 | elif b and not is_xdist and not have_windows: | |
401 | pytest.exit("{} use requires byobu/TMUX/SCREEN/XTerm".format(feature)) | |
402 | ||
249ac6f0 CH |
403 | # |
404 | # Check for window capability if given options that require window | |
405 | # | |
406 | assert_feature_windows(config.option.gdb_routers, "GDB") | |
407 | assert_feature_windows(config.option.gdb_daemons, "GDB") | |
408 | assert_feature_windows(config.option.cli_on_error, "--cli-on-error") | |
409 | assert_feature_windows(config.option.shell, "--shell") | |
410 | assert_feature_windows(config.option.shell_on_error, "--shell-on-error") | |
411 | assert_feature_windows(config.option.vtysh, "--vtysh") | |
412 | assert_feature_windows(config.option.vtysh_on_error, "--vtysh-on-error") | |
413 | ||
414 | if config.option.topology_only and is_xdist: | |
a15e5ac0 | 415 | pytest.exit("Cannot use --topology-only with distributed test mode") |
80cb48d2 | 416 | |
d4977708 LB |
417 | pytest.exit("Cannot use --topology-only with distributed test mode") |
418 | ||
49581587 CH |
419 | # Check environment now that we have config |
420 | if not diagnose_env(rundir): | |
e40b7130 | 421 | pytest.exit("environment has errors, please read the logs in %s" % rundir) |
49581587 | 422 | |
d4977708 LB |
423 | # slave TOPOTESTS_CHECK_MEMLEAK to memleaks flag |
424 | if config.option.memleaks: | |
425 | if "TOPOTESTS_CHECK_MEMLEAK" not in os.environ: | |
426 | os.environ["TOPOTESTS_CHECK_MEMLEAK"] = "/dev/null" | |
427 | else: | |
428 | if "TOPOTESTS_CHECK_MEMLEAK" in os.environ: | |
429 | del os.environ["TOPOTESTS_CHECK_MEMLEAK"] | |
430 | if "TOPOTESTS_CHECK_STDERR" in os.environ: | |
431 | del os.environ["TOPOTESTS_CHECK_STDERR"] | |
432 | ||
49581587 CH |
433 | |
434 | @pytest.fixture(autouse=True, scope="session") | |
435 | def setup_session_auto(): | |
436 | if "PYTEST_TOPOTEST_WORKER" not in os.environ: | |
437 | is_worker = False | |
438 | elif not os.environ["PYTEST_TOPOTEST_WORKER"]: | |
439 | is_worker = False | |
440 | else: | |
441 | is_worker = True | |
442 | ||
443 | logger.debug("Before the run (is_worker: %s)", is_worker) | |
444 | if not is_worker: | |
445 | cleanup_previous() | |
446 | yield | |
447 | if not is_worker: | |
448 | cleanup_current() | |
449 | logger.debug("After the run (is_worker: %s)", is_worker) | |
450 | ||
787e7624 | 451 | |
02547745 CH |
452 | def pytest_runtest_setup(item): |
453 | module = item.parent.module | |
454 | script_dir = os.path.abspath(os.path.dirname(module.__file__)) | |
455 | os.environ["PYTEST_TOPOTEST_SCRIPTDIR"] = script_dir | |
456 | ||
457 | ||
e7ba3cd1 RZ |
458 | def pytest_runtest_makereport(item, call): |
459 | "Log all assert messages to default logger with error level" | |
e7ba3cd1 | 460 | |
cad55444 | 461 | pause = bool(item.config.getoption("--pause")) |
a53c08bc | 462 | title = "unset" |
49581587 | 463 | |
3f950192 CH |
464 | if call.excinfo is None: |
465 | error = False | |
466 | else: | |
467 | parent = item.parent | |
468 | modname = parent.module.__name__ | |
469 | ||
470 | # Treat skips as non errors, don't pause after | |
4f99894d | 471 | if call.excinfo.typename == "Skipped": |
3f950192 CH |
472 | pause = False |
473 | error = False | |
474 | logger.info( | |
4f99894d | 475 | 'test skipped at "{}/{}": {}'.format( |
3f950192 CH |
476 | modname, item.name, call.excinfo.value |
477 | ) | |
478 | ) | |
479 | else: | |
480 | error = True | |
481 | # Handle assert failures | |
0b25370e | 482 | parent._previousfailed = item # pylint: disable=W0212 |
3f950192 | 483 | logger.error( |
4f99894d | 484 | 'test failed at "{}/{}": {}'.format( |
0b25370e DS |
485 | modname, item.name, call.excinfo.value |
486 | ) | |
787e7624 | 487 | ) |
49581587 | 488 | title = "{}/{}".format(modname, item.name) |
c8c265f5 | 489 | |
0ba1d257 CH |
490 | # We want to pause, if requested, on any error not just test cases |
491 | # (e.g., call.when == "setup") | |
492 | if not pause: | |
249ac6f0 | 493 | pause = item.config.option.pause_on_error or item.config.option.pause |
0ba1d257 | 494 | |
3f950192 | 495 | # (topogen) Set topology error to avoid advancing in the test. |
1623dc4c | 496 | tgen = get_topogen() # pylint: disable=redefined-outer-name |
3f950192 CH |
497 | if tgen is not None: |
498 | # This will cause topogen to report error on `routers_have_failure`. | |
499 | tgen.set_error("{}/{}".format(modname, item.name)) | |
500 | ||
49581587 CH |
501 | commander = Commander("pytest") |
502 | isatty = sys.stdout.isatty() | |
503 | error_cmd = None | |
3f950192 | 504 | |
249ac6f0 | 505 | if error and item.config.option.vtysh_on_error: |
49581587 | 506 | error_cmd = commander.get_exec_path(["vtysh"]) |
249ac6f0 | 507 | elif error and item.config.option.shell_on_error: |
49581587 CH |
508 | error_cmd = os.getenv("SHELL", commander.get_exec_path(["bash"])) |
509 | ||
510 | if error_cmd: | |
1726edc3 CH |
511 | is_tmux = bool(os.getenv("TMUX", "")) |
512 | is_screen = not is_tmux and bool(os.getenv("STY", "")) | |
513 | is_xterm = not is_tmux and not is_screen and bool(os.getenv("DISPLAY", "")) | |
514 | ||
515 | channel = None | |
49581587 CH |
516 | win_info = None |
517 | wait_for_channels = [] | |
1726edc3 CH |
518 | wait_for_procs = [] |
519 | # Really would like something better than using this global here. | |
520 | # Not all tests use topogen though so get_topogen() won't work. | |
49581587 | 521 | for node in Mininet.g_mnet_inst.hosts.values(): |
3f950192 | 522 | pause = True |
3f950192 | 523 | |
1726edc3 CH |
524 | if is_tmux: |
525 | channel = ( | |
526 | "{}-{}".format(os.getpid(), Commander.tmux_wait_gen) | |
527 | if not isatty | |
528 | else None | |
529 | ) | |
530 | Commander.tmux_wait_gen += 1 | |
531 | wait_for_channels.append(channel) | |
49581587 CH |
532 | |
533 | pane_info = node.run_in_window( | |
534 | error_cmd, | |
535 | new_window=win_info is None, | |
536 | background=True, | |
537 | title="{} ({})".format(title, node.name), | |
538 | name=title, | |
539 | tmux_target=win_info, | |
a53c08bc | 540 | wait_for=channel, |
49581587 | 541 | ) |
1726edc3 CH |
542 | if is_tmux: |
543 | if win_info is None: | |
544 | win_info = pane_info | |
545 | elif is_xterm: | |
546 | assert isinstance(pane_info, subprocess.Popen) | |
547 | wait_for_procs.append(pane_info) | |
49581587 CH |
548 | |
549 | # Now wait on any channels | |
550 | for channel in wait_for_channels: | |
551 | logger.debug("Waiting on TMUX channel %s", channel) | |
552 | commander.cmd_raises([commander.get_exec_path("tmux"), "wait", channel]) | |
1726edc3 CH |
553 | for p in wait_for_procs: |
554 | logger.debug("Waiting on TMUX xterm process %s", p) | |
555 | o, e = p.communicate() | |
556 | if p.wait(): | |
557 | logger.warning("xterm proc failed: %s:", proc_error(p, o, e)) | |
49581587 | 558 | |
249ac6f0 | 559 | if error and item.config.option.cli_on_error: |
49581587 CH |
560 | # Really would like something better than using this global here. |
561 | # Not all tests use topogen though so get_topogen() won't work. | |
562 | if Mininet.g_mnet_inst: | |
60e03778 | 563 | cli.cli(Mininet.g_mnet_inst, title=title, background=False) |
49581587 CH |
564 | else: |
565 | logger.error("Could not launch CLI b/c no mininet exists yet") | |
3f950192 | 566 | |
249ac6f0 CH |
567 | if pause and isatty: |
568 | pause_test() | |
49581587 CH |
569 | |
570 | ||
571 | # | |
572 | # Add common fixtures available to all tests as parameters | |
573 | # | |
e2e677f6 | 574 | |
49581587 CH |
575 | tgen = pytest.fixture(lib.fixtures.tgen) |
576 | topo = pytest.fixture(lib.fixtures.topo) |