3
Test ownership was introduced in https://github.com/pytorch/pytorch/issues/66232.
5
This lint verifies that every Python test file (file that matches test_*.py or *_test.py in the test folder)
6
has valid ownership information in a comment header. Valid means:
7
- The format of the header follows the pattern "# Owner(s): ["list", "of owner", "labels"]
8
- Each owner label actually exists in PyTorch
9
- Each owner label starts with "module: " or "oncall: " or is in ACCEPTABLE_OWNER_LABELS
14
from typing import Any, List, NamedTuple, Optional
15
from urllib.request import urlopen
18
LINTER_CODE = "TESTOWNERS"
21
class LintSeverity(str, Enum):
28
class LintMessage(NamedTuple):
33
severity: LintSeverity
35
original: Optional[str]
36
replacement: Optional[str]
37
description: Optional[str]
40
# Team/owner labels usually start with "module: " or "oncall: ", but the following are acceptable exceptions
41
ACCEPTABLE_OWNER_LABELS = ["NNC", "high priority"]
42
OWNERS_PREFIX = "# Owner(s): "
45
def get_pytorch_labels() -> Any:
47
urlopen("https://ossci-metrics.s3.amazonaws.com/pytorch_labels.json")
51
return json.loads(labels)
54
PYTORCH_LABELS = get_pytorch_labels()
55
# Team/owner labels usually start with "module: " or "oncall: ", but the following are acceptable exceptions
56
ACCEPTABLE_OWNER_LABELS = ["NNC", "high priority"]
57
GLOB_EXCEPTIONS = ["**/test/run_test.py"]
61
labels: List[str], filename: str, line_number: int
62
) -> List[LintMessage]:
65
if label not in PYTORCH_LABELS:
72
severity=LintSeverity.ERROR,
73
name="[invalid-label]",
77
f"{label} is not a PyTorch label "
78
"(please choose from https://github.com/pytorch/pytorch/labels)"
83
if label.startswith(("module:", "oncall:")) or label in ACCEPTABLE_OWNER_LABELS:
92
severity=LintSeverity.ERROR,
93
name="[invalid-owner]",
97
f"{label} is not an acceptable owner "
98
"(please update to another label or edit ACCEPTABLE_OWNERS_LABELS "
99
"in tools/linters/adapters/testowners_linter.py"
107
def check_file(filename: str) -> List[LintMessage]:
109
has_ownership_info = False
111
with open(filename) as f:
112
for idx, line in enumerate(f):
113
if not line.startswith(OWNERS_PREFIX):
116
has_ownership_info = True
117
labels = json.loads(line[len(OWNERS_PREFIX) :])
118
lint_messages.extend(check_labels(labels, filename, idx + 1))
120
if has_ownership_info is False:
121
lint_messages.append(
127
severity=LintSeverity.ERROR,
128
name="[no-owner-info]",
131
description="Missing a comment header with ownership information.",
139
parser = argparse.ArgumentParser(
140
description="test ownership linter",
141
fromfile_prefix_chars="@",
146
help="paths to lint",
149
args = parser.parse_args()
152
for filename in args.filenames:
153
lint_messages.extend(check_file(filename))
155
for lint_message in lint_messages:
156
print(json.dumps(lint_message._asdict()), flush=True)
159
if __name__ == "__main__":