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

Commit 7f8247d

Browse files
committed
create utils for accessing testrun timescale models
1 parent 18f0799 commit 7f8247d

File tree

2 files changed

+555
-0
lines changed

2 files changed

+555
-0
lines changed

services/ta_timeseries.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
from __future__ import annotations
2+
3+
from datetime import datetime
4+
5+
import mmh3
6+
import test_results_parser
7+
from django.db import connections
8+
from django.db.models import Q
9+
from shared.django_apps.timeseries.models import (
10+
Testrun,
11+
TestrunBranchSummary,
12+
TestrunSummary,
13+
)
14+
15+
16+
def calc_test_id(name: str, classname: str, testsuite: str) -> bytes:
17+
h = mmh3.mmh3_x64_128() # assumes we're running on x64 machines
18+
h.update(testsuite.encode("utf-8"))
19+
h.update(classname.encode("utf-8"))
20+
h.update(name.encode("utf-8"))
21+
test_id_hash = h.digest()
22+
23+
return test_id_hash
24+
25+
26+
def calc_flags_hash(flags: list[str]) -> bytes | None:
27+
flags_str = " ".join(sorted(flags)) # we know that flags cannot contain spaces
28+
29+
# returns a tuple of two int64 values
30+
# we only need the first one
31+
flags_hash, _ = mmh3.hash64(flags_str, signed=False)
32+
flags_hash_bytes = flags_hash.to_bytes(8)
33+
return flags_hash_bytes
34+
35+
36+
def insert_testrun(
37+
timestamp: datetime,
38+
repo_id: int | None,
39+
commit_sha: str | None,
40+
branch: str | None,
41+
upload_id: int | None,
42+
flags: list[str] | None,
43+
parsing_info: test_results_parser.ParsingInfo,
44+
flaky_test_ids: set[bytes] | None = None,
45+
):
46+
for testrun in parsing_info["testruns"]:
47+
test_id = calc_test_id(
48+
testrun["name"], testrun["classname"], testrun["testsuite"]
49+
)
50+
flags_hash = calc_flags_hash(flags) if flags else None
51+
outcome = testrun["outcome"]
52+
53+
if outcome == "failure" and flaky_test_ids and test_id in flaky_test_ids:
54+
outcome = "flaky_failure"
55+
56+
Testrun.objects.create(
57+
timestamp=timestamp,
58+
test_id=test_id,
59+
flags_hash=flags_hash,
60+
name=testrun["name"],
61+
classname=testrun["classname"],
62+
testsuite=testrun["testsuite"],
63+
computed_name=testrun["computed_name"],
64+
outcome=outcome,
65+
duration_seconds=testrun["duration"],
66+
failure_message=testrun["failure_message"],
67+
framework=parsing_info["framework"],
68+
filename=testrun["filename"],
69+
repo_id=repo_id,
70+
commit_sha=commit_sha,
71+
branch=branch,
72+
flags=flags,
73+
upload_id=upload_id,
74+
)
75+
76+
77+
def get_pr_comment_failures(
78+
repo_id: int, commit_sha: str
79+
) -> list[dict[str, bytes | str | None]]:
80+
with connections["timeseries"].cursor() as cursor:
81+
cursor.execute(
82+
"""
83+
SELECT
84+
test_id,
85+
flags_hash,
86+
FIRST(computed_name, timestamp) as computed_name,
87+
FIRST(failure_message, timestamp) as failure_message
88+
FROM timeseries_testrun
89+
WHERE repo_id = %s AND commit_sha = %s AND outcome IN ('failure', 'flaky_failure')
90+
GROUP BY test_id, flags_hash
91+
""",
92+
[repo_id, commit_sha],
93+
)
94+
return [
95+
{
96+
"test_id": bytes(test_id),
97+
"flags_hash": bytes(flags_hash),
98+
"computed_name": computed_name,
99+
"failure_message": failure_message,
100+
}
101+
for test_id, flags_hash, computed_name, failure_message in cursor.fetchall()
102+
]
103+
104+
105+
def get_pr_comment_agg(repo_id: int, commit_sha: str) -> list[dict[str, int]]:
106+
with connections["timeseries"].cursor() as cursor:
107+
cursor.execute(
108+
"""
109+
SELECT outcome, count(*) FROM (
110+
SELECT
111+
test_id,
112+
flags_hash,
113+
FIRST(outcome, timestamp) as outcome
114+
FROM timeseries_testrun
115+
WHERE repo_id = %s AND commit_sha = %s
116+
GROUP BY test_id, flags_hash
117+
) AS t
118+
GROUP BY outcome
119+
""",
120+
[repo_id, commit_sha],
121+
)
122+
return [
123+
{"outcome": outcome, "count": count} for outcome, count in cursor.fetchall()
124+
]
125+
126+
127+
def get_testruns_for_flake_detection(
128+
upload_id: int,
129+
flaky_test_ids: set[bytes],
130+
) -> list[Testrun]:
131+
return list(
132+
Testrun.objects.filter(
133+
Q(upload_id=upload_id)
134+
& (
135+
Q(outcome="failure")
136+
| Q(outcome="flaky_failure")
137+
| (Q(outcome="pass") & Q(test_id__in=flaky_test_ids))
138+
)
139+
)
140+
)
141+
142+
143+
def update_testrun_to_flaky(
144+
timestamp: datetime, test_id: bytes, flags_hash: bytes | None
145+
):
146+
with connections["timeseries"].cursor() as cursor:
147+
cursor.execute(
148+
"UPDATE timeseries_testrun SET outcome = %s WHERE timestamp = %s AND test_id = %s AND flags_hash = %s",
149+
["flaky_failure", timestamp, test_id, flags_hash],
150+
)
151+
152+
153+
def get_testrun_summary(repo_id: int) -> list[TestrunSummary]:
154+
return list(
155+
TestrunSummary.objects.filter(repo_id=repo_id)
156+
.order_by("-timestamp_bin")
157+
.distinct("timestamp_bin", "testsuite", "classname", "name")
158+
)
159+
160+
161+
def get_testrun_branch_summary(repo_id: int, branch: str) -> list[TestrunBranchSummary]:
162+
return list(
163+
TestrunBranchSummary.objects.filter(repo_id=repo_id, branch=branch)
164+
.order_by("-timestamp_bin")
165+
.distinct("timestamp_bin", "testsuite", "classname", "name")
166+
)

0 commit comments

Comments
 (0)