Skip to content

Commit c2d3eb6

Browse files
authored
Remove unused buildbotapi code and test the rest (#207)
1 parent 3aeecca commit c2d3eb6

File tree

10 files changed

+1144
-407
lines changed

10 files changed

+1144
-407
lines changed

buildbotapi.py

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import asyncio
21
import json
32
from dataclasses import dataclass
43
from typing import Any, cast
@@ -12,7 +11,6 @@
1211
class Builder:
1312
builderid: int
1413
description: str | None
15-
masterids: list[int]
1614
name: str
1715
tags: list[str]
1816

@@ -23,26 +21,6 @@ def __hash__(self) -> int:
2321
return hash(self.builderid)
2422

2523

26-
@dataclass
27-
class Build:
28-
id: int
29-
is_currently_failing: bool
30-
builderid: int
31-
builder: Builder | None
32-
33-
def __init__(self, **kwargs: Any) -> None:
34-
self.__dict__.update(**kwargs)
35-
self.id = int(kwargs.get("number", -1))
36-
self.is_currently_failing = kwargs.get("currently_failing", False)
37-
self.builder = None
38-
39-
def __eq__(self, other: object) -> bool:
40-
return isinstance(other, Build) and self.id == other.id
41-
42-
def __hash__(self) -> int:
43-
return hash(self.id)
44-
45-
4624
class BuildBotAPI:
4725
def __init__(self, session: ClientSession) -> None:
4826
self._session = session
@@ -91,44 +69,3 @@ async def is_builder_failing_currently(self, builder: Builder) -> bool:
9169
if build["results"] == 2:
9270
return True
9371
return False
94-
95-
async def get_build(self, builder_id: int, build_id: int) -> Build:
96-
data = await self._fetch_json(
97-
f"https://buildbot.python.org/all/api/v2/builders/{builder_id}"
98-
f"/builds/{build_id}"
99-
)
100-
(build_data,) = data["builds"]
101-
build: Build = Build(**build_data)
102-
build.builder = (await self.all_builders())[build.builderid]
103-
build.is_currently_failing = await self.is_builder_failing_currently(
104-
build.builder
105-
)
106-
return build
107-
108-
async def get_recent_failures(self, limit: int = 100) -> set[Build]:
109-
data = await self._fetch_json(
110-
f"https://buildbot.python.org/all/api/v2/builds?"
111-
f"complete__eq=true&&results__eq=2&&"
112-
f"order=-complete_at&&limit={limit}"
113-
)
114-
115-
stable_builders = await self.stable_builders()
116-
117-
all_failures = {
118-
Build(**build)
119-
for build in data["builds"]
120-
if build["builderid"] in stable_builders
121-
}
122-
123-
for failure in all_failures:
124-
failure.builder = stable_builders[failure.builderid]
125-
126-
async def _get_missing_info(failure: Build) -> None:
127-
assert failure.builder is not None
128-
failure.is_currently_failing = await self.is_builder_failing_currently(
129-
failure.builder
130-
)
131-
132-
await asyncio.gather(*[_get_missing_info(failure) for failure in all_failures])
133-
134-
return all_failures

dev-requirements.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pytest
2+
pytest-aiohttp
23
pytest-cov
34
pytest-mock

dev-requirements.txt

Lines changed: 466 additions & 0 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
[tool.pytest.ini_options]
2+
asyncio_mode = "auto"
3+
asyncio_default_fixture_loop_scope = "function"
4+
15
[tool.mypy]
26
python_version = "3.12"
37
pretty = true

requirements.txt

Lines changed: 437 additions & 344 deletions
Large diffs are not rendered by default.

tests/buildbotapi/builders.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"builders": [
3+
{
4+
"builderid": 3,
5+
"description": null,
6+
"description_format": null,
7+
"description_html": null,
8+
"masterids": [
9+
1
10+
],
11+
"name": "AMD64 RHEL8 LTO 3.13",
12+
"projectid": null,
13+
"tags": [
14+
"3.13",
15+
"stable",
16+
"lto",
17+
"nondebug",
18+
"tier-1"
19+
]
20+
},
21+
{
22+
"builderid": 1623,
23+
"description": null,
24+
"description_format": null,
25+
"description_html": null,
26+
"masterids": [
27+
1
28+
],
29+
"name": "AMD64 Windows PGO NoGIL PR",
30+
"projectid": null,
31+
"tags": [
32+
"PullRequest",
33+
"unstable",
34+
"win64",
35+
"nogil",
36+
"nondebug",
37+
"pgo",
38+
"tier-1"
39+
]
40+
}
41+
],
42+
"meta": {
43+
"total": 3
44+
}
45+
}

tests/buildbotapi/failure.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"builds": [
3+
{
4+
"builderid": 3,
5+
"buildid": 1732278,
6+
"buildrequestid": 2341889,
7+
"complete": true,
8+
"complete_at": 1734198808,
9+
"locks_duration_s": 0,
10+
"masterid": 1,
11+
"number": 228,
12+
"properties": {},
13+
"results": 2,
14+
"started_at": 1734197714,
15+
"state_string": "failed test (failure)",
16+
"workerid": 28
17+
}
18+
],
19+
"meta": {
20+
"total": 1
21+
}
22+
}

tests/buildbotapi/no-builds.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"builds": [
3+
],
4+
"meta": {
5+
"total": 0
6+
}
7+
}

