-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Add tool for easier test updates #6089
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
7c0a8fe
69ed76b
dc3a6de
5c09ef0
ad061b2
a6f2324
302b3d1
db74d2f
11694e3
d8b4e26
2308a5a
2fb6842
db5eb4b
af1c28d
42365d2
fdce40b
32baa80
fb7324d
f4056ac
4296b59
e2aa220
74b47a0
51c6ad9
01a90ef
a0e56ae
43a63a8
3e2c1f1
789cf6e
3e9872c
d0763e4
e00bb28
8a5875e
6c615cc
c0e90cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,23 +1,23 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/env python | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import argparse | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import ast | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import dataclasses | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import collections | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import enum | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import pathlib | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||||||||||||||||||||
| from typing import TYPE_CHECKING, Self | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import typing | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if TYPE_CHECKING: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if typing.TYPE_CHECKING: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| from collections.abc import Iterator | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| COMMENT = "TODO: RUSTPYTHON" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| type Patches = dict[str, dict[str, list["PatchSpec"]]] | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix Python syntax: replace Python 3.12 This is a SyntaxError under flake8 (E999). Use a backwards-compatible alias. +from typing import TypeAlias
@@
-type Patches = dict[str, dict[str, list["PatchSpec"]]]
+Patches: TypeAlias = dict[str, dict[str, list["PatchSpec"]]]📝 Committable suggestion
Suggested change
🧰 Tools🪛 Flake8 (7.2.0)[error] 40-40: SyntaxError: invalid syntax (E999) 🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine and can be ignored
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ShaharNaveh, understood! I'll note that this syntax issue can be ignored for this tool. 🧠 Learnings used |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| @enum.unique | ||||||||||||||||||||||||||||||||||||||||||||||||||
| class ProgName(enum.StrEnum): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Gen = enum.auto() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Patch = enum.auto() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| COL_OFFSET = 4 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| INDENT1 = " " * COL_OFFSET | ||||||||||||||||||||||||||||||||||||||||||||||||||
| INDENT2 = INDENT1 * 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| COMMENT = "TODO: RUSTPYTHON" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| @enum.unique | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -29,6 +29,9 @@ class UtMethod(enum.StrEnum): | |||||||||||||||||||||||||||||||||||||||||||||||||
| def _generate_next_value_(name, start, count, last_values) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return name[0].lower() + name[1:] | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def has_cond(self) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return self.endswith(("If", "Unless")) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| ExpectedFailure = enum.auto() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ExpectedFailureIf = enum.auto() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ExpectedFailureIfWindows = enum.auto() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -37,33 +40,57 @@ def _generate_next_value_(name, start, count, last_values) -> str: | |||||||||||||||||||||||||||||||||||||||||||||||||
| SkipUnless = enum.auto() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| @dataclasses.dataclass(frozen=True, slots=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| class PatchEntry: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| class PatchSpec(typing.NamedTuple): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Stores patch metadata. | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| Attributes | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ---------- | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parent_class : str | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Parent class of test. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test_name : str | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Test name. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ut_method : UtMethod | ||||||||||||||||||||||||||||||||||||||||||||||||||
| unittest method. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cond : str, optional | ||||||||||||||||||||||||||||||||||||||||||||||||||
| `ut_method` condition. Relevant only for UtMethod.{expectedFailureIf,skipIf}. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| `ut_method` condition. Relevant only for some of `ut_method` types. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| reason : str, optional | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Reason for why the test is patched in this way. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| parent_class: str | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test_name: str | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ut_method: UtMethod | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cond: str | None = None | ||||||||||||||||||||||||||||||||||||||||||||||||||
| reason: str = "" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def fmt(self) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| prefix = prefix = f"@unittest.{self.ut_method}" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| match self.ut_method: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| case UtMethod.ExpectedFailure: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| line = f"{prefix} # {COMMENT}; {self.reason}" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| case UtMethod.ExpectedFailureIfWindows | UtMethod.Skip: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| line = f'{prefix}("{COMMENT}; {self.reason}")' | ||||||||||||||||||||||||||||||||||||||||||||||||||
| case UtMethod.SkipIf | UtMethod.SkipUnless | UtMethod.ExpectedFailureIf: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| line = f'{prefix}({self.cond}, "{COMMENT}; {self.reason}")' | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| return line.strip().rstrip(";").strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+84
to
+94
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Escape reason strings to avoid invalid Python when quotes appear String args aren’t escaped; a double-quote in reason breaks syntax. Use json.dumps for safe quoting. def fmt(self) -> str:
prefix = f"@unittest.{self.ut_method}"
match self.ut_method:
case UtMethod.ExpectedFailure:
line = f"{prefix} # {COMMENT}; {self.reason}"
case UtMethod.ExpectedFailureIfWindows | UtMethod.Skip:
- line = f'{prefix}("{COMMENT}; {self.reason}")'
+ msg = f"{COMMENT}; {self.reason}".strip().rstrip(";")
+ line = f"{prefix}({json.dumps(msg)})"
case UtMethod.SkipIf | UtMethod.SkipUnless | UtMethod.ExpectedFailureIf:
- line = f'{prefix}({self.cond}, "{COMMENT}; {self.reason}")'
+ msg = f"{COMMENT}; {self.reason}".strip().rstrip(";")
+ line = f"{prefix}({self.cond}, {json.dumps(msg)})"
- return line.strip().rstrip(";").strip()
+ return line.strip()📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| class PatchEntry(typing.NamedTuple): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Stores patch metadata. | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| Attributes | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ---------- | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parent_class : str | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Parent class of test. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test_name : str | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Test name. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| spec : PatchSpec | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Patch spec. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| parent_class: str | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test_name: str | ||||||||||||||||||||||||||||||||||||||||||||||||||
| spec: PatchSpec | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| @classmethod | ||||||||||||||||||||||||||||||||||||||||||||||||||
| def iter_patch_entires(cls, tree: ast.Module, lines: list[str]) -> "Iterator[Self]": | ||||||||||||||||||||||||||||||||||||||||||||||||||
| def iter_patch_entires( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cls, tree: ast.Module, lines: list[str] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> "Iterator[typing.Self]": | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for cls_node, fn_node in iter_tests(tree): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parent_class = cls_node.name | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for dec_node in fn_node.decorator_list: | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -78,7 +105,12 @@ def iter_patch_entires(cls, tree: ast.Module, lines: list[str]) -> "Iterator[Sel | |||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| cond = None | ||||||||||||||||||||||||||||||||||||||||||||||||||
| match attr_node.attr: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ut_method = UtMethod(attr_node.attr) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| except ValueError: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| match ut_method: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| case UtMethod.ExpectedFailure: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for line in lines[dec_node.lineno - 2 : dec_node.lineno]: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if COMMENT not in line: | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -87,42 +119,31 @@ def iter_patch_entires(cls, tree: ast.Module, lines: list[str]) -> "Iterator[Sel | |||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| case ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| UtMethod.Skip | ||||||||||||||||||||||||||||||||||||||||||||||||||
| | UtMethod.SkipIf | ||||||||||||||||||||||||||||||||||||||||||||||||||
| | UtMethod.ExpectedFailureIf | ||||||||||||||||||||||||||||||||||||||||||||||||||
| | UtMethod.ExpectedFailureIfWindows | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| case _: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| reason = next( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| node.value | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for node in ast.walk(dec_node) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(node, ast.Constant) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| and isinstance(node.value, str) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| and node.value.startswith(COMMENT) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| and COMMENT in node.value | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| None, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # If we didn't find a constant with the COMMENT, then we didn't put this decorator | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # If we didn't find a constant containing <COMMENT>, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # then we didn't put this decorator | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if not reason: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if attr_node.attr not in ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| UtMethod.Skip, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| UtMethod.ExpectedFailureIfWindows, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if ut_method.has_cond(): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cond = ast.unparse(dec_node.args[0]) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| case _: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| yield cls( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parent_class, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fn_node.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| UtMethod(attr_node.attr), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cond, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| reason.replace(COMMENT, "").strip().lstrip(";").lstrip(":").strip(), | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+170
to
+172
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid crash when extracting
- if ut_method.has_cond():
- cond = ast.unparse(dec_node.args[0])
+ if ut_method.has_cond():
+ if isinstance(dec_node, ast.Call) and dec_node.args:
+ cond = ast.unparse(dec_node.args[0])
+ else:
+ # Malformed or missing condition; skip this decorator.
+ continue📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
| reason = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| reason.replace(COMMENT, "").strip().lstrip(";").lstrip(":").strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| spec = PatchSpec(ut_method, cond, reason) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| yield cls(parent_class, fn_node.name, spec) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def iter_tests( | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -149,38 +170,89 @@ def iter_patches(contents: str) -> "Iterator[PatchEntry]": | |||||||||||||||||||||||||||||||||||||||||||||||||
| yield from PatchEntry.iter_patch_entires(tree, lines) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+198
to
+202
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Handle unparseable files gracefully (bad-syntax tests)
-def iter_patches(contents: str) -> "Iterator[PatchEntry]":
- lines = contents.splitlines()
- tree = ast.parse(contents)
- yield from PatchEntry.iter_patch_entires(tree, lines)
+def iter_patches(contents: str) -> "Iterator[PatchEntry]":
+ lines = contents.splitlines()
+ try:
+ tree = ast.parse(contents)
+ except SyntaxError as e:
+ print(f"ERROR: could not parse source for patch extraction: {e}", file=sys.stderr)
+ return
+ yield from PatchEntry.iter_patch_entires(tree, lines)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine and intended. you can ignore it
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ShaharNaveh Understood! I'll ignore the error handling suggestion. The current behavior of letting ✏️ Learnings added
🧠 Learnings used |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def read_infile(infile: str) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if infile == "-": | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return sys.stdin.read() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| def build_patch_dict(it: "Iterator[PatchEntry]") -> Patches: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| patches = collections.defaultdict(lambda: collections.defaultdict(list)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for entry in it: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| patches[entry.parent_class][entry.test_name].append(entry.spec) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| with open(infile, mode="r", encoding="utf-8") as fd: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return fd.read() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return {k: dict(v) for k, v in patches.items()} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def iter_patch_lines(tree: ast.Module, patches: Patches) -> "Iterator[tuple[int, str]]": | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cache = {} # Used in phase 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Phase 1: Iterate and mark existing tests | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for cls_node, fn_node in iter_tests(tree): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cache[cls_node.name] = cls_node.end_lineno | ||||||||||||||||||||||||||||||||||||||||||||||||||
| specs = patches.get(cls_node.name, {}).pop(fn_node.name, None) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+212
to
+218
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Respect class indentation when adding inherited test wrappers Using a fixed 4-space indent can produce invalid indentation for nested/indented classes. Cache both -def iter_patch_lines(tree: ast.Module, patches: Patches) -> "Iterator[tuple[int, str]]":
- cache = {} # Used in phase 2
+def iter_patch_lines(tree: ast.Module, patches: Patches) -> "Iterator[tuple[int, str]]":
+ # cache: {class_name: (end_lineno, col_offset)}
+ cache: dict[str, tuple[int, int]] = {}
@@
- for cls_node, fn_node in iter_tests(tree):
- cache[cls_node.name] = cls_node.end_lineno
+ for cls_node, fn_node in iter_tests(tree):
+ cache[cls_node.name] = (cls_node.end_lineno, cls_node.col_offset)
@@
- # Phase 2: Iterate and mark inhereted tests
+ # Phase 2: Iterate and mark inherited tests
for cls_name, tests in patches.items():
- lineno = cache.get(cls_name)
- if not lineno:
+ cached = cache.get(cls_name)
+ if not cached:
print(f"WARNING: {cls_name} does not exist in remote file", file=sys.stderr)
continue
+ lineno, cls_col = cached
for test_name, specs in tests.items():
- patch_lines = "\n".join(f"{INDENT1}{spec.fmt()}" for spec in specs)
+ indent1 = " " * (cls_col + COL_OFFSET)
+ indent2 = indent1 + (" " * COL_OFFSET)
+ patch_lines = "\n".join(f"{indent1}{spec.fmt()}" for spec in specs)
yield (
lineno,
f"""
{patch_lines}
-{INDENT1}def {test_name}(self):
-{INDENT2}return super().{test_name}()
+{indent1}def {test_name}(self):
+{indent2}return super().{test_name}()
""".rstrip(),
)Also applies to: 229-244 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
| if not specs: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| lineno = min( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| (dec_node.lineno for dec_node in fn_node.decorator_list), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| default=fn_node.lineno, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| indent = " " * fn_node.col_offset | ||||||||||||||||||||||||||||||||||||||||||||||||||
| yield (lineno - 1, "\n".join(f"{indent}{spec.fmt()}" for spec in specs)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Phase 2: Iterate and mark inhereted tests | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for cls_name, tests in patches.items(): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| lineno = cache[cls_name] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for test_name, specs in tests.items(): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| patch_lines = "\n".join(f"{INDENT1}{spec.fmt()}" for spec in specs) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| yield ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| lineno, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| f""" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| {patch_lines} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| {INDENT1}def {test_name}(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| {INDENT2}return super().{test_name}() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| """.rstrip(), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def apply_patches(contents: str, patches: Patches) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| tree = ast.parse(contents) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| lines = contents.splitlines() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| modifications = list(iter_patch_lines(tree, patches)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # Going in reverse to not distrupt the line offset | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for lineno, patch in sorted(modifications, reverse=True): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| lines.insert(lineno, patch) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| joined = "\n".join(lines) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return f"{joined}\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def build_argparse() -> argparse.ArgumentParser: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parser = argparse.ArgumentParser( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| description="Helper tool for updating files under Lib/" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| subparsers = parser.add_subparsers(dest="pname", required=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Gen | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parser_gen = subparsers.add_parser(ProgName.Gen) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parser_gen.add_argument( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "infile", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| default="-", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| help="File path to generate patches from, can get from stdin", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| nargs="?", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parser.add_argument( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "orig_file", help="File to gather patches from", type=pathlib.Path | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Patch | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parser_patch = subparsers.add_parser(ProgName.Patch) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parser_patch.add_argument("src", help="File path to apply patches for") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parser_patch.add_argument( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "infile", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| default="-", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| help="File path containing patches, can get from stdin", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| group = parser.add_mutually_exclusive_group(required=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| group.add_argument( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "remote_file", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| nargs="?", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| help="File to apply patches to", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| type=pathlib.Path, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| group.add_argument( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "--show-patches", action="store_true", help="Show the patches and exit" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parser.add_argument( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "-p", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "--patches", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| help="File path to file containing patches in a JSON format", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| type=pathlib.Path, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| parser.add_argument( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "--inplace", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| action=argparse.BooleanOptionalAction, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| default=False, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| help="Whether write the changes", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| return parser | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -190,13 +262,33 @@ def build_argparse() -> argparse.ArgumentParser: | |||||||||||||||||||||||||||||||||||||||||||||||||
| parser = build_argparse() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| args = parser.parse_args() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| contents = read_infile(args.infile) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| match args.pname: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| case ProgName.Gen: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| patches = list(map(dataclasses.asdict, iter_patches(contents))) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| output = json.dumps(patches, indent=4) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| case ProgName.Patch: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| pass # TODO | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.stdout.write(f"{output}\n") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.stdout.flush() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| contents = args.orig_file.read_text() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if args.patches: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| patches = { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cls_name: { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test_name: [PatchSpec(**spec) for spec in specs] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for test_name, specs in tests.items() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for cls_name, tests in json.loads(args.patches.read_text()).items() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| patches = build_patch_dict(iter_patches(contents)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if args.show_patches: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| patches = { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cls_name: { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| test_name: [spec._asdict() for spec in specs] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for test_name, specs in tests.items() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for cls_name, tests in patches.items() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| output = json.dumps(patches, indent=4) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.stdout.write(f"{output}\n") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.exit(0) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| patched = apply_patches(args.remote_file.read_text(), patches) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if args.inplace: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| args.orig_file.write_text(patched) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.stdout.write(patched) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.