Skip to content

Commit 81480e6

Browse files
gh-124190: Ignore files directories check warning tooling (#124193)
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
1 parent 646f16b commit 81480e6

File tree

4 files changed

+105
-47
lines changed

4 files changed

+105
-47
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add capability to ignore entire files or directories in check warning CI tool

Tools/build/.warningignore_macos

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
Modules/expat/siphash.h 7
77
Modules/expat/xmlparse.c 8
88
Modules/expat/xmltok.c 3
9-
Modules/expat/xmltok_impl.c 26
9+
Modules/expat/xmltok_impl.c 26

Tools/build/.warningignore_ubuntu

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@
33
# Keep lines sorted lexicographically to help avoid merge conflicts.
44
# Format example:
55
# /path/to/file (number of warnings in file)
6-

Tools/build/check_warnings.py

Lines changed: 103 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,49 @@
1111
from typing import NamedTuple
1212

1313

14-
class FileWarnings(NamedTuple):
15-
name: str
14+
class IgnoreRule(NamedTuple):
15+
file_path: str
1616
count: int
17+
ignore_all: bool = False
18+
is_directory: bool = False
19+
20+
21+
def parse_warning_ignore_file(file_path: str) -> set[IgnoreRule]:
22+
"""
23+
Parses the warning ignore file and returns a set of IgnoreRules
24+
"""
25+
files_with_expected_warnings = set()
26+
with Path(file_path).open(encoding="UTF-8") as ignore_rules_file:
27+
files_with_expected_warnings = set()
28+
for i, line in enumerate(ignore_rules_file):
29+
line = line.strip()
30+
if line and not line.startswith("#"):
31+
line_parts = line.split()
32+
if len(line_parts) >= 2:
33+
file_name = line_parts[0]
34+
count = line_parts[1]
35+
ignore_all = count == "*"
36+
is_directory = file_name.endswith("/")
37+
38+
# Directories must have a wildcard count
39+
if is_directory and count != "*":
40+
print(
41+
f"Error parsing ignore file: {file_path} at line: {i}"
42+
)
43+
print(
44+
f"Directory {file_name} must have count set to *"
45+
)
46+
sys.exit(1)
47+
if ignore_all:
48+
count = 0
49+
50+
files_with_expected_warnings.add(
51+
IgnoreRule(
52+
file_name, int(count), ignore_all, is_directory
53+
)
54+
)
55+
56+
return files_with_expected_warnings
1757

1858

1959
def extract_warnings_from_compiler_output(
@@ -48,11 +88,15 @@ def extract_warnings_from_compiler_output(
4888
"line": match.group("line"),
4989
"column": match.group("column"),
5090
"message": match.group("message"),
51-
"option": match.group("option").lstrip("[").rstrip("]"),
91+
"option": match.group("option")
92+
.lstrip("[")
93+
.rstrip("]"),
5294
}
5395
)
5496
except:
55-
print(f"Error parsing compiler output. Unable to extract warning on line {i}:\n{line}")
97+
print(
98+
f"Error parsing compiler output. Unable to extract warning on line {i}:\n{line}"
99+
)
56100
sys.exit(1)
57101

58102
return compiler_warnings
@@ -78,9 +122,24 @@ def get_warnings_by_file(warnings: list[dict]) -> dict[str, list[dict]]:
78122
return warnings_by_file
79123

80124

125+
def is_file_ignored(
126+
file_path: str, ignore_rules: set[IgnoreRule]
127+
) -> IgnoreRule | None:
128+
"""
129+
Returns the IgnoreRule object for the file path if there is a related rule for it
130+
"""
131+
for rule in ignore_rules:
132+
if rule.is_directory:
133+
if file_path.startswith(rule.file_path):
134+
return rule
135+
elif file_path == rule.file_path:
136+
return rule
137+
return None
138+
139+
81140
def get_unexpected_warnings(
82-
files_with_expected_warnings: set[FileWarnings],
83-
files_with_warnings: set[FileWarnings],
141+
ignore_rules: set[IgnoreRule],
142+
files_with_warnings: set[IgnoreRule],
84143
) -> int:
85144
"""
86145
Returns failure status if warnings discovered in list of warnings
@@ -89,14 +148,21 @@ def get_unexpected_warnings(
89148
"""
90149
unexpected_warnings = {}
91150
for file in files_with_warnings.keys():
92-
found_file_in_ignore_list = False
93-
for ignore_file in files_with_expected_warnings:
94-
if file == ignore_file.name:
95-
if len(files_with_warnings[file]) > ignore_file.count:
96-
unexpected_warnings[file] = (files_with_warnings[file], ignore_file.count)
97-
found_file_in_ignore_list = True
98-
break
99-
if not found_file_in_ignore_list:
151+
152+
rule = is_file_ignored(file, ignore_rules)
153+
154+
if rule:
155+
if rule.ignore_all:
156+
continue
157+
158+
if len(files_with_warnings[file]) > rule.count:
159+
unexpected_warnings[file] = (
160+
files_with_warnings[file],
161+
rule.count,
162+
)
163+
continue
164+
elif rule is None:
165+
# If the file is not in the ignore list, then it is unexpected
100166
unexpected_warnings[file] = (files_with_warnings[file], 0)
101167

102168
if unexpected_warnings:
@@ -115,19 +181,27 @@ def get_unexpected_warnings(
115181

116182

117183
def get_unexpected_improvements(
118-
files_with_expected_warnings: set[FileWarnings],
119-
files_with_warnings: set[FileWarnings],
184+
ignore_rules: set[IgnoreRule],
185+
files_with_warnings: set[IgnoreRule],
120186
) -> int:
121187
"""
122-
Returns failure status if there are no warnings in the list of warnings
123-
for a file that is in the list of files with expected warnings
188+
Returns failure status if the number of warnings for a file is greater
189+
than the expected number of warnings for that file based on the ignore
190+
rules
124191
"""
125192
unexpected_improvements = []
126-
for file in files_with_expected_warnings:
127-
if file.name not in files_with_warnings.keys():
128-
unexpected_improvements.append((file.name, file.count, 0))
129-
elif len(files_with_warnings[file.name]) < file.count:
130-
unexpected_improvements.append((file.name, file.count, len(files_with_warnings[file.name])))
193+
for rule in ignore_rules:
194+
if not rule.ignore_all and rule.file_path not in files_with_warnings.keys():
195+
if rule.file_path not in files_with_warnings.keys():
196+
unexpected_improvements.append((rule.file_path, rule.count, 0))
197+
elif len(files_with_warnings[rule.file_path]) < rule.count:
198+
unexpected_improvements.append(
199+
(
200+
rule.file_path,
201+
rule.count,
202+
len(files_with_warnings[rule.file_path]),
203+
)
204+
)
131205

132206
if unexpected_improvements:
133207
print("Unexpected improvements:")
@@ -202,55 +276,39 @@ def main(argv: list[str] | None = None) -> int:
202276
"Warning ignore file not specified."
203277
" Continuing without it (no warnings ignored)."
204278
)
205-
files_with_expected_warnings = set()
279+
ignore_rules = set()
206280
else:
207281
if not Path(args.warning_ignore_file_path).is_file():
208282
print(
209283
f"Warning ignore file does not exist:"
210284
f" {args.warning_ignore_file_path}"
211285
)
212286
return 1
213-
with Path(args.warning_ignore_file_path).open(
214-
encoding="UTF-8"
215-
) as clean_files:
216-
# Files with expected warnings are stored as a set of tuples
217-
# where the first element is the file name and the second element
218-
# is the number of warnings expected in that file
219-
files_with_expected_warnings = {
220-
FileWarnings(
221-
file.strip().split()[0], int(file.strip().split()[1])
222-
)
223-
for file in clean_files
224-
if file.strip() and not file.startswith("#")
225-
}
287+
ignore_rules = parse_warning_ignore_file(args.warning_ignore_file_path)
226288

227289
with Path(args.compiler_output_file_path).open(encoding="UTF-8") as f:
228290
compiler_output_file_contents = f.read()
229291

230292
warnings = extract_warnings_from_compiler_output(
231293
compiler_output_file_contents,
232294
args.compiler_output_type,
233-
args.path_prefix
295+
args.path_prefix,
234296
)
235297

236298
files_with_warnings = get_warnings_by_file(warnings)
237299

238-
status = get_unexpected_warnings(
239-
files_with_expected_warnings, files_with_warnings
240-
)
300+
status = get_unexpected_warnings(ignore_rules, files_with_warnings)
241301
if args.fail_on_regression:
242302
exit_code |= status
243303

244-
status = get_unexpected_improvements(
245-
files_with_expected_warnings, files_with_warnings
246-
)
304+
status = get_unexpected_improvements(ignore_rules, files_with_warnings)
247305
if args.fail_on_improvement:
248306
exit_code |= status
249307

250308
print(
251309
"For information about this tool and its configuration"
252310
" visit https://devguide.python.org/development-tools/warnings/"
253-
)
311+
)
254312

255313
return exit_code
256314

0 commit comments

Comments
 (0)