tests/buildbotapi/success.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"builds": [
3+
{
4+
"builderid": 3,
5+
"buildid": 1645411,
6+
"buildrequestid": 2211085,
7+
"complete": true,
8+
"complete_at": 1728312495,
9+
"locks_duration_s": 531,
10+
"masterid": 1,
11+
"number": 6844,
12+
"properties": {},
13+
"results": 0,
14+
"started_at": 1728311538,
15+
"state_string": "build successful",
16+
"workerid": 27
17+
}
18+
],
19+
"meta": {
20+
"total": 1
21+
}
22+
}

tests/test_buildbotapi.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
from functools import cache
2+
from unittest.mock import AsyncMock
3+
4+
import aiohttp
5+
import pytest
6+
7+
import buildbotapi
8+
9+
10+
def test_builder_class() -> None:
11+
# Arrange / Act
12+
builder = buildbotapi.Builder(
13+
builderid=123,
14+
description="my description",
15+
name="my name",
16+
tags=["tag1", "tag2"],
17+
)
18+
19+
# Assert
20+
assert builder.builderid == 123
21+
assert builder.description == "my description"
22+
assert builder.name == "my name"
23+
assert builder.tags == ["tag1", "tag2"]
24+
assert hash(builder) == 123
25+
26+
27+
@cache
28+
def load(filename: str) -> str:
29+
with open(filename) as f:
30+
return f.read()
31+
32+
33+
@pytest.mark.asyncio
34+
async def test_buildbotapi_authenticate() -> None:
35+
# Arrange
36+
async with AsyncMock(aiohttp.ClientSession) as mock_session:
37+
api = buildbotapi.BuildBotAPI(mock_session)
38+
39+
# Act
40+
await api.authenticate(token="")
41+
42+
# Assert
43+
mock_session.get.assert_called_with(
44+
"https://buildbot.python.org/all/auth/login", params={"token": ""}
45+
)
46+
47+
48+
@pytest.mark.asyncio
49+
async def test_buildbotapi_all_builders() -> None:
50+
# Arrange
51+
mock_session = AsyncMock(aiohttp.ClientSession)
52+
mock_session.get.return_value.__aenter__.return_value.status = 200
53+
mock_session.get.return_value.__aenter__.return_value.text.return_value = load(
54+
"tests/buildbotapi/builders.json"
55+
)
56+
api = buildbotapi.BuildBotAPI(mock_session)
57+
58+
# Act
59+
all_builders = await api.all_builders()
60+
61+
# Assert
62+
mock_session.get.assert_called_with(
63+
"https://buildbot.python.org/all/api/v2/builders"
64+
)
65+
assert len(all_builders) == 2
66+
assert all_builders[3].name == "AMD64 RHEL8 LTO 3.13"
67+
assert all_builders[1623].name == "AMD64 Windows PGO NoGIL PR"
68+
69+
70+
@pytest.mark.asyncio
71+
async def test_buildbotapi_all_builders_with_branch() -> None:
72+
# Arrange
73+
mock_session = AsyncMock(aiohttp.ClientSession)
74+
mock_session.get.return_value.__aenter__.return_value.status = 200
75+
mock_session.get.return_value.__aenter__.return_value.text.return_value = load(
76+
"tests/buildbotapi/builders.json"
77+
)
78+
api = buildbotapi.BuildBotAPI(mock_session)
79+
80+
# Act
81+
await api.all_builders(branch="3.13")
82+
83+
# Assert
84+
mock_session.get.assert_called_with(
85+
"https://buildbot.python.org/all/api/v2/builders?tags__contains=3.13"
86+
)
87+
88+
89+
@pytest.mark.asyncio
90+
async def test_buildbotapi_stable_builders() -> None:
91+
# Arrange
92+
mock_session = AsyncMock(aiohttp.ClientSession)
93+
mock_session.get.return_value.__aenter__.return_value.status = 200
94+
mock_session.get.return_value.__aenter__.return_value.text.return_value = load(
95+
"tests/buildbotapi/builders.json"
96+
)
97+
api = buildbotapi.BuildBotAPI(mock_session)
98+
99+
# Act
100+
all_builders = await api.stable_builders()
101+
102+
# Assert
103+
mock_session.get.assert_called_with(
104+
"https://buildbot.python.org/all/api/v2/builders"
105+
)
106+
assert len(all_builders) == 1
107+
assert all_builders[3].name == "AMD64 RHEL8 LTO 3.13"
108+
assert "stable" in all_builders[3].tags
109+
110+
111+
@pytest.mark.asyncio
112+
@pytest.mark.parametrize(
113+
["json_data", "expected"],
114+
[
115+
("tests/buildbotapi/success.json", False),
116+
("tests/buildbotapi/failure.json", True),
117+
("tests/buildbotapi/no-builds.json", False),
118+
],
119+
)
120+
async def test_buildbotapi_is_builder_failing_currently_yes(
121+
json_data: str, expected: bool
122+
) -> None:
123+
# Arrange
124+
mock_session = AsyncMock(aiohttp.ClientSession)
125+
mock_session.get.return_value.__aenter__.return_value.status = 200
126+
mock_session.get.return_value.__aenter__.return_value.text.return_value = load(
127+
json_data
128+
)
129+
api = buildbotapi.BuildBotAPI(mock_session)
130+
builder = buildbotapi.Builder(builderid=3)
131+
132+
# Act
133+
failing = await api.is_builder_failing_currently(builder=builder)
134+
135+
# Assert
136+
mock_session.get.assert_called_with(
137+
"https://buildbot.python.org/all/api/v2/builds?complete__eq=true"
138+
"&&builderid__eq=3&&order=-complete_at&&limit=1"
139+
)
140+
assert failing is expected

0 commit comments

Comments
 (0)