Skip to content

Commit 9406b99

Browse files
authored
Improve filtering of warnings in pytest (#411)
This updates the cookiecutter templates to ignore warnings by using the separate `filterwarnings` options instead of using command-line options. This allows using regex, which is needed to add a new ignore for protobuf gencode version warnings, that includes the protobuf version, which will change dynamically.
2 parents b716775 + b95e156 commit 9406b99

File tree

9 files changed

+203
-9
lines changed

9 files changed

+203
-9
lines changed

RELEASE_NOTES.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
All upgrading should be done via the migration script or regenerating the templates.
1414

1515
```bash
16-
curl -sSL https://raw.githubusercontent.com/frequenz-floss/frequenz-repo-config-python/v0.12/cookiecutter/migrate.py | python3
16+
curl -sSL https://raw.githubusercontent.com/frequenz-floss/frequenz-repo-config-python/v0.14/cookiecutter/migrate.py | python3
1717
```
1818

1919
But you might still need to adapt your code:
@@ -26,7 +26,7 @@ But you might still need to adapt your code:
2626

2727
### Cookiecutter template
2828

29-
<!-- Here new features for cookiecutter specifically -->
29+
- New warning ignores for protobuf gencode versions in pytest.
3030

3131
## Bug Fixes
3232

cookiecutter/migrate.py

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,136 @@
2222

2323
import hashlib
2424
import os
25+
import re
2526
import subprocess
2627
import tempfile
2728
from pathlib import Path
28-
from typing import SupportsIndex
29+
from typing import Final, SupportsIndex
2930

3031

3132
def main() -> None:
3233
"""Run the migration steps."""
3334
# Add a separation line like this one after each migration step.
3435
print("=" * 72)
36+
migrate_filterwarnings(Path("pyproject.toml"))
37+
print("=" * 72)
3538
print("Migration script finished. Remember to follow any manual instructions.")
3639
print("=" * 72)
3740

3841

42+
# pylint: disable-next=too-many-locals,too-many-statements,too-many-branches
43+
def migrate_filterwarnings(path: Path) -> None:
44+
"""Migrate the filterwarnings configuration in pyproject.toml files."""
45+
print(f"Migrating from pytest addopts to filterwarnings in {path}...")
46+
# Patterns to identify and clean existing addopts flags
47+
addopts_re: Final = re.compile(r'^(\s*)addopts\s*=\s*"(.*)"')
48+
filterwarnings_re: Final = re.compile(r"^(\s*)filterwarnings\s*=\s*(.*)")
49+
w_flag_re: Final = re.compile(r"^-W=?(.*)$")
50+
unwanted_flags: Final = {
51+
"-W=all",
52+
"-Werror",
53+
"-Wdefault::DeprecationWarning",
54+
"-Wdefault::PendingDeprecationWarning",
55+
}
56+
57+
text = path.read_text(encoding="utf-8")
58+
lines = text.splitlines(keepends=True)
59+
new_lines: list[str] = []
60+
modified = False
61+
addopts_found = False
62+
has_filterwarnings = False
63+
has_w_flags = False
64+
w_flags: list[str] = []
65+
66+
for line in lines:
67+
filterwarnings_match = filterwarnings_re.match(line)
68+
if filterwarnings_match:
69+
has_filterwarnings = True
70+
addopts_match = addopts_re.match(line)
71+
if addopts_match and not modified:
72+
addopts_found = True
73+
indent, inner = addopts_match.group(1), addopts_match.group(2)
74+
tokens = inner.split()
75+
remaining_tokens: list[str] = []
76+
extra_specs: list[str] = []
77+
78+
for tok in tokens:
79+
if tok in unwanted_flags:
80+
# Discard it; it will be replaced by base_specs
81+
continue
82+
83+
w_match = w_flag_re.match(tok)
84+
if w_match:
85+
w_flags.append(tok)
86+
has_w_flags = True
87+
spec = w_match.group(1)
88+
if spec:
89+
# Convert this -W... into a filterwarnings spec
90+
extra_specs.append(spec)
91+
else:
92+
# Keep any non -W token
93+
remaining_tokens.append(tok)
94+
95+
# Base filterwarnings specs to replace unwanted flags
96+
base_specs = map(
97+
str.strip,
98+
r"""
99+
"error",
100+
"once::DeprecationWarning",
101+
"once::PendingDeprecationWarning",
102+
# We ignore warnings about protobuf gencode version being one version older
103+
# than the current version, as this is supported by protobuf, and we expect to
104+
# have such cases. If we go too far, we will get a proper error anyways.
105+
# We use a raw string (single quotes) to avoid the need to escape special
106+
# characters as this is a regex.
107+
'ignore:Protobuf gencode version .*exactly one major version older.*:UserWarning',
108+
""".strip().splitlines(),
109+
)
110+
111+
# Rebuild addopts line without unwanted flags
112+
new_addopts_value = " ".join(remaining_tokens)
113+
new_lines.append(f'{indent}addopts = "{new_addopts_value}"\n')
114+
115+
# Build the filterwarnings block
116+
new_lines.append(f"{indent}filterwarnings = [\n")
117+
# This is fine, indent is defined only once, so even if it is a closure
118+
# bound late, the value will always be the same.
119+
# pylint: disable-next=cell-var-from-loop
120+
new_lines.extend(map(lambda s: f"{indent} {s}\n", base_specs))
121+
for spec in extra_specs:
122+
new_lines.append(f'{indent} "{spec}",\n')
123+
new_lines.append(f"{indent}]\n")
124+
125+
modified = True
126+
else:
127+
new_lines.append(line)
128+
129+
if modified and not has_filterwarnings:
130+
print(f"Updated {path} to use filterwarnings.")
131+
path.write_text("".join(new_lines), encoding="utf-8")
132+
return
133+
134+
if has_filterwarnings and not has_w_flags:
135+
print(
136+
f"The file {path} already has a `filterwarnings` section and has no "
137+
"-W flags in `addopts`, it is probably already migrated."
138+
)
139+
elif has_filterwarnings and has_w_flags:
140+
print(
141+
f"The file {path} already has a `filterwarnings` section, but also "
142+
f"has -W flags in `addopts` ({' '.join(w_flags)!r}), it looks like "
143+
"it is half-migrated, you should probably migrate it manually. Avoid using -W "
144+
"flags in `addopts` if there is a `filterwarnings` section."
145+
)
146+
if not addopts_found:
147+
print(f"No 'addopts' found in {path}.")
148+
149+
manual_step(
150+
f"No changes done to {path}. "
151+
"Please double check no manual steps are required."
152+
)
153+
154+
39155
def apply_patch(patch_content: str) -> None:
40156
"""Apply a patch using the patch utility."""
41157
subprocess.run(["patch", "-p1"], input=patch_content.encode(), check=True)

cookiecutter/{{cookiecutter.github_repo_name}}/pyproject.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,19 @@ disable = [
202202
]
203203

204204
[tool.pytest.ini_options]
205+
addopts = "-vv"
206+
filterwarnings = [
207+
"error",
208+
"once::DeprecationWarning",
209+
"once::PendingDeprecationWarning",
210+
# We ignore warnings about protobuf gencode version being one version older
211+
# than the current version, as this is supported by protobuf, and we expect to
212+
# have such cases. If we go too far, we will get a proper error anyways.
213+
# We use a raw string (single quotes) to avoid the need to escape special
214+
# characters as this is a regex.
215+
'ignore:Protobuf gencode version .*exactly one major version older.*:UserWarning',
216+
]
205217
{%- if cookiecutter.type != "api" %}
206-
addopts = "-W=all -Werror -Wdefault::DeprecationWarning -Wdefault::PendingDeprecationWarning -vv"
207218
testpaths = ["tests", "src"]
208219
asyncio_mode = "auto"
209220
asyncio_default_fixture_loop_scope = "function"

pyproject.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,18 @@ module = [
206206
ignore_missing_imports = true
207207

208208
[tool.pytest.ini_options]
209-
addopts = "-W=all -Werror -Wdefault::DeprecationWarning -Wdefault::PendingDeprecationWarning -vv"
209+
addopts = "-vv"
210+
filterwarnings = [
211+
"error",
212+
"once::DeprecationWarning",
213+
"once::PendingDeprecationWarning",
214+
# We ignore warnings about protobuf gencode version being one version older
215+
# than the current version, as this is supported by protobuf, and we expect to
216+
# have such cases. If we go too far, we will get a proper error anyways.
217+
# We use a raw string (single quotes) to avoid the need to escape special
218+
# characters as this is a regex.
219+
'ignore:Protobuf gencode version .*exactly one major version older.*:UserWarning',
220+
]
210221
testpaths = ["src", "tests"]
211222
markers = [
212223
"integration: integration tests (deselect with '-m \"not integration\"')",

tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/pyproject.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,18 @@ disable = [
153153
]
154154

155155
[tool.pytest.ini_options]
156-
addopts = "-W=all -Werror -Wdefault::DeprecationWarning -Wdefault::PendingDeprecationWarning -vv"
156+
addopts = "-vv"
157+
filterwarnings = [
158+
"error",
159+
"once::DeprecationWarning",
160+
"once::PendingDeprecationWarning",
161+
# We ignore warnings about protobuf gencode version being one version older
162+
# than the current version, as this is supported by protobuf, and we expect to
163+
# have such cases. If we go too far, we will get a proper error anyways.
164+
# We use a raw string (single quotes) to avoid the need to escape special
165+
# characters as this is a regex.
166+
'ignore:Protobuf gencode version .*exactly one major version older.*:UserWarning',
167+
]
157168
testpaths = ["tests", "src"]
158169
asyncio_mode = "auto"
159170
asyncio_default_fixture_loop_scope = "function"

tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/pyproject.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,18 @@ disable = [
161161
]
162162

163163
[tool.pytest.ini_options]
164+
addopts = "-vv"
165+
filterwarnings = [
166+
"error",
167+
"once::DeprecationWarning",
168+
"once::PendingDeprecationWarning",
169+
# We ignore warnings about protobuf gencode version being one version older
170+
# than the current version, as this is supported by protobuf, and we expect to
171+
# have such cases. If we go too far, we will get a proper error anyways.
172+
# We use a raw string (single quotes) to avoid the need to escape special
173+
# characters as this is a regex.
174+
'ignore:Protobuf gencode version .*exactly one major version older.*:UserWarning',
175+
]
164176
testpaths = ["pytests"]
165177

166178
[tool.mypy]

tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/pyproject.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,18 @@ disable = [
152152
]
153153

154154
[tool.pytest.ini_options]
155-
addopts = "-W=all -Werror -Wdefault::DeprecationWarning -Wdefault::PendingDeprecationWarning -vv"
155+
addopts = "-vv"
156+
filterwarnings = [
157+
"error",
158+
"once::DeprecationWarning",
159+
"once::PendingDeprecationWarning",
160+
# We ignore warnings about protobuf gencode version being one version older
161+
# than the current version, as this is supported by protobuf, and we expect to
162+
# have such cases. If we go too far, we will get a proper error anyways.
163+
# We use a raw string (single quotes) to avoid the need to escape special
164+
# characters as this is a regex.
165+
'ignore:Protobuf gencode version .*exactly one major version older.*:UserWarning',
166+
]
156167
testpaths = ["tests", "src"]
157168
asyncio_mode = "auto"
158169
asyncio_default_fixture_loop_scope = "function"

tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/pyproject.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,18 @@ disable = [
149149
]
150150

151151
[tool.pytest.ini_options]
152-
addopts = "-W=all -Werror -Wdefault::DeprecationWarning -Wdefault::PendingDeprecationWarning -vv"
152+
addopts = "-vv"
153+
filterwarnings = [
154+
"error",
155+
"once::DeprecationWarning",
156+
"once::PendingDeprecationWarning",
157+
# We ignore warnings about protobuf gencode version being one version older
158+
# than the current version, as this is supported by protobuf, and we expect to
159+
# have such cases. If we go too far, we will get a proper error anyways.
160+
# We use a raw string (single quotes) to avoid the need to escape special
161+
# characters as this is a regex.
162+
'ignore:Protobuf gencode version .*exactly one major version older.*:UserWarning',
163+
]
153164
testpaths = ["tests", "src"]
154165
asyncio_mode = "auto"
155166
asyncio_default_fixture_loop_scope = "function"

tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/pyproject.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,18 @@ disable = [
153153
]
154154

155155
[tool.pytest.ini_options]
156-
addopts = "-W=all -Werror -Wdefault::DeprecationWarning -Wdefault::PendingDeprecationWarning -vv"
156+
addopts = "-vv"
157+
filterwarnings = [
158+
"error",
159+
"once::DeprecationWarning",
160+
"once::PendingDeprecationWarning",
161+
# We ignore warnings about protobuf gencode version being one version older
162+
# than the current version, as this is supported by protobuf, and we expect to
163+
# have such cases. If we go too far, we will get a proper error anyways.
164+
# We use a raw string (single quotes) to avoid the need to escape special
165+
# characters as this is a regex.
166+
'ignore:Protobuf gencode version .*exactly one major version older.*:UserWarning',
167+
]
157168
testpaths = ["tests", "src"]
158169
asyncio_mode = "auto"
159170
asyncio_default_fixture_loop_scope = "function"

0 commit comments

Comments
 (0)