Skip to content
This repository was archived by the owner on Jul 16, 2025. It is now read-only.

Commit 3a9adaa

Browse files
authored
Merge pull request #61 from codecov/dana/base-picking-command
implement base picking command
2 parents ce8c3ea + 5fff113 commit 3a9adaa

File tree

7 files changed

+300
-12
lines changed

7 files changed

+300
-12
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import logging
2+
import typing
3+
import uuid
4+
5+
import click
6+
7+
from codecov_cli.fallbacks import CodecovOption, FallbackFieldEnum
8+
from codecov_cli.helpers.encoder import slug_without_subgroups_is_invalid
9+
from codecov_cli.services.commit.base_picking import base_picking_logic
10+
11+
logger = logging.getLogger("codecovcli")
12+
13+
14+
@click.command()
15+
@click.option(
16+
"--base-sha",
17+
help="Base commit SHA (with 40 chars)",
18+
cls=CodecovOption,
19+
fallback_field=FallbackFieldEnum.commit_sha,
20+
required=True,
21+
)
22+
@click.option(
23+
"--pr",
24+
help="Pull Request id to associate commit with",
25+
cls=CodecovOption,
26+
fallback_field=FallbackFieldEnum.pull_request_number,
27+
)
28+
@click.option(
29+
"--slug",
30+
cls=CodecovOption,
31+
fallback_field=FallbackFieldEnum.slug,
32+
help="owner/repo slug",
33+
envvar="CODECOV_SLUG",
34+
)
35+
@click.option(
36+
"-t",
37+
"--token",
38+
help="Codecov upload token",
39+
type=click.UUID,
40+
envvar="CODECOV_TOKEN",
41+
)
42+
@click.option(
43+
"--service",
44+
cls=CodecovOption,
45+
fallback_field=FallbackFieldEnum.service,
46+
help="Specify the service provider of the repo e.g. github",
47+
)
48+
@click.pass_context
49+
def pr_base_picking(
50+
ctx,
51+
base_sha: str,
52+
pr: typing.Optional[int],
53+
slug: typing.Optional[str],
54+
token: typing.Optional[uuid.UUID],
55+
service: typing.Optional[str],
56+
):
57+
logger.debug(
58+
"Starting base picking process",
59+
extra=dict(
60+
extra_log_attributes=dict(
61+
pr=pr,
62+
slug=slug,
63+
token=token,
64+
service=service,
65+
)
66+
),
67+
)
68+
69+
if slug_without_subgroups_is_invalid(slug):
70+
logger.error(
71+
"Slug is invalid. Slug should be in the form of owner_username/repo_name"
72+
)
73+
return
74+
75+
base_picking_logic(base_sha, pr, slug, token, service)

codecov_cli/helpers/encoder.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,29 @@
1+
import re
2+
3+
slug_without_subgroups_regex = re.compile(r"[^/\s]+\/[^/\s]+$")
4+
slug_with_subgroups_regex = re.compile(r"[^/\s]+(\/[^/\s]+)+$")
5+
6+
17
def encode_slug(slug: str):
2-
if "/" not in slug:
8+
if slug_with_subgroups_is_invalid(slug):
39
raise ValueError("The provided slug is invalid")
410
owner, repo = slug.rsplit("/", 1)
511
encoded_owner = ":::".join(owner.split("/"))
612
encoded_slug = "::::".join([encoded_owner, repo])
713
return encoded_slug
14+
15+
16+
def slug_without_subgroups_is_invalid(slug: str):
17+
"""
18+
Checks if slug is in the form of owner/repo
19+
Returns True if it's invalid, otherwise return False
20+
"""
21+
return not slug or not slug_without_subgroups_regex.match(slug)
22+
23+
24+
def slug_with_subgroups_is_invalid(slug: str):
25+
"""
26+
Checks if slug is in the form of owner/repo or owner/subgroup/repo
27+
Returns True if it's invalid, otherwise return False
28+
"""
29+
return not slug or not slug_with_subgroups_regex.match(slug)

