]>
Commit | Line | Data |
---|---|---|
acddc0ed | 1 | # SPDX-License-Identifier: ISC |
36d1dc45 RZ |
2 | # |
3 | # topolog.py | |
4 | # Library of helper functions for NetDEF Topology Tests | |
5 | # | |
6 | # Copyright (c) 2017 by | |
7 | # Network Device Education Foundation, Inc. ("NetDEF") | |
8 | # | |
36d1dc45 RZ |
9 | |
10 | """ | |
11 | Logging utilities for topology tests. | |
12 | ||
13 | This file defines our logging abstraction. | |
14 | """ | |
15 | ||
16 | import logging | |
49581587 CH |
17 | import os |
18 | import subprocess | |
19 | import sys | |
20 | ||
21 | if sys.version_info[0] > 2: | |
4953ca97 | 22 | pass |
49581587 | 23 | else: |
4953ca97 | 24 | pass |
49581587 CH |
25 | |
26 | try: | |
27 | from xdist import is_xdist_controller | |
28 | except ImportError: | |
a53c08bc | 29 | |
49581587 CH |
30 | def is_xdist_controller(): |
31 | return False | |
32 | ||
a53c08bc | 33 | |
49581587 | 34 | BASENAME = "topolog" |
36d1dc45 RZ |
35 | |
36 | # Helper dictionary to convert Topogen logging levels to Python's logging. | |
37 | DEBUG_TOPO2LOGGING = { | |
787e7624 | 38 | "debug": logging.DEBUG, |
39 | "info": logging.INFO, | |
40 | "output": logging.INFO, | |
41 | "warning": logging.WARNING, | |
42 | "error": logging.ERROR, | |
43 | "critical": logging.CRITICAL, | |
36d1dc45 | 44 | } |
49581587 | 45 | FORMAT = "%(asctime)s.%(msecs)03d %(levelname)s: %(name)s: %(message)s" |
36d1dc45 | 46 | |
49581587 CH |
47 | handlers = {} |
48 | logger = logging.getLogger("topolog") | |
787e7624 | 49 | |
9427b78f | 50 | |
49581587 CH |
51 | def set_handler(l, target=None): |
52 | if target is None: | |
53 | h = logging.NullHandler() | |
54 | else: | |
55 | if isinstance(target, str): | |
56 | h = logging.FileHandler(filename=target, mode="w") | |
57 | else: | |
58 | h = logging.StreamHandler(stream=target) | |
59 | h.setFormatter(logging.Formatter(fmt=FORMAT)) | |
60 | # Don't filter anything at the handler level | |
61 | h.setLevel(logging.DEBUG) | |
62 | l.addHandler(h) | |
63 | return h | |
787e7624 | 64 | |
36d1dc45 | 65 | |
49581587 CH |
66 | def set_log_level(l, level): |
67 | "Set the logging level." | |
68 | # Messages sent to this logger only are created if this level or above. | |
69 | log_level = DEBUG_TOPO2LOGGING.get(level, level) | |
70 | l.setLevel(log_level) | |
787e7624 | 71 | |
77ebccac | 72 | |
49581587 CH |
73 | def get_logger(name, log_level=None, target=None): |
74 | l = logging.getLogger("{}.{}".format(BASENAME, name)) | |
36d1dc45 | 75 | |
49581587 CH |
76 | if log_level is not None: |
77 | set_log_level(l, log_level) | |
78 | ||
79 | if target is not None: | |
80 | set_handler(l, target) | |
81 | ||
82 | return l | |
83 | ||
84 | ||
85 | # nodeid: all_protocol_startup/test_all_protocol_startup.py::test_router_running | |
86 | ||
a53c08bc | 87 | |
49581587 CH |
88 | def get_test_logdir(nodeid=None): |
89 | """Get log directory relative pathname.""" | |
90 | xdist_worker = os.getenv("PYTEST_XDIST_WORKER", "") | |
a53c08bc | 91 | mode = os.getenv("PYTEST_XDIST_MODE", "no") |
49581587 CH |
92 | |
93 | if not nodeid: | |
94 | nodeid = os.environ["PYTEST_CURRENT_TEST"].split(" ")[0] | |
95 | ||
96 | cur_test = nodeid.replace("[", "_").replace("]", "_") | |
97 | path, testname = cur_test.split("::") | |
98 | path = path[:-3].replace("/", ".") | |
99 | ||
100 | # We use different logdir paths based on how xdist is running. | |
101 | if mode == "each": | |
102 | return os.path.join(path, testname, xdist_worker) | |
103 | elif mode == "load": | |
104 | return os.path.join(path, testname) | |
105 | else: | |
106 | assert ( | |
a53c08bc | 107 | mode == "no" or mode == "loadfile" or mode == "loadscope" |
49581587 CH |
108 | ), "Unknown dist mode {}".format(mode) |
109 | ||
110 | return path | |
111 | ||
112 | ||
113 | def logstart(nodeid, location, rundir): | |
114 | """Called from pytest before module setup.""" | |
115 | ||
a53c08bc | 116 | mode = os.getenv("PYTEST_XDIST_MODE", "no") |
49581587 CH |
117 | worker = os.getenv("PYTEST_TOPOTEST_WORKER", "") |
118 | ||
119 | # We only per-test log in the workers (or non-dist) | |
120 | if not worker and mode != "no": | |
121 | return | |
122 | ||
123 | handler_id = nodeid + worker | |
124 | assert handler_id not in handlers | |
125 | ||
126 | rel_log_dir = get_test_logdir(nodeid) | |
127 | exec_log_dir = os.path.join(rundir, rel_log_dir) | |
a53c08bc CH |
128 | subprocess.check_call( |
129 | "mkdir -p {0} && chmod 1777 {0}".format(exec_log_dir), shell=True | |
130 | ) | |
49581587 CH |
131 | exec_log_path = os.path.join(exec_log_dir, "exec.log") |
132 | ||
133 | # Add test based exec log handler | |
134 | h = set_handler(logger, exec_log_path) | |
135 | handlers[handler_id] = h | |
136 | ||
137 | if worker: | |
a53c08bc CH |
138 | logger.info( |
139 | "Logging on worker %s for %s into %s", worker, handler_id, exec_log_path | |
140 | ) | |
49581587 CH |
141 | else: |
142 | logger.info("Logging for %s into %s", handler_id, exec_log_path) | |
143 | ||
144 | ||
145 | def logfinish(nodeid, location): | |
146 | """Called from pytest after module teardown.""" | |
147 | # This function may not be called if pytest is interrupted. | |
148 | ||
149 | worker = os.getenv("PYTEST_TOPOTEST_WORKER", "") | |
150 | handler_id = nodeid + worker | |
151 | ||
152 | if handler_id in handlers: | |
153 | # Remove test based exec log handler | |
154 | if worker: | |
155 | logger.info("Closing logs for %s", handler_id) | |
156 | ||
157 | h = handlers[handler_id] | |
158 | logger.removeHandler(handlers[handler_id]) | |
159 | h.flush() | |
160 | h.close() | |
161 | del handlers[handler_id] | |
787e7624 | 162 | |
36d1dc45 | 163 | |
49581587 CH |
164 | console_handler = set_handler(logger, None) |
165 | set_log_level(logger, "debug") |