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

Commit d4d1fa4

Browse files
feat: add backfill one off scripts (#734)
1 parent 64598f2 commit d4d1fa4

8 files changed

Lines changed: 543 additions & 3 deletions

one_off_script.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import os
2+
3+
import django
4+
5+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_scaffold.settings")
6+
django.setup()
7+
8+
if __name__ == "__main__":
9+
from one_off_scripts.backfill_daily_test_rollups import run_impl
10+
from one_off_scripts.backfill_test_flag_bridges import backfill_test_flag_bridges
11+
12+
run_impl()
13+
backfill_test_flag_bridges()

one_off_scripts/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import os
2+
3+
import django
4+
5+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_scaffold.settings")
6+
django.setup()
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import logging
2+
from collections import defaultdict
3+
from dataclasses import dataclass, field
4+
from datetime import date, datetime, timedelta
5+
6+
from django.db import transaction as django_transaction
7+
from shared.django_apps.core.models import Repository
8+
from shared.django_apps.reports.models import DailyTestRollup, Flake, TestInstance
9+
from test_results_parser import Outcome
10+
11+
logging.basicConfig(level=logging.INFO)
12+
log = logging.getLogger()
13+
14+
15+
@dataclass
16+
class RollupObj:
17+
pass_count: int
18+
fail_count: int
19+
skip_count: int
20+
flaky_fail_count: int
21+
22+
sum_duration_seconds: float
23+
last_duration_seconds: float
24+
25+
latest_run: datetime
26+
27+
commits_where_fail: set[str] = field(default_factory=set)
28+
29+
30+
def get_test_analytics_repos(start_repoid):
31+
# get all repos that have test_analytics_enabled == True
32+
test_analytics_repos = Repository.objects.filter(
33+
test_analytics_enabled=True
34+
).order_by("repoid")
35+
36+
if start_repoid is not None:
37+
test_analytics_repos = test_analytics_repos.filter(repoid__gte=start_repoid)
38+
39+
return test_analytics_repos
40+
41+
42+
def process_instance(
43+
rollup_dict: dict[tuple[str, str], RollupObj],
44+
flake_dict: dict[str, list[tuple[datetime, datetime | None]]],
45+
instance: TestInstance,
46+
):
47+
pass_count = 0
48+
fail_count = 0
49+
skip_count = 0
50+
flaky_fail_count = 0
51+
duration_seconds = instance.duration_seconds
52+
created_at = instance.created_at
53+
commitid = instance.commitid
54+
55+
match instance.outcome:
56+
case Outcome.Pass:
57+
pass_count = 1
58+
case Outcome.Skip:
59+
skip_count = 1
60+
case _:
61+
fail_count = 1
62+
if (flaky_range_list := flake_dict.get(instance.test_id)) is not None:
63+
for range in flaky_range_list:
64+
if range[0] <= instance.created_at and (
65+
range[1] is None or instance.created_at < range[1]
66+
):
67+
flaky_fail_count += 1
68+
break
69+
70+
if (entry := rollup_dict.get((instance.test_id, instance.branch))) is not None:
71+
entry.pass_count += pass_count
72+
entry.fail_count += fail_count
73+
entry.skip_count += skip_count
74+
entry.flaky_fail_count += flaky_fail_count
75+
entry.sum_duration_seconds += duration_seconds
76+
entry.last_duration_seconds = duration_seconds
77+
entry.latest_run = created_at
78+
if commitid:
79+
entry.commits_where_fail.add(commitid)
80+
else:
81+
rollup_dict[(instance.test_id, instance.branch)] = RollupObj(
82+
pass_count,
83+
fail_count,
84+
skip_count,
85+
flaky_fail_count,
86+
duration_seconds,
87+
duration_seconds,
88+
created_at,
89+
set(),
90+
)
91+
if commitid:
92+
rollup_dict[(instance.test_id, instance.branch)].commits_where_fail.add(
93+
commitid
94+
)
95+
96+
97+
def save_rollups(rollup_dict, repoid, date):
98+
rollups_to_create = []
99+
for obj_key, obj in rollup_dict.items():
100+
rollup = DailyTestRollup(
101+
repoid=repoid,
102+
date=date,
103+
test_id=obj_key[0],
104+
branch=obj_key[1],
105+
pass_count=obj.pass_count,
106+
fail_count=obj.fail_count,
107+
skip_count=obj.skip_count,
108+
flaky_fail_count=obj.flaky_fail_count,
109+
commits_where_fail=list(obj.commits_where_fail),
110+
latest_run=obj.latest_run,
111+
last_duration_seconds=obj.last_duration_seconds,
112+
avg_duration_seconds=obj.sum_duration_seconds
113+
/ (obj.pass_count + obj.fail_count),
114+
)
115+
116+
rollups_to_create.append(rollup)
117+
118+
DailyTestRollup.objects.bulk_create(rollups_to_create, 1000)
119+
120+
121+
def backfill_test_rollups(
122+
start_repoid: int | None = None,
123+
start_date: str | None = None, # default is 2024-07-16
124+
end_date: str | None = None, # default is 2024-09-17
125+
) -> dict[str, bool]:
126+
log.info(
127+
"Updating test instances",
128+
extra=dict(start_repoid=start_repoid, start_date=start_date, end_date=end_date),
129+
)
130+
test_analytics_repos = get_test_analytics_repos(start_repoid)
131+
132+
chunk_size = 10000
133+
134+
log.info(
135+
"Starting backfill for repos",
136+
extra=dict(repos=[repo.repoid for repo in test_analytics_repos]),
137+
)
138+
139+
for repo in test_analytics_repos:
140+
repoid = repo.repoid
141+
log.info("Starting backfill for repo", extra=dict(repoid=repoid))
142+
143+
curr_date = date.fromisoformat(start_date) if start_date else date(2024, 7, 16)
144+
until_date = date.fromisoformat(end_date) if end_date else date(2024, 9, 17)
145+
146+
# delete all existing rollups for this day
147+
DailyTestRollup.objects.filter(
148+
repoid=repoid, date__gte=curr_date, date__lte=until_date
149+
).delete()
150+
django_transaction.commit()
151+
log.info("Deleted rollups for repo", extra=dict(repoid=repoid))
152+
153+
# get flakes
154+
flake_list = list(Flake.objects.filter(repository_id=repoid))
155+
156+
flake_dict: dict[str, list[tuple[datetime, datetime | None]]] = defaultdict(
157+
list
158+
)
159+
for flake in flake_list:
160+
flake_dict[flake.test_id].append((flake.start_date, flake.end_date))
161+
162+
while curr_date <= until_date:
163+
log.info(
164+
"Starting backfill for repo on date",
165+
extra=dict(repoid=repoid, date=curr_date),
166+
)
167+
168+
rollup_dict: dict[tuple[str, str], RollupObj] = {}
169+
170+
test_instances = TestInstance.objects.filter(
171+
repoid=repoid, created_at__date=curr_date
172+
).order_by("created_at")
173+
174+
num_test_instances = test_instances.count()
175+
if num_test_instances == 0:
176+
curr_date += timedelta(days=1)
177+
continue
178+
179+
chunks = [
180+
test_instances[i : i + chunk_size]
181+
for i in range(0, num_test_instances, chunk_size)
182+
]
183+
184+
for chunk in chunks:
185+
for instance in chunk:
186+
if instance.branch is None or instance.commitid is None:
187+
continue
188+
189+
process_instance(rollup_dict, flake_dict, instance)
190+
191+
save_rollups(rollup_dict, repoid, curr_date)
192+
django_transaction.commit()
193+
log.info(
194+
"Committed repo for day",
195+
extra=dict(repoid=repoid, date=curr_date),
196+
)
197+
curr_date += timedelta(days=1)
198+
199+
log.info("Finished backfill for repo", extra=dict(repoid=repoid))
200+
201+
return {"successful": True}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import logging
2+
3+
from django.db import transaction as django_transaction
4+
from shared.django_apps.core.models import Repository
5+
from shared.django_apps.reports.models import (
6+
RepositoryFlag,
7+
Test,
8+
TestFlagBridge,
9+
TestInstance,
10+
)
11+
12+
logging.basicConfig(level=logging.INFO)
13+
log = logging.getLogger()
14+
15+
16+
def backfill_test_flag_bridges(repoid=None):
17+
log.info("Backfilling TestFlagBridge objects", extra=dict(repoid=repoid))
18+
repos = Repository.objects.filter(test_analytics_enabled=True)
19+
if repoid is not None:
20+
repos = repos.filter(repoid=repoid)
21+
22+
for repo in repos:
23+
tests = Test.objects.filter(repository_id=repo.repoid)
24+
25+
flags = {
26+
flag.flag_name: flag
27+
for flag in RepositoryFlag.objects.filter(repository=repo)
28+
}
29+
30+
bridges_to_create = []
31+
for test in tests:
32+
TestFlagBridge.objects.filter(test=test).delete()
33+
34+
first_test_instance = (
35+
TestInstance.objects.filter(test_id=test.id)
36+
.select_related("upload")
37+
.first()
38+
)
39+
40+
if first_test_instance is None:
41+
continue
42+
43+
flag_names = first_test_instance.upload.flag_names
44+
45+
for flag_name in flag_names:
46+
new_bridge = TestFlagBridge(test=test, flag=flags[flag_name])
47+
bridges_to_create.append(new_bridge)
48+
49+
TestFlagBridge.objects.bulk_create(bridges_to_create, 1000)
50+
log.info(
51+
"Done creating flag bridges for repo",
52+
extra=dict(repoid=repoid, num_tests=len(tests)),
53+
)
54+
django_transaction.commit()

0 commit comments

Comments
 (0)