codecov_cli/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import click
66
import yaml
77

8+
from codecov_cli.commands.base_picking import pr_base_picking
89
from codecov_cli.commands.commit import create_commit
910
from codecov_cli.commands.create_report_result import create_report_results
1011
from codecov_cli.commands.get_report_results import get_report_results
@@ -60,6 +61,7 @@ def cli(
6061
cli.add_command(create_report)
6162
cli.add_command(create_report_results)
6263
cli.add_command(get_report_results)
64+
cli.add_command(pr_base_picking)
6365
cli.add_command(label_analysis)
6466
cli.add_command(static_analysis)
6567

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import logging
2+
3+
from codecov_cli.helpers.request import log_warnings_and_errors_if_any, send_put_request
4+
5+
logger = logging.getLogger("codecovcli")
6+
7+
8+
def base_picking_logic(base_sha, pr, slug, token, service):
9+
data = {
10+
"user_provided_base_sha": base_sha,
11+
}
12+
headers = {"Authorization": f"token {token.hex}"}
13+
url = f"https://api.codecov.io/api/v1/{service}/{slug}/pulls/{pr}"
14+
sending_result = send_put_request(url=url, data=data, headers=headers)
15+
16+
log_warnings_and_errors_if_any(sending_result, "Base picking")
17+
return sending_result

tests/helpers/test_encoder.py

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,55 @@
11
import pytest
22

3-
from codecov_cli.helpers.encoder import encode_slug
3+
from codecov_cli.helpers.encoder import encode_slug, slug_without_subgroups_is_invalid
44

55

6-
def test_invalid_slug():
7-
slug = "invalid-slug"
6+
@pytest.mark.parametrize(
7+
"slug",
8+
[
9+
("invalid_slug"),
10+
(""),
11+
("/"),
12+
("//"),
13+
("///"),
14+
("random string"),
15+
(None),
16+
],
17+
)
18+
def test_encode_invalid_slug(slug):
819
with pytest.raises(ValueError) as ex:
920
encode_slug(slug)
1021

1122

12-
def test_owner_repo_slug():
13-
slug = "owner/repo"
14-
encoded_slug = encode_slug(slug)
15-
assert encoded_slug == "owner::::repo"
23+
@pytest.mark.parametrize(
24+
"slug, encoded_slug",
25+
[
26+
("owner/repo", "owner::::repo"),
27+
("owner/subgroup/repo", "owner:::subgroup::::repo"),
28+
],
29+
)
30+
def test_encode_valid_slug(slug, encoded_slug):
31+
expected_encoded_slug = encode_slug(slug)
32+
assert expected_encoded_slug == encoded_slug
33+
1634

35+
@pytest.mark.parametrize(
36+
"slug",
37+
[
38+
("invalid_slug"),
39+
(""),
40+
("/"),
41+
("//"),
42+
("///"),
43+
("random string"),
44+
("owner/subgroup/repo"),
45+
("owner//repo"),
46+
(None),
47+
],
48+
)
49+
def test_invalid_slug(slug):
50+
assert slug_without_subgroups_is_invalid(slug)
1751

18-
def test_owner_with_subgroups_slug():
19-
slug = "owner/subgroup/repo"
20-
encoded_slug = encode_slug(slug)
21-
assert encoded_slug == "owner:::subgroup::::repo"
52+
53+
def test_valid_slug():
54+
slug = "owner/repo"
55+
assert not slug_without_subgroups_is_invalid(slug)

tests/test_base_picking.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import uuid
2+
3+
from click.testing import CliRunner
4+
5+
from codecov_cli.commands.base_picking import pr_base_picking
6+
from codecov_cli.main import cli
7+
from codecov_cli.types import RequestError, RequestResult, RequestResultWarning
8+
from tests.test_helpers import parse_outstreams_into_log_lines
9+
10+
11+
def test_base_picking_command(mocker):
12+
mocked_response = mocker.patch(
13+
"codecov_cli.services.commit.base_picking.send_put_request",
14+
return_value=RequestResult(status_code=200, error=None, warnings=[], text=""),
15+
)
16+
token = uuid.uuid4()
17+
runner = CliRunner()
18+
result = runner.invoke(
19+
pr_base_picking,
20+
[
21+
"-t",
22+
token,
23+
"--pr",
24+
"11",
25+
"--base-sha",
26+
"9a6902ee94c18e8e27561ce316b16d75a02c7bc1",
27+
"--service",
28+
"github",
29+
"--slug",
30+
"owner/repo",
31+
],
32+
)
33+
assert result.exit_code == 0
34+
mocked_response.assert_called_once()
35+
36+
37+
def test_base_picking_command_slug_invalid(mocker):
38+
token = uuid.uuid4()
39+
runner = CliRunner()
40+
result = runner.invoke(
41+
pr_base_picking,
42+
[
43+
"-t",
44+
token,
45+
"--pr",
46+
"11",
47+
"--base-sha",
48+
"9a6902ee94c18e8e27561ce316b16d75a02c7bc1",
49+
"--service",
50+
"github",
51+
"--slug",
52+
"owner-repo",
53+
],
54+
)
55+
assert result.exit_code == 0
56+
assert (
57+
"error",
58+
"Slug is invalid. Slug should be in the form of owner_username/repo_name",
59+
) in parse_outstreams_into_log_lines(result.output)
60+
61+
62+
def test_base_picking_command_warnings(mocker):
63+
mocked_response = mocker.patch(
64+
"codecov_cli.services.commit.base_picking.send_put_request",
65+
return_value=RequestResult(
66+
error=None,
67+
warnings=[RequestResultWarning(message="some random warning")],
68+
status_code=200,
69+
text="",
70+
),
71+
)
72+
token = uuid.uuid4()
73+
runner = CliRunner()
74+
result = runner.invoke(
75+
pr_base_picking,
76+
[
77+
"-t",
78+
token,
79+
"--pr",
80+
"11",
81+
"--base-sha",
82+
"9a6902ee94c18e8e27561ce316b16d75a02c7bc1",
83+
"--service",
84+
"github",
85+
"--slug",
86+
"owner/repo",
87+
],
88+
)
89+
assert result.exit_code == 0
90+
assert (
91+
"info",
92+
"Base picking process had 1 warning",
93+
) in parse_outstreams_into_log_lines(result.output)
94+
assert (
95+
"warning",
96+
"Warning 1: some random warning",
97+
) in parse_outstreams_into_log_lines(result.output)
98+
mocked_response.assert_called_once()
99+
100+
101+
def test_base_picking_command_error(mocker):
102+
mocked_response = mocker.patch(
103+
"codecov_cli.services.commit.base_picking.send_put_request",
104+
return_value=RequestResult(
105+
status_code=401,
106+
error=RequestError(
107+
code="HTTP Error 401",
108+
description="Unauthorized",
109+
params={},
110+
),
111+
warnings=[],
112+
text="",
113+
),
114+
)
115+
token = uuid.uuid4()
116+
runner = CliRunner()
117+
result = runner.invoke(
118+
pr_base_picking,
119+
[
120+
"-t",
121+
token,
122+
"--pr",
123+
"11",
124+
"--base-sha",
125+
"9a6902ee94c18e8e27561ce316b16d75a02c7bc1",
126+
"--service",
127+
"github",
128+
"--slug",
129+
"owner/repo",
130+
],
131+
)
132+
mocked_response.assert_called_once()
133+
assert result.exit_code == 0
134+
assert (
135+
"error",
136+
"Base picking failed: Unauthorized",
137+
) in parse_outstreams_into_log_lines(result.output)

tests/test_codecov_cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ def test_existing_commands():
1313
"do-upload",
1414
"get-report-results",
1515
"label-analysis",
16+
"pr-base-picking",
1617
"static-analysis",
1718
]

0 commit comments

Comments
 (0)