Skip to content

Commit c1c2577

Browse files
Fix: hardware details build commits (#1748)
* fix: correct typo on timestamp * fix(commitHistory): correct build-test filter relation Fixes a problem where when asking for builds with a hardware filter on hardware details, we would either get 0 builds with that hardware or builds with that hardware that weren't necessarily related to tests with that hardware. This is done by adding a paramenter to the endpoint to identify this exact scenario and rewiring the flow accordingly. This parameter is needed because if we filter on treeListing, we will want any build related to that hardware to be returned, even if they are not related to tests from that hardware Closes #1747
1 parent 8fc49d1 commit c1c2577

8 files changed

Lines changed: 415 additions & 82 deletions

File tree

backend/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,15 @@ We are not using sessions or anything like that right now, so changing the secre
176176
## Requests
177177
In the `/requests` directory we have scripts that execute requests to endpoints using [httpie](https://httpie.io/). They serve as examples of how you could use the API, and what responses you can expect. If you are contributing to some endpoint and change any of the responses, please remember to update those files.
178178
179+
### Tree commits builds scope
180+
181+
For `GET /api/tree/<commit_hash>/commits` (and direct tree variant), when requesting only builds (`types=builds`) there are two supported scopes:
182+
183+
- default behavior (no extra flag): returns builds that match the active filters directly (used by treeDetails);
184+
- relation-gated behavior: set `builds_related_to_filtered_tests_only=true` to return only builds related to tests/boots that pass the active filters (used by hardwareDetails commit navigation).
185+
186+
This flag is optional and defaults to `false` to preserve existing behavior.
187+
179188
## Debug
180189
181190
For debugging we have four env variables:
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
from datetime import datetime, timezone
2+
from unittest.mock import patch
3+
4+
from django.test import SimpleTestCase
5+
from rest_framework.test import APIRequestFactory
6+
7+
from kernelCI_app.views.treeCommitsHistory import (
8+
TreeCommitsHistory,
9+
TreeCommitsHistoryDirect,
10+
)
11+
12+
13+
class TestTreeCommitsHistory(SimpleTestCase):
14+
def setUp(self):
15+
self.factory = APIRequestFactory()
16+
self.view = TreeCommitsHistory()
17+
self.direct_view = TreeCommitsHistoryDirect()
18+
self.commit_hash = "1f44358e5057873ced21617aca6443b8b9e9c191"
19+
self.url = f"/api/tree/{self.commit_hash}/commits"
20+
21+
@patch("kernelCI_app.views.treeCommitsHistory.get_tree_commit_history")
22+
def test_builds_with_hardware_filter_returns_non_empty_response(
23+
self, mock_get_tree_commit_history
24+
):
25+
mock_get_tree_commit_history.return_value = [
26+
(
27+
self.commit_hash,
28+
"commit-name",
29+
["v1"],
30+
datetime(2026, 2, 1, tzinfo=timezone.utc),
31+
120,
32+
"x86_64",
33+
"gcc",
34+
"defconfig",
35+
"PASS",
36+
"maestro",
37+
"build-1",
38+
{"platform": "acer-cp514-2h-1160g7-volteer", "lab": "lab-a"},
39+
"boot",
40+
"PASS",
41+
30,
42+
["acer-cp514-2h-1160g7-volteer"],
43+
{"runtime": {"platform": "acer-cp514-2h-1160g7-volteer"}},
44+
"maestro",
45+
"lab-a",
46+
"test-1",
47+
None,
48+
None,
49+
None,
50+
None,
51+
)
52+
]
53+
54+
request = self.factory.get(
55+
self.url,
56+
{
57+
"origin": "maestro",
58+
"git_url": "https://chromium.googlesource.com/chromiumos/third_party/kernel.git",
59+
"git_branch": "chromeos-5.4",
60+
"start_timestamp_in_seconds": "1769779800",
61+
"end_timestamp_in_seconds": "1771507800",
62+
"types": "builds",
63+
"builds_related_to_filtered_tests_only": "true",
64+
"filter_test.hardware": "acer-cp514-2h-1160g7-volteer",
65+
},
66+
)
67+
68+
response = self.view.get(request, commit_hash=self.commit_hash)
69+
70+
self.assertEqual(response.status_code, 200)
71+
self.assertEqual(len(response.data), 1)
72+
self.assertEqual(response.data[0]["git_commit_hash"], self.commit_hash)
73+
self.assertEqual(response.data[0]["builds"]["PASS"], 1)
74+
self.assertEqual(sum(response.data[0]["boots"].values()), 0)
75+
self.assertEqual(sum(response.data[0]["tests"].values()), 0)
76+
77+
mock_get_tree_commit_history.assert_called_once_with(
78+
commit_hash=self.commit_hash,
79+
origin="maestro",
80+
git_url="https://chromium.googlesource.com/chromiumos/third_party/kernel.git",
81+
git_branch="chromeos-5.4",
82+
tree_name=None,
83+
include_types=["builds", "boots", "tests"],
84+
)
85+
86+
@patch("kernelCI_app.views.treeCommitsHistory.get_tree_commit_history")
87+
def test_builds_default_scope_does_not_expand_include_types(
88+
self, mock_get_tree_commit_history
89+
):
90+
mock_get_tree_commit_history.return_value = [
91+
(
92+
self.commit_hash,
93+
"commit-name",
94+
["v1"],
95+
datetime(2026, 2, 1, tzinfo=timezone.utc),
96+
120,
97+
"x86_64",
98+
"gcc",
99+
"defconfig",
100+
"PASS",
101+
"maestro",
102+
"build-1",
103+
{"platform": "acer-cp514-2h-1160g7-volteer", "lab": "lab-a"},
104+
None,
105+
None,
106+
None,
107+
None,
108+
None,
109+
None,
110+
None,
111+
None,
112+
None,
113+
None,
114+
None,
115+
None,
116+
)
117+
]
118+
119+
request = self.factory.get(
120+
self.url,
121+
{
122+
"origin": "maestro",
123+
"git_url": "https://chromium.googlesource.com/chromiumos/third_party/kernel.git",
124+
"git_branch": "chromeos-5.4",
125+
"types": "builds",
126+
},
127+
)
128+
129+
response = self.view.get(request, commit_hash=self.commit_hash)
130+
131+
self.assertEqual(response.status_code, 200)
132+
self.assertEqual(response.data[0]["builds"]["PASS"], 1)
133+
134+
mock_get_tree_commit_history.assert_called_once_with(
135+
commit_hash=self.commit_hash,
136+
origin="maestro",
137+
git_url="https://chromium.googlesource.com/chromiumos/third_party/kernel.git",
138+
git_branch="chromeos-5.4",
139+
tree_name=None,
140+
include_types=["builds"],
141+
)
142+
143+
@patch("kernelCI_app.views.treeCommitsHistory.get_tree_commit_history")
144+
def test_builds_relation_scope_counts_only_builds_linked_to_filtered_tests(
145+
self, mock_get_tree_commit_history
146+
):
147+
target_hardware = "acer-cp514-2h-1160g7-volteer"
148+
149+
mock_get_tree_commit_history.return_value = [
150+
(
151+
self.commit_hash,
152+
"commit-name",
153+
["v1"],
154+
datetime(2026, 2, 1, tzinfo=timezone.utc),
155+
120,
156+
"x86_64",
157+
"gcc",
158+
"defconfig",
159+
"PASS",
160+
"maestro",
161+
"build-unrelated",
162+
{"platform": target_hardware, "lab": "lab-a"},
163+
None,
164+
None,
165+
None,
166+
None,
167+
None,
168+
None,
169+
None,
170+
None,
171+
None,
172+
None,
173+
None,
174+
None,
175+
),
176+
(
177+
self.commit_hash,
178+
"commit-name",
179+
["v1"],
180+
datetime(2026, 2, 1, tzinfo=timezone.utc),
181+
180,
182+
"x86_64",
183+
"gcc",
184+
"defconfig",
185+
"PASS",
186+
"maestro",
187+
"build-related",
188+
{"platform": "some-other-platform", "lab": "lab-a"},
189+
"boot",
190+
"PASS",
191+
30,
192+
[target_hardware],
193+
{"runtime": {"platform": target_hardware}},
194+
"maestro",
195+
"lab-a",
196+
"test-related",
197+
None,
198+
None,
199+
None,
200+
None,
201+
),
202+
]
203+
204+
request = self.factory.get(
205+
self.url,
206+
{
207+
"origin": "maestro",
208+
"git_url": "https://chromium.googlesource.com/chromiumos/third_party/kernel.git",
209+
"git_branch": "chromeos-5.4",
210+
"types": "builds",
211+
"builds_related_to_filtered_tests_only": "true",
212+
"filter_test.hardware": target_hardware,
213+
},
214+
)
215+
216+
response = self.view.get(request, commit_hash=self.commit_hash)
217+
218+
self.assertEqual(response.status_code, 200)
219+
self.assertEqual(response.data[0]["builds"]["PASS"], 1)
220+
self.assertEqual(sum(response.data[0]["boots"].values()), 0)
221+
self.assertEqual(sum(response.data[0]["tests"].values()), 0)
222+
223+
@patch("kernelCI_app.views.treeCommitsHistory.get_tree_commit_history")
224+
def test_direct_tree_details_builds_with_hardware_filter_is_not_empty(
225+
self, mock_get_tree_commit_history
226+
):
227+
commit_hash = "6bd9ed02871f22beb0e50690b0c3caf457104f7c"
228+
hardware = "fsl,imx8mp-evk"
229+
direct_url = "/api/tree/mainline/master/" f"{commit_hash}/commits"
230+
231+
mock_get_tree_commit_history.return_value = [
232+
(
233+
commit_hash,
234+
"commit-name",
235+
["v1"],
236+
datetime(2026, 2, 1, tzinfo=timezone.utc),
237+
120,
238+
"arm64",
239+
"gcc",
240+
"defconfig",
241+
"PASS",
242+
"maestro",
243+
"build-1",
244+
{"platform": hardware, "lab": "lab-a"},
245+
None,
246+
None,
247+
None,
248+
None,
249+
{"platform": "unknown"},
250+
None,
251+
"lab-a",
252+
None,
253+
None,
254+
None,
255+
None,
256+
None,
257+
)
258+
]
259+
260+
request = self.factory.get(
261+
direct_url,
262+
{
263+
"origin": "maestro",
264+
"git_url": "",
265+
"git_branch": "master",
266+
"types": "builds",
267+
"filter_test.hardware": hardware,
268+
},
269+
)
270+
271+
response = self.direct_view.get(
272+
request,
273+
commit_hash=commit_hash,
274+
tree_name="mainline",
275+
git_branch="master",
276+
)
277+
278+
self.assertEqual(response.status_code, 200)
279+
self.assertGreater(len(response.data), 0)
280+
self.assertEqual(response.data[0]["builds"]["PASS"], 1)

backend/kernelCI_app/typeModels/treeCommits.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,23 @@ class DirectTreeCommitsQueryParameters(BaseModel):
2525
origin: str = Field(
2626
DEFAULT_ORIGIN, description=DocStrings.TREE_COMMIT_ORIGIN_DESCRIPTION
2727
)
28-
start_time_stamp_in_seconds: Optional[str] = Field(
28+
start_timestamp_in_seconds: Optional[str] = Field(
2929
None, description=DocStrings.TREE_COMMIT_START_TS_DESCRIPTION
3030
)
31-
end_time_stamp_in_seconds: Optional[str] = Field(
31+
end_timestamp_in_seconds: Optional[str] = Field(
3232
None, description=DocStrings.TREE_COMMIT_END_TS_DESCRIPTION
3333
)
3434
types: Optional[list[TreeEntityTypes]] = Field(
3535
None,
3636
description="List of types to include (builds, boots, tests)",
3737
)
38+
builds_related_to_filtered_tests_only: bool = Field(
39+
False,
40+
description=(
41+
"When true, and requesting only builds, count only builds related to "
42+
"tests/boots that pass the current filters."
43+
),
44+
)
3845
# TODO: Add filters field in this model
3946

4047
@field_validator("types", mode="before")

0 commit comments

Comments
 (0)