Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 1 | # Copyright lowRISC contributors. |
| 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| 3 | # SPDX-License-Identifier: Apache-2.0 |
| 4 | |
| 5 | import locale |
| 6 | import logging |
| 7 | import os |
| 8 | import re |
| 9 | import shlex |
| 10 | import signal |
| 11 | import subprocess |
| 12 | import time |
| 13 | from pathlib import Path |
| 14 | |
| 15 | import serial |
| 16 | |
| 17 | log = logging.getLogger(__name__) |
| 18 | |
| 19 | |
| 20 | class Process: |
| 21 | def __init__(self, |
| 22 | cmd, |
| 23 | logdir, |
| 24 | cwd=None, |
| 25 | startup_done_expect=None, |
| 26 | startup_timeout=None, |
| 27 | default_filter_func=None): |
| 28 | """Utility class used to spawn an interact with processes. |
| 29 | |
| 30 | Args: |
| 31 | cmd: Command line argument used to spawn process. |
| 32 | logdir: Directory used to store STDOUT and STDERR output. |
| 33 | cwd: Working directory used to spawn process. |
| 34 | startup_done_expect: Pattern used to check at process startup time. |
| 35 | startup_timeout: Timeout in seconds for |startup_done_expect| check. |
| 36 | default_filter_func: Default function to filter all stdout/stderr |
| 37 | output with when matching it with find_in_output(). |
| 38 | """ |
| 39 | if isinstance(cmd, str): |
| 40 | self.cmd = shlex.split(cmd) |
| 41 | else: |
| 42 | self.cmd = [str(c) for c in cmd] |
| 43 | self.logdir = str(logdir) |
| 44 | self.cwd = str(cwd) |
| 45 | self.startup_done_expect = startup_done_expect |
| 46 | self.startup_timeout = startup_timeout |
| 47 | self.proc = None |
| 48 | self.logger = logging.getLogger(__name__) |
| 49 | |
| 50 | self._f_stdout = None |
| 51 | self._f_stderr = None |
| 52 | self._f_stdout_r = None |
| 53 | self._f_stderr_r = None |
| 54 | |
| 55 | self.default_filter_func = default_filter_func |
| 56 | |
| 57 | def __del__(self): |
| 58 | try: |
| 59 | self.proc.kill() |
| 60 | self._f_stdout.close() |
| 61 | self._f_stderr.close() |
| 62 | self._f_stdout_r.close() |
| 63 | self._f_stderr_r.close() |
| 64 | except Exception: |
| 65 | pass |
| 66 | |
| 67 | def is_running(self): |
| 68 | """ Check if the process is running |
| 69 | |
| 70 | Returns: |
| 71 | True if the process is running, False otherwise |
| 72 | """ |
| 73 | return self.proc and self.proc.poll() is not None |
| 74 | |
| 75 | def run(self): |
| 76 | """Start process with command line configured in constructor.""" |
| 77 | cmd_name = os.path.basename(self.cmd[0]) |
| 78 | |
| 79 | # Enforce line-buffered STDOUT even when sending STDOUT/STDERR to file. |
| 80 | # If applications don't fflush() STDOUT manually, STDOUT goes through |
| 81 | # a 4kB buffer before we see any output, which prevents searching for |
| 82 | # the string indicating a successful startup. |
| 83 | # see discussion at http://www.pixelbeat.org/programming/stdio_buffering/ |
| 84 | cmd = ['stdbuf', '-oL'] + self.cmd |
Philipp Wagner | 4fffa38 | 2021-02-23 17:57:55 +0000 | [diff] [blame] | 85 | self.logger.info("Running command " + |
| 86 | ' '.join(shlex.quote(w) for w in cmd)) |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 87 | |
| 88 | logfile_stdout = os.path.join(self.logdir, |
| 89 | "{}.stdout".format(cmd_name)) |
| 90 | logfile_stderr = os.path.join(self.logdir, |
| 91 | "{}.stderr".format(cmd_name)) |
| 92 | self.logger.debug("Capturing STDOUT at " + logfile_stdout) |
| 93 | self.logger.debug("Capturing STDERR at " + logfile_stderr) |
| 94 | |
| 95 | self._f_stdout = open(logfile_stdout, 'w') |
| 96 | self._f_stderr = open(logfile_stderr, 'w') |
| 97 | self.proc = subprocess.Popen(cmd, |
| 98 | cwd=self.cwd, |
| 99 | universal_newlines=True, |
Rupert Swarbrick | d8dca9f | 2020-10-27 13:43:01 +0000 | [diff] [blame] | 100 | bufsize=1, # Use line buffering |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 101 | stdin=subprocess.PIPE, |
| 102 | stdout=self._f_stdout, |
| 103 | stderr=self._f_stderr) |
| 104 | |
| 105 | self._f_stdout_r = open(logfile_stdout, 'r') |
| 106 | self._f_stderr_r = open(logfile_stderr, 'r') |
| 107 | |
| 108 | # no startup match pattern given => startup done! |
| 109 | if self.startup_done_expect is None: |
| 110 | return True |
| 111 | |
| 112 | # check if the string indicating a successful startup appears in the |
Rupert Swarbrick | 1de9b33 | 2020-10-27 13:39:42 +0000 | [diff] [blame] | 113 | # the program output (STDOUT or STDERR). |
| 114 | try: |
| 115 | init_done = self.find_in_output(pattern=self.startup_done_expect, |
| 116 | timeout=self.startup_timeout) |
| 117 | if init_done is None: |
| 118 | raise RuntimeError('No match for pattern {!r} in startup.' |
| 119 | .format(str(self.startup_done_expect))) |
| 120 | except subprocess.TimeoutExpired as err: |
| 121 | # On time-out, find_in_output will raise a TimeoutExpired exception |
| 122 | # but (of course) it doesn't know the command it's running, so we |
| 123 | # amend it as it goes past. |
| 124 | assert err.cmd is None |
| 125 | err.cmd = cmd |
| 126 | raise |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 127 | |
| 128 | self.logger.info("Startup sequence matched, startup done.") |
| 129 | |
| 130 | return True |
| 131 | |
| 132 | def terminate(self): |
| 133 | """Terminates process started by run call.""" |
| 134 | if self.proc is not None: |
| 135 | self.proc.terminate() |
| 136 | |
| 137 | def send_ctrl_c(self): |
| 138 | """Sends SIGINT to process started by run call.""" |
| 139 | if self.proc is not None: |
| 140 | self.proc.send_signal(signal.SIGINT) |
| 141 | |
| 142 | def expect(self, stdin_data=None, pattern=None, timeout=None): |
| 143 | """Write data to STDIN and check if the output is as expected. |
| 144 | |
| 145 | Args: |
| 146 | stdin_data: Data to send to STDIN. |
| 147 | pattern: Pattern to search for after sending |stdin_data|. |
| 148 | timeout: Timeout in seconds for |pattern| check. |
| 149 | Returns: |
| 150 | True if |pattern| found, False otherwise. |
| 151 | """ |
| 152 | # We don't get STDOUT/STDERR from subprocess.communicate() as it's |
| 153 | # redirected to file. We need to read the files instead. |
| 154 | |
| 155 | # XXX: races (false positives) can happen here if output is generated |
| 156 | # before the input is sent to the process. |
| 157 | if pattern is None: |
| 158 | self._f_stdout_r.seek(0, 2) |
| 159 | |
| 160 | self.proc.stdin.write(stdin_data) |
| 161 | self.proc.stdin.flush() |
| 162 | |
| 163 | if pattern is None: |
| 164 | return True |
| 165 | |
| 166 | return self.find_in_output(pattern, timeout) is not None |
| 167 | |
| 168 | def find_in_output(self, |
| 169 | pattern, |
| 170 | timeout, |
| 171 | from_start=False, |
| 172 | filter_func=None): |
| 173 | """Read STDOUT and STDERR to find an expected pattern. |
| 174 | |
| 175 | See find_in_files() for more documentation. |
| 176 | """ |
| 177 | |
| 178 | if filter_func is None: |
| 179 | filter_func = self.default_filter_func |
| 180 | |
| 181 | def wait_func(): |
| 182 | """ Wait up to 0.2s until the process terminates. |
| 183 | |
| 184 | Returns: |
| 185 | True if the subprocess terminated and a further wait will not |
| 186 | produce more output, False otherwise. |
| 187 | """ |
| 188 | try: |
| 189 | self.proc.wait(timeout=0.2) |
| 190 | except subprocess.TimeoutExpired: |
| 191 | # The process did not yet terminate. |
| 192 | return False |
| 193 | |
| 194 | # The process did terminate. |
| 195 | return True |
| 196 | |
| 197 | return find_in_files([self._f_stdout_r, self._f_stderr_r], |
| 198 | pattern, |
| 199 | timeout, |
| 200 | filter_func=filter_func, |
| 201 | from_start=from_start, |
| 202 | wait_func=wait_func) |
| 203 | |
| 204 | |
| 205 | class LoggingSerial(serial.Serial): |
Pirmin Vogel | ad2afea | 2021-05-20 10:43:40 +0200 | [diff] [blame] | 206 | """ Access to a serial console which logs all read/written data to file. """ |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 207 | def __init__(self, |
| 208 | *args, |
| 209 | log_dir_path, |
| 210 | default_filter_func=None, |
| 211 | **kwargs): |
| 212 | super().__init__(*args, **kwargs) |
| 213 | |
| 214 | self._log_to_device_fp = open(str(log_dir_path / 'to-device.log'), |
| 215 | 'wb') |
| 216 | self._log_from_device_fp = open(str(log_dir_path / 'from-device.log'), |
| 217 | 'wb') |
| 218 | |
| 219 | self.default_filter_func = default_filter_func |
| 220 | |
| 221 | log.debug("Logging UART communication for {} to {}".format( |
| 222 | self.port, str(log_dir_path))) |
| 223 | |
| 224 | def read(self, size=1): |
| 225 | bytes = super().read(size) |
| 226 | self._log_from_device_fp.write(bytes) |
| 227 | |
| 228 | # Explicitly flush the log file to ensure that data is present in the |
| 229 | # log file when the conftest log dumping routines read the file after a |
| 230 | # failed test. |
| 231 | self._log_from_device_fp.flush() |
| 232 | |
| 233 | return bytes |
| 234 | |
| 235 | def write(self, data): |
| 236 | retcode = super().write(data) |
| 237 | self._log_to_device_fp.write(data) |
| 238 | |
| 239 | # Explicitly flush the log file to ensure that data is present in the |
| 240 | # log file when the conftest log dumping routines read the file after a |
| 241 | # failed test. |
| 242 | self._log_to_device_fp.flush() |
| 243 | |
| 244 | return retcode |
| 245 | |
| 246 | def close(self): |
| 247 | if self._log_to_device_fp: |
| 248 | self._log_to_device_fp.close() |
| 249 | |
| 250 | if self._log_from_device_fp: |
| 251 | self._log_from_device_fp.close() |
| 252 | |
| 253 | super().close() |
| 254 | |
Philipp Wagner | 9cc6766 | 2020-09-09 19:47:41 +0100 | [diff] [blame] | 255 | def log_add_marker(self, text: str): |
| 256 | """ Write a marker text into the UART send and receive log files """ |
| 257 | text_b = text.encode('UTF-8') |
| 258 | self._log_to_device_fp.write(text_b) |
| 259 | self._log_from_device_fp.write(text_b) |
| 260 | |
| 261 | def drain_in(self, timeout=None, silence_time=.5): |
| 262 | """ Read all available input data |
| 263 | |
| 264 | Args: |
| 265 | timeout: Maximum time this function blocks, in seconds. |
| 266 | silence_time: Consider the input drained if no new data can be read |
| 267 | after this time, in seconds. |
| 268 | Returns: |
| 269 | The data read from the device. |
| 270 | """ |
| 271 | t_end = None |
| 272 | if timeout is not None: |
| 273 | t_end = time.time() + timeout |
| 274 | |
| 275 | read_data = b'' |
| 276 | |
| 277 | first_iteration = True |
| 278 | while first_iteration or self.in_waiting != 0: |
| 279 | if timeout is not None and time.time() >= t_end: |
| 280 | break |
| 281 | |
| 282 | read_data += self.read(self.in_waiting) |
| 283 | first_iteration = False |
| 284 | time.sleep(silence_time) |
| 285 | |
| 286 | return read_data |
| 287 | |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 288 | def find_in_output(self, pattern, timeout=None, filter_func=None): |
| 289 | """ Expect a pattern to appear in one of the output lines. |
| 290 | |
| 291 | See the documentation of match_line() for a description of |pattern|. |
| 292 | |
| 293 | Args: |
| 294 | pattern: Pattern to search for |
| 295 | timeout: Timeout in seconds for |pattern| check. |
| 296 | filter_func: Function to filter the output with before applying |
| 297 | |pattern|. If none, the |self.default_filter_func| is used. |
| 298 | Returns: |
| 299 | None if |pattern| was not found, the return value of match_line() |
| 300 | otherwise. |
| 301 | """ |
| 302 | |
| 303 | if filter_func is None: |
| 304 | filter_func = self.default_filter_func |
| 305 | |
| 306 | t_end = None |
| 307 | if timeout is not None: |
| 308 | t_end = time.time() + timeout |
| 309 | |
| 310 | line = self.readline() |
| 311 | while True: |
| 312 | m = match_line(line, pattern, filter_func) |
| 313 | if m is not None: |
| 314 | return m |
| 315 | |
| 316 | if timeout is not None and time.time() >= t_end: |
| 317 | break |
| 318 | |
| 319 | line = self.readline() |
| 320 | |
| 321 | return None |
| 322 | |
| 323 | |
| 324 | def dump_temp_files(tmp_path): |
| 325 | """ Dump all files in a directory (typically logs) """ |
| 326 | logging.debug( |
| 327 | "================= DUMP OF ALL TEMPORARY FILES =================") |
| 328 | |
| 329 | tmp_files = [ |
| 330 | Path(root) / f for root, dirs, files in os.walk(str(tmp_path)) |
| 331 | for f in files |
| 332 | ] |
| 333 | |
| 334 | textchars = bytearray({7, 8, 9, 10, 12, 13, 27} | |
| 335 | set(range(0x20, 0x100)) - {0x7f}) |
| 336 | |
| 337 | def is_binary_string(bytes): |
| 338 | return bool(bytes.translate(None, textchars)) |
| 339 | |
| 340 | for f in tmp_files: |
| 341 | if f.name == '.lock': |
| 342 | continue |
| 343 | |
| 344 | logging.debug("vvvvvvvvvvvvvvvvvvvv {} vvvvvvvvvvvvvvvvvvvv".format(f)) |
| 345 | |
| 346 | if not f.is_file(): |
| 347 | logging.debug("[Not a regular file.]") |
| 348 | elif f.stat().st_size > 50 * 1024: |
| 349 | logging.debug("[File is too large to be shown ({} bytes).]".format( |
| 350 | f.stat().st_size)) |
| 351 | else: |
| 352 | with open(str(f), 'rb') as fp: |
| 353 | data = fp.read(1024) |
| 354 | if is_binary_string(data): |
| 355 | logging.debug( |
| 356 | "[File contains {} bytes of binary data.]".format( |
| 357 | f.stat().st_size)) |
| 358 | else: |
| 359 | fp.seek(0) |
| 360 | for line in fp: |
| 361 | line_str = line.decode(locale.getpreferredencoding(), |
| 362 | errors='backslashreplace') |
| 363 | logging.debug(line_str.rstrip()) |
| 364 | logging.debug( |
| 365 | "^^^^^^^^^^^^^^^^^^^^ {} ^^^^^^^^^^^^^^^^^^^^\n".format(f)) |
| 366 | |
| 367 | |
Philipp Wagner | bb63cc9 | 2021-02-01 12:09:31 +0000 | [diff] [blame] | 368 | def load_sw_over_spi(tmp_path, |
Pirmin Vogel | ad2afea | 2021-05-20 10:43:40 +0200 | [diff] [blame] | 369 | loader_path, |
Philipp Wagner | bb63cc9 | 2021-02-01 12:09:31 +0000 | [diff] [blame] | 370 | sw_test_bin, |
Pirmin Vogel | ad2afea | 2021-05-20 10:43:40 +0200 | [diff] [blame] | 371 | loader_args=[], |
Philipp Wagner | bb63cc9 | 2021-02-01 12:09:31 +0000 | [diff] [blame] | 372 | timeout=600): |
Pirmin Vogel | ad2afea | 2021-05-20 10:43:40 +0200 | [diff] [blame] | 373 | """ Use the specified loader utility to load software onto a device. """ |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 374 | |
| 375 | log.info("Flashing device software from {} over SPI".format( |
| 376 | str(sw_test_bin))) |
| 377 | |
Pirmin Vogel | ad2afea | 2021-05-20 10:43:40 +0200 | [diff] [blame] | 378 | # cw310_loader.py supports both loading bistream and firmware. |
| 379 | if re.search('cw310_loader', str(loader_path)): |
| 380 | bin_arg = '--firmware' |
| 381 | else: # spiflash loads firmware only. |
| 382 | bin_arg = '--input' |
| 383 | cmd_flash = [loader_path, bin_arg, sw_test_bin] + loader_args |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 384 | p_flash = Process(cmd_flash, logdir=tmp_path, cwd=tmp_path) |
| 385 | p_flash.run() |
Philipp Wagner | bb63cc9 | 2021-02-01 12:09:31 +0000 | [diff] [blame] | 386 | p_flash.proc.wait(timeout=timeout) |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 387 | assert p_flash.proc.returncode == 0 |
| 388 | |
| 389 | log.info("Device software flashed.") |
| 390 | |
| 391 | |
| 392 | def find_in_files(file_objects, |
| 393 | pattern, |
| 394 | timeout, |
| 395 | from_start=False, |
| 396 | filter_func=None, |
| 397 | wait_func=None): |
| 398 | """Find a pattern in a list of file objects (file descriptors) |
| 399 | |
| 400 | See the documentation of match_line() for a description of |pattern|. |
| 401 | |
| 402 | Args: |
| 403 | pattern: Pattern to search for |
| 404 | timeout: Timeout in seconds for |pattern| check. Set to None to wait |
| 405 | indefinitely. |
| 406 | from_start: Search from the start of the given file objects. |
| 407 | filter_func: Function to filter the output with before applying |
| 408 | |pattern|. If none, the |default_filter_func| is used. |
| 409 | wait_func: Function to call to wait. The function is expected to return |
| 410 | True if no further output is expected in |file_objects| (this will |
| 411 | end the wait loop before |timeout| expires), and False otherwise |
| 412 | (more output could be produced). |
Rupert Swarbrick | 1de9b33 | 2020-10-27 13:39:42 +0000 | [diff] [blame] | 413 | |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 414 | Returns: |
Rupert Swarbrick | 1de9b33 | 2020-10-27 13:39:42 +0000 | [diff] [blame] | 415 | If |pattern| was found, returns the return value of match_line(). |
| 416 | Otherwise, returns None if |wait_func| returned True (signalling the |
| 417 | end of the input with no match). |
| 418 | |
| 419 | Raises a subprocess.TimeoutExpired exception on timeout. This will have |
| 420 | a |cmd| field of None. |
| 421 | |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 422 | """ |
| 423 | |
| 424 | t_end = None |
| 425 | if timeout is not None: |
| 426 | t_end = time.time() + timeout |
| 427 | |
| 428 | if wait_func is None: |
| 429 | # By default, sleep for 200 ms when waiting for new input. |
| 430 | def wait_func(): |
| 431 | time.sleep(.2) |
| 432 | return False |
| 433 | |
| 434 | if from_start: |
| 435 | for file_object in file_objects: |
| 436 | file_object.seek(0) |
| 437 | |
Philipp Wagner | ad0fb5d | 2021-01-19 17:08:03 +0000 | [diff] [blame] | 438 | end_loop = False |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 439 | while True: |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 440 | for file_object in file_objects: |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 441 | for line in file_object: |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 442 | m = match_line(line.rstrip(), pattern, filter_func) |
| 443 | if m is not None: |
| 444 | return m |
| 445 | |
Philipp Wagner | ad0fb5d | 2021-01-19 17:08:03 +0000 | [diff] [blame] | 446 | if end_loop: |
| 447 | break |
| 448 | |
Philipp Wagner | 52fce15 | 2020-11-06 15:38:15 +0000 | [diff] [blame] | 449 | if timeout is not None and time.time() >= t_end: |
Rupert Swarbrick | 1de9b33 | 2020-10-27 13:39:42 +0000 | [diff] [blame] | 450 | raise subprocess.TimeoutExpired(None, timeout) |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 451 | |
Philipp Wagner | ad0fb5d | 2021-01-19 17:08:03 +0000 | [diff] [blame] | 452 | # The wait function returns True to indicate that no more data will be |
| 453 | # produced (e.g. because the producing process terminated). But we still |
| 454 | # need to check one last time if the already produced data is matching |
| 455 | # the `pattern`. |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 456 | end_loop = wait_func() |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 457 | |
| 458 | return None |
| 459 | |
| 460 | |
| 461 | def match_line(line, pattern, filter_func=None): |
| 462 | """ |
| 463 | Check if a line matches a pattern |
| 464 | |
| 465 | Line endings (CR/LR) are removed from |line| and neither matched nor |
| 466 | returned. |
| 467 | |
| 468 | pattern can be of two types: |
| 469 | 1. A regular expression (any object with a match attribute, or a re.Pattern |
| 470 | object in Python 3.7+). In this case, the pattern is matched against all |
| 471 | lines and the result of re.match(pattern) (a re.Match object since |
Philipp Wagner | 2ba5f99 | 2021-02-24 14:46:17 +0000 | [diff] [blame] | 472 | Python 3.7) is returned. Note that re.match() always matches from the |
| 473 | beginning of the line. |
Philipp Wagner | 03aaf32 | 2020-09-09 19:11:44 +0100 | [diff] [blame] | 474 | 2. A string. In this case pattern is compared to each line with |
| 475 | startswith(), and the full matching line is returned on a match. |
| 476 | If no match is found None is returned. |
| 477 | |
| 478 | Optionally apply a filter to the line before matching it. |
| 479 | """ |
| 480 | |
| 481 | if isinstance(line, bytes): |
| 482 | line = line.rstrip(b'\r\n') |
| 483 | else: |
| 484 | line = line.rstrip('\r\n') |
| 485 | |
| 486 | if filter_func is not None: |
| 487 | line = filter_func(line) |
| 488 | |
| 489 | # TODO: Check for an instance of re.Pattern once we move to Python 3.7+ |
| 490 | if hasattr(pattern, "match"): |
| 491 | return pattern.match(line) |
| 492 | else: |
| 493 | if line.startswith(pattern): |
| 494 | return line |
| 495 | |
| 496 | return None |
| 497 | |
| 498 | |
| 499 | def filter_remove_device_sw_log_prefix(line): |
| 500 | """ |
| 501 | Remove the file/line information in log messages produced by device software |
| 502 | LOG() macros. |
| 503 | """ |
| 504 | |
| 505 | # See base_log_internal_core() in lib/base/log.c for the format description. |
| 506 | pattern = r'^[IWEF?]\d{5} [a-zA-Z0-9\.-_]+:\d+\] ' |
| 507 | if isinstance(line, bytes): |
| 508 | return re.sub(bytes(pattern, encoding='utf-8'), b'', line) |
| 509 | else: |
| 510 | return re.sub(pattern, '', line) |
Philipp Wagner | c208de0 | 2021-01-19 17:20:11 +0000 | [diff] [blame] | 511 | |
| 512 | |
| 513 | def filter_remove_sw_test_status_log_prefix(line): |
| 514 | """ |
| 515 | Remove the logging prefix produced by the sw_test_status DV component. |
| 516 | """ |
| 517 | |
| 518 | # Example of a full prefix to be matched: |
Pirmin Vogel | 25105dd | 2021-05-24 14:28:23 +0200 | [diff] [blame] | 519 | # 1629002: (../src/lowrisc_dv_sw_test_status_0/sw_test_status_if.sv:42) [TOP.chip_earlgrey_verilator.u_sw_test_status_if] # noqa: E501 |
Philipp Wagner | c208de0 | 2021-01-19 17:20:11 +0000 | [diff] [blame] | 520 | pattern = r'\d+: \(.+/sw_test_status_if\.sv:\d+\) \[TOP\..+\] ' |
| 521 | if isinstance(line, bytes): |
| 522 | return re.sub(bytes(pattern, encoding='utf-8'), b'', line) |
| 523 | else: |
| 524 | return re.sub(pattern, '', line) |