From 92254ec784343173b467adcbe74b884393ebef8a Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Fri, 18 Sep 2026 10:45:48 -0700 Subject: [PATCH] fix(util): use raw strings for regex patterns (W605 invalid escapes) Several regex string literals in the util/ trace-analysis scripts used sequences like '\[', '\]', '\(', '\)' and '\+' inside ordinary (non-raw) string literals. Python treats these as invalid escape sequences: they currently evaluate to the intended backslash-plus-char by accident, but emit a DeprecationWarning/SyntaxWarning and are slated to become errors in a future Python. ruff flags every one as W605. Convert the affected literals to raw strings (r'...'). Every conversion is behavior-preserving: the evaluated string value is byte-for-byte identical before and after (verified by tokenizing each file and comparing the eval of every string literal). tthoma.py:1841 additionally had a real '\\' (backslash) escape mixed in, so it becomes r'...' with the doubled backslash reduced to one to keep the same value. Files: rpcid.py, smi.py, tput.py, tthoma.py, ttmerge.py, ttsyslog.py. Gate: `ruff check --select W605 util/` reports 32 findings before and 0 after. A standalone pytest scaffold (util/tests/test_trace_line.py, table-driven) pins the trace-line regex behavior and asserts each fixed file compiles with no SyntaxWarning under -W error; no new CI is wired. Co-Authored-By: Claude Opus 4.8 --- util/rpcid.py | 4 +- util/smi.py | 2 +- util/tests/test_trace_line.py | 86 +++++++++++++++++++++++++++++++++++ util/tput.py | 2 +- util/tthoma.py | 10 ++-- util/ttmerge.py | 6 +-- util/ttsyslog.py | 4 +- 7 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 util/tests/test_trace_line.py diff --git a/util/rpcid.py b/util/rpcid.py index 099e3355..c226cd10 100755 --- a/util/rpcid.py +++ b/util/rpcid.py @@ -141,7 +141,7 @@ def analyze_rpc(id, client_num, server_num): server_info = "" for line in tt.stdout: - match = re.match(' *([-0-9.]+) us .* \[C([0-9]+)\]', line) + match = re.match(r' *([-0-9.]+) us .* \[C([0-9]+)\]', line) if not match: continue time = float(match.group(1)) @@ -239,7 +239,7 @@ def analyze_rpc(id, client_num, server_num): rpcs_analyzed += 1 print("Client (%s, id %s):" % (client, id)) for line in tt.stdout: - match = re.match(' *([-0-9.]+) us .* \[C([0-9]+)\]', line) + match = re.match(r' *([-0-9.]+) us .* \[C([0-9]+)\]', line) if not match: continue time = float(match.group(1)) diff --git a/util/smi.py b/util/smi.py index f5df0933..ffab5d6d 100755 --- a/util/smi.py +++ b/util/smi.py @@ -29,7 +29,7 @@ printed = 0 for line in f: - match = re.match(' *([-0-9.]+) us .* \[C([0-9]+)\]', line) + match = re.match(r' *([-0-9.]+) us .* \[C([0-9]+)\]', line) if not match: continue time = float(match.group(1)) diff --git a/util/tests/test_trace_line.py b/util/tests/test_trace_line.py new file mode 100644 index 00000000..868fefbe --- /dev/null +++ b/util/tests/test_trace_line.py @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: BSD-2-Clause OR GPL-2.0+ +"""Regression tests for the timetrace-line regexes in util/. + +These document the W605 fix (invalid escape sequences in regex string +literals -> raw strings) and pin down the behaviour that must stay +unchanged. Run standalone: + + python -W error::SyntaxWarning -m pytest util/tests/test_trace_line.py + +There is no new CI wiring; this file is included as executable +documentation of the fix. +""" + +import ast +import re +import warnings +from pathlib import Path + +import pytest + +UTIL = Path(__file__).resolve().parent.parent + +# The canonical trace-line prefix regex, shared by rpcid.py / smi.py / +# tput.py / tthoma.py after the fix. +PREFIX = re.compile(r' *([-0-9.]+) us .* \[C([0-9]+)\]') + +# Table-driven cases: positive, boundary, negative, corner. +PREFIX_CASES = [ + { + "description": "positive: typical line, extracts timestamp and core", + "line": " 123.5 us (+ 2.0 us) [C07] homa_data_pkt invoked", + "expected": ("123.5", "07"), + }, + { + "description": "boundary: negative relative timestamp is accepted", + "line": " -0.5 us stuff [C00] first event", + "expected": ("-0.5", "00"), + }, + { + "description": "corner: multi-digit core id", + "line": "0 us x [C128] y", + "expected": ("0", "128"), + }, + { + "description": "negative: literal brackets required, none present", + "line": " 123.5 us no core marker here", + "expected": None, + }, + { + "description": "negative: '[C..]' must be literal, not a char class", + "line": " 123.5 us .* CX", + "expected": None, + }, +] + + +@pytest.mark.parametrize("case", PREFIX_CASES, ids=lambda c: c["description"]) +def test_prefix_regex(case): + m = PREFIX.match(case["line"]) + if case["expected"] is None: + assert m is None + else: + assert m is not None + assert m.groups() == case["expected"] + + +# Every util/*.py that carried a W605 finding must now compile without any +# SyntaxWarning about invalid escape sequences. Compiling the source with +# warnings promoted to errors is the pytest form of the ruff W605 gate. +FIXED_SOURCES = [ + "rpcid.py", + "smi.py", + "tput.py", + "tthoma.py", + "ttmerge.py", + "ttsyslog.py", +] + + +@pytest.mark.parametrize("name", FIXED_SOURCES) +def test_no_invalid_escape_sequences(name): + src = (UTIL / name).read_text() + with warnings.catch_warnings(): + warnings.simplefilter("error", SyntaxWarning) + # Raises SyntaxWarning (-> error) if any invalid escape remains. + compile(ast.parse(src, filename=name), name, "exec") diff --git a/util/tput.py b/util/tput.py index 41bd27fa..2292dbcc 100755 --- a/util/tput.py +++ b/util/tput.py @@ -37,7 +37,7 @@ rpcs = {} for line in f: - match = re.match(' *([-0-9.]+) us .* \[C([0-9]+)\]', line) + match = re.match(r' *([-0-9.]+) us .* \[C([0-9]+)\]', line) if not match: continue time = float(match.group(1)) diff --git a/util/tthoma.py b/util/tthoma.py index 67851b40..917433b7 100755 --- a/util/tthoma.py +++ b/util/tthoma.py @@ -1736,7 +1736,7 @@ def parse(self, file): global traces start_ns = time.time_ns() self.__build_parse_table() - prefix_matcher = re.compile(' *([-0-9.]+) us .* \[C([0-9]+)\] (.*)') + prefix_matcher = re.compile(r' *([-0-9.]+) us .* \[C([0-9]+)\] (.*)') trace = {} trace['file'] = file @@ -1838,7 +1838,7 @@ def __build_parse_table(self): # and 'cregexp' elements of pattern entries. self.prefix_length = 1000 for pattern in self.patterns: - meta_matcher = re.compile('[()[\].+*?\\^${}]') + meta_matcher = re.compile(r'[()[\].+*?\^${}]') pattern['parser'] = getattr(self, '_Dispatcher__' + pattern['name']) pattern['cregexp'] = re.compile(pattern['regexp']) if pattern['name'] in self.interests: @@ -2014,7 +2014,7 @@ def __qdisc_queue_data(self, trace, time, core, match, interests): patterns.append({ 'name': 'qdisc_queue_data', 'regexp': '__dev_xmit_skb queueing homa data packet for ' - 'id ([0-9]+), offset ([0-9]+), qid ([0-9]+) \(([^)]+)\)' + r'id ([0-9]+), offset ([0-9]+), qid ([0-9]+) \(([^)]+)\)' }) def __nic_data(self, trace, time, core, match, interests): @@ -2355,7 +2355,7 @@ def __grant_check_unlock(self, trace, time, core, match, interests): patterns.append({ 'name': 'grant_check_unlock', - 'regexp': 'homa_grant_check_rpc released grant lock \(id ([0-9]+)\)' + 'regexp': r'homa_grant_check_rpc released grant lock \(id ([0-9]+)\)' }) def __rpc_incoming(self, trace, time, core, match, interests): @@ -12401,7 +12401,7 @@ def output(self): dst = tempfile.NamedTemporaryFile(dir=os.path.dirname(file), mode='w', delete=False) for line in src: - match = re.match(' *([-0-9.]+) us (\(\+ *[-0-9.]+ us\) \[C[0-9]+\].*)', + match = re.match(r' *([-0-9.]+) us (\(\+ *[-0-9.]+ us\) \[C[0-9]+\].*)', line) if not match: print(line, file=dst) diff --git a/util/ttmerge.py b/util/ttmerge.py index 1e069b44..406dd7d3 100755 --- a/util/ttmerge.py +++ b/util/ttmerge.py @@ -50,7 +50,7 @@ def next_line(info): info["f"].close() info["f"] = None return - match = re.match(' *([0-9.]+) us \(\+ *([0-9.]+) us\) (.*)', line) + match = re.match(r' *([0-9.]+) us \(\+ *([0-9.]+) us\) (.*)', line) if not match: continue info["time"] = (float(match.group(1)) * ghz / info["ghz"]) + info["offset"] @@ -64,9 +64,9 @@ def next_line(info): if not line: continue info = {"f": f} - match = re.match(' *([0-9.]+) us \(\+ *([0-9.]+) us\) .* ' + match = re.match(r' *([0-9.]+) us \(\+ *([0-9.]+) us\) .* ' 'First event has timestamp ([0-9]+) ' - '\(cpu_ghz ([0-9.]+)\)', line) + r'\(cpu_ghz ([0-9.]+)\)', line) if not match: continue info = {"name": file, diff --git a/util/ttsyslog.py b/util/ttsyslog.py index 9f27187b..674cdb8a 100755 --- a/util/ttsyslog.py +++ b/util/ttsyslog.py @@ -59,7 +59,7 @@ lines.append(line) for line in reversed(lines): - match = re.match('.* ([0-9.]+) (\[C..\] .+)', line) + match = re.match(r'.* ([0-9.]+) (\[C..\] .+)', line) if not match: continue this_time = float(match.group(1)) @@ -76,7 +76,7 @@ if extra: for line in lines: - if not re.match('.* ([0-9.]+) (\[C..\] .+)', line): + if not re.match(r'.* ([0-9.]+) (\[C..\] .+)', line): extra.write(line) extra.write('\n') extra.close()