Skip to content

Commit dcf440e

Browse files
committed
work
1 parent e679e1d commit dcf440e

File tree

5 files changed

+140
-184
lines changed

5 files changed

+140
-184
lines changed

firebase-dataconnect/ci/calculate_github_issue_for_commenting.py

Lines changed: 47 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -15,95 +15,68 @@
1515
from __future__ import annotations
1616

1717
import argparse
18-
import dataclasses
1918
import logging
20-
import sys
19+
import re
2120
import typing
22-
from typing import override
2321

24-
if typing.TYPE_CHECKING:
25-
from collections.abc import Sequence
26-
from typing import Never, TextIO
22+
type ExitCode = int
2723

28-
from _typeshed import SupportsWrite
2924

30-
type ExitCode = int
25+
def main() -> None:
26+
args = parse_args()
27+
logging.basicConfig(format="%(message)s", level=logging.INFO)
3128

29+
logging.info("Extracting PR number from ${{ github.ref }}: %s", args.github_ref)
30+
pr_number: int | None = pr_number_from_github_ref(args.github_ref)
31+
logging.info("Extracted PR number: %s", pr_number)
3232

33-
def main(args: Sequence[str], stdout: TextIO, stderr: TextIO) -> ExitCode:
34-
try:
35-
parsed_args = parse_args(args[0], args[1:], stdout)
36-
except MyArgumentParser.Error as e:
37-
if e.exit_code != 0:
38-
print(f"ERROR: invalid command-line arguments: {e}", file=stderr)
39-
print("Run with --help for help", file=stderr)
40-
return e.exit_code
4133

42-
print(f"Successfully parsed arguments: {parsed_args!r}")
43-
return 0
34+
def pr_number_from_github_ref(github_ref: str) -> int | None:
35+
match = re.fullmatch("refs/pull/([0-9]+)/merge", github_ref)
36+
return int(match.group(1)) if match else None
4437

4538

46-
@dataclasses.dataclass(frozen=True)
47-
class GetIssueNumberCommand:
39+
class ParsedArgs(typing.Protocol):
4840
github_ref: str
41+
github_repository: str
4942
github_event_name: str
50-
default_github_issue: int
51-
52-
53-
@dataclasses.dataclass(frozen=True)
54-
class ParsedArgs:
55-
log_level: int
56-
command: GetIssueNumberCommand
57-
58-
59-
class MyArgumentParser(argparse.ArgumentParser):
60-
def __init__(self, prog: str, stdout: SupportsWrite[str]) -> None:
61-
super().__init__(prog=prog, usage="%(prog)s <command> [options]")
62-
self.stdout = stdout
63-
64-
@override
65-
def exit(self, status: int = 0, message: str | None = None) -> Never:
66-
raise self.Error(exit_code=status, message=message)
67-
68-
@override
69-
def error(self, message: str) -> Never:
70-
self.exit(2, message)
71-
72-
@override
73-
def print_usage(self, file: SupportsWrite[str] | None = None) -> None:
74-
file = file if file is not None else self.stdout
75-
super().print_usage(file)
43+
pr_body_github_issue_key: str
44+
github_issue_for_scheduled_run: str
7645

77-
@override
78-
def print_help(self, file: SupportsWrite[str] | None = None) -> None:
79-
file = file if file is not None else self.stdout
80-
super().print_help(file)
8146

82-
class Error(Exception):
83-
def __init__(self, exit_code: ExitCode, message: str | None) -> None:
84-
super().__init__(message)
85-
self.exit_code = exit_code
86-
87-
88-
def parse_args(prog: str, args: Sequence[str], stdout: TextIO) -> ParsedArgs:
89-
arg_parser = MyArgumentParser(prog, stdout)
90-
parsed_args = arg_parser.parse_args(args)
91-
print(f"parsed_args: {parsed_args!r}")
92-
return ParsedArgs(
93-
log_level=logging.INFO,
94-
command=GetIssueNumberCommand(
95-
github_ref="sample_github_ref",
96-
github_event_name="sample_github_event_name",
97-
default_github_issue=123456,
98-
),
47+
def parse_args() -> ParsedArgs:
48+
arg_parser = argparse.ArgumentParser()
49+
arg_parser.add_argument(
50+
"--github-ref",
51+
required=True,
52+
help="The value of ${{ github.ref }} in the workflow",
53+
)
54+
arg_parser.add_argument(
55+
"--github-repository",
56+
required=True,
57+
help="The value of ${{ github.repository }} in the workflow",
58+
)
59+
arg_parser.add_argument(
60+
"--github-event-name",
61+
required=True,
62+
help="The value of ${{ github.event_name }} in the workflow",
63+
)
64+
arg_parser.add_argument(
65+
"--pr-body-github-issue-key",
66+
required=True,
67+
help="The string to search for in a Pull Request body to determine the GitHub Issue number "
68+
"for commenting. For example, if the value is 'foobar' then this script searched a PR "
69+
"body for a line of the form 'foobar=NNNN' where 'NNNN' is the GitHub issue number",
70+
)
71+
arg_parser.add_argument(
72+
"--github-issue-for-scheduled-run",
73+
required=True,
74+
help="The GitHub Issue number to use for commenting when --github-event-name is 'schedule'",
9975
)
10076

77+
parse_result = arg_parser.parse_args()
78+
return typing.cast("ParsedArgs", parse_result)
10179

102-
if __name__ == "__main__":
103-
try:
104-
exit_code = main(sys.argv, sys.stdout, sys.stderr)
105-
except KeyboardInterrupt:
106-
print("ERROR: application terminated by keyboard interrupt", file=sys.stderr)
107-
exit_code = 1
10880

109-
sys.exit(exit_code)
81+
if __name__ == "__main__":
82+
main()
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from __future__ import annotations
16+
17+
import hypothesis
18+
import hypothesis.strategies as st
19+
import pytest
20+
21+
import calculate_github_issue_for_commenting as sut
22+
23+
24+
class Test_pr_number_from_github_ref:
25+
@hypothesis.given(number=st.integers(min_value=0, max_value=10000))
26+
def test_returns_number_from_valid_github_ref(self, number: int) -> None:
27+
github_ref = f"refs/pull/{number}/merge"
28+
assert sut.pr_number_from_github_ref(github_ref) == number
29+
30+
@hypothesis.given(invalid_github_ref=st.text())
31+
def test_returns_none_on_random_input(self, invalid_github_ref: str) -> None:
32+
assert sut.pr_number_from_github_ref(invalid_github_ref) is None
33+
34+
@pytest.mark.parametrize(
35+
"invalid_number",
36+
[
37+
"",
38+
"123a",
39+
"a123",
40+
"12a34",
41+
"1.2",
42+
pytest.param(
43+
"1234",
44+
marks=pytest.mark.xfail(
45+
reason="make sure that the test would otherwise pass on valid int values",
46+
strict=True,
47+
),
48+
),
49+
],
50+
)
51+
def test_returns_none_on_invalid_number(self, invalid_number: str) -> None:
52+
invalid_github_ref = f"refs/pull/{invalid_number}/merge"
53+
assert sut.pr_number_from_github_ref(invalid_github_ref) is None
54+
55+
@pytest.mark.parametrize(
56+
"malformed_ref",
57+
[
58+
"",
59+
"refs",
60+
"refs/",
61+
"refs/pull",
62+
"refs/pull/",
63+
"refs/pull/1234",
64+
"refs/pull/1234/",
65+
"Refs/pull/1234/merge",
66+
"refs/Pull/1234/merge",
67+
"refs/pull/1234/Merge",
68+
"Arefs/pull/1234/merge",
69+
"refs/pull/1234/mergeZ",
70+
" refs/pull/1234/merge",
71+
"refs/pull/1234/merge ",
72+
pytest.param(
73+
"refs/pull/1234/merge",
74+
marks=pytest.mark.xfail(
75+
reason="make sure that the test would otherwise pass on valid ref",
76+
strict=True,
77+
),
78+
),
79+
],
80+
)
81+
def test_returns_none_on_malformed_ref(self, malformed_ref: str) -> None:
82+
assert sut.pr_number_from_github_ref(malformed_ref) is None

firebase-dataconnect/ci/notify_issue.py

Lines changed: 0 additions & 91 deletions
This file was deleted.

firebase-dataconnect/ci/notify_issue_test.py

Lines changed: 0 additions & 19 deletions
This file was deleted.

firebase-dataconnect/ci/pyproject.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
name = "Firebase Data Connect Android SDK Continuous Integration Tools"
33
requires-python = ">= 3.13"
44

5+
[tool.pytest.ini_options]
6+
addopts = "--strict-markers"
7+
markers = [ "paramaterize" ]
8+
59
[tool.pyright]
610
include = ["**/*.py"]
711
typeCheckingMode = "strict"
@@ -23,9 +27,16 @@ ignore = [
2327
"D203", # incorrect-blank-line-before-class
2428
"D211", # no-blank-line-before-class
2529
"D212", # multi-line-summary-second-line
30+
"LOG015", # root-logger-call
2631
"T201", # print` found
2732
]
2833

34+
[tool.ruff.lint.per-file-ignores]
35+
"*_test.py" = [
36+
"N801", # invalid-class-name
37+
"S101", # Use of `assert` detected
38+
]
39+
2940
[tool.ruff.format]
3041
quote-style = "double"
3142
indent-style = "space"

0 commit comments

Comments
 (0)