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

Commit 96bed1f

Browse files
authored
Cleanup and optimize TestResultsFinisher (#924)
1 parent 50e7312 commit 96bed1f

File tree

3 files changed

+135
-189
lines changed

3 files changed

+135
-189
lines changed

services/test_results.py

Lines changed: 54 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import logging
22
from dataclasses import dataclass
33
from hashlib import sha256
4-
from typing import List, Sequence
4+
from typing import Sequence
55

66
from shared.plan.constants import FREE_PLAN_REPRESENTATIONS, TEAM_PLAN_REPRESENTATIONS
77
from shared.yaml import UserYaml
8-
from sqlalchemy import desc
8+
from sqlalchemy import desc, distinct, func
9+
from sqlalchemy.orm import joinedload
10+
from sqlalchemy.orm.session import Session
911

1012
from database.enums import ReportType
1113
from database.models import (
@@ -21,7 +23,6 @@
2123
from services.license import requires_license
2224
from services.processing.types import UploadArguments
2325
from services.report import BaseReportService
24-
from services.repository import get_repo_provider_service
2526
from services.urls import get_members_url, get_test_analytics_url
2627
from services.yaml import read_yaml_field
2728

@@ -121,7 +122,7 @@ def generate_test_id(repoid, testsuite, name, flags_hash):
121122
class TestResultsNotificationFailure:
122123
failure_message: str
123124
display_name: str
124-
envs: List[str]
125+
envs: list[str]
125126
test_id: str
126127
duration_seconds: float
127128
build_url: str | None = None
@@ -138,7 +139,7 @@ class TestResultsNotificationPayload:
138139
failed: int
139140
passed: int
140141
skipped: int
141-
failures: List[TestResultsNotificationFailure]
142+
failures: list[TestResultsNotificationFailure]
142143
flaky_tests: dict[str, FlakeInfo]
143144

144145

@@ -149,10 +150,7 @@ def wrap_in_details(summary: str, content: str):
149150

150151
def make_quoted(content: str) -> str:
151152
lines = content.splitlines()
152-
153-
result = ["> " + line for line in lines]
154-
155-
result = "\n".join(result)
153+
result = "\n".join("> " + line for line in lines)
156154
return f"\n{result}\n"
157155

158156

@@ -246,9 +244,7 @@ def build_message(self) -> str:
246244
if self.payload is None:
247245
raise ValueError("Payload passed to notifier is None, cannot build message")
248246

249-
message = []
250-
251-
message.append(f"### :x: {self.payload.failed} Tests Failed:")
247+
message = [f"### :x: {self.payload.failed} Tests Failed:"]
252248

253249
completed = self.payload.failed + self.payload.passed
254250

@@ -259,13 +255,13 @@ def build_message(self) -> str:
259255
]
260256

261257
failures = sorted(
262-
filter(
263-
lambda x: x.test_id not in self.payload.flaky_tests,
264-
self.payload.failures,
258+
(
259+
failure
260+
for failure in self.payload.failures
261+
if failure.test_id not in self.payload.flaky_tests
265262
),
266263
key=lambda x: (x.duration_seconds, x.display_name),
267264
)
268-
269265
if failures:
270266
failure_content = [f"{messagify_failure(failure)}" for failure in failures]
271267

@@ -276,19 +272,17 @@ def build_message(self) -> str:
276272

277273
message.append(top_3_failed_section)
278274

279-
flaky_failures = list(
280-
filter(
281-
lambda x: x.test_id in self.payload.flaky_tests,
282-
self.payload.failures,
283-
),
284-
)
285-
286-
flake_content = [
287-
f"{messagify_flake(flaky_failure, self.payload.flaky_tests[flaky_failure.test_id])}"
288-
for flaky_failure in flaky_failures
275+
flaky_failures = [
276+
failure
277+
for failure in self.payload.failures
278+
if failure.test_id in self.payload.flaky_tests
289279
]
280+
if flaky_failures:
281+
flake_content = [
282+
f"{messagify_flake(flaky_failure, self.payload.flaky_tests[flaky_failure.test_id])}"
283+
for flaky_failure in flaky_failures
284+
]
290285

291-
if flake_content:
292286
flaky_section = wrap_in_details(
293287
f"View the full list of {len(flaky_failures)} :snowflake: flaky tests",
294288
"\n".join(flake_content),
@@ -300,15 +294,11 @@ def build_message(self) -> str:
300294
return "\n".join(message)
301295

302296
def error_comment(self):
303-
self._repo_service = get_repo_provider_service(self.commit.repository)
304-
305297
pull = self.get_pull()
306298
if pull is None:
307299
log.info(
308300
"Not notifying since there is no pull request associated with this commit",
309-
extra=dict(
310-
commitid=self.commit.commitid,
311-
),
301+
extra=dict(commitid=self.commit.commitid),
312302
)
313303
return False, "no_pull"
314304

@@ -321,16 +311,11 @@ def error_comment(self):
321311
return (True, "comment_posted")
322312

323313
def upgrade_comment(self):
324-
if self._repo_service is None:
325-
self._repo_service = get_repo_provider_service(self.commit.repository)
326-
327314
pull = self.get_pull()
328315
if pull is None:
329316
log.info(
330317
"Not notifying since there is no pull request associated with this commit",
331-
extra=dict(
332-
commitid=self.commit.commitid,
333-
),
318+
extra=dict(commitid=self.commit.commitid),
334319
)
335320
return False, "no_pull"
336321

@@ -369,34 +354,53 @@ def upgrade_comment(self):
369354
return (True, "comment_posted")
370355

371356

372-
def latest_test_instances_for_a_given_commit(db_session, commit_id):
357+
def latest_failures_for_commit(
358+
db_session: Session, repo_id: int, commit_sha: str
359+
) -> list[TestInstance]:
373360
"""
374361
This will result in a SQL query that looks something like this:
375362
376-
SELECT DISTINCT ON (rt.test_id) rt.id, rt.external_id, rt.created_at, rt.updated_at, rt.test_id, rt.duration_seconds, rt.outcome, rt.upload_id, rt.failure_message
377-
FROM reports_testinstance rt JOIN reports_upload ru ON ru.id = rt.upload_id JOIN reports_commitreport rc ON rc.id = ru.report_id
378-
WHERE rc.commit_id = <commit_id> ORDER BY rt.test_id, ru.created_at desc
363+
SELECT DISTINCT ON (rti.test_id) rti.id, ...
364+
FROM reports_testinstance rti
365+
JOIN reports_upload ru ON ru.id = rti.upload_id
366+
LEFT OUTER JOIN reports_test rt ON rt.id = rti.test_id
367+
WHERE ...
368+
ORDER BY rti.test_id, ru.created_at DESC
379369
380-
The goal of this query is to return: "the latest test instance for each unique test based on upload creation time"
370+
The goal of this query is to return:
371+
> the latest test instance failure for each unique test based on upload creation time
381372
382-
The `DISTINCT ON` test_id with the order by test_id, enforces that we are only fetching one test instance for each test
373+
The `DISTINCT ON` test_id with the order by test_id, enforces that we are only fetching one test instance for each test.
383374
384-
The ordering of the upload.create_at desc enforces that we get the latest test instance for that unique test
375+
The ordering by `upload.create_at DESC` enforces that we get the latest test instance for that unique test.
385376
"""
377+
386378
return (
387379
db_session.query(TestInstance)
388-
.join(Upload)
389-
.join(CommitReport)
390-
.filter(
391-
CommitReport.commit_id == commit_id,
392-
)
380+
.join(TestInstance.upload)
381+
.options(joinedload(TestInstance.test))
382+
.filter(TestInstance.repoid == repo_id, TestInstance.commitid == commit_sha)
383+
.filter(TestInstance.outcome.in_(["failure", "error"]))
393384
.order_by(TestInstance.test_id)
394385
.order_by(desc(Upload.created_at))
395386
.distinct(TestInstance.test_id)
396387
.all()
397388
)
398389

399390

391+
def get_test_summary_for_commit(
392+
db_session: Session, repo_id: int, commit_sha: str
393+
) -> dict[str, int]:
394+
return dict(
395+
db_session.query(
396+
TestInstance.outcome, func.count(distinct(TestInstance.test_id))
397+
)
398+
.filter(TestInstance.repoid == repo_id, TestInstance.commitid == commit_sha)
399+
.group_by(TestInstance.outcome)
400+
.all()
401+
)
402+
403+
400404
def not_private_and_free_or_team(repo: Repository):
401405
return not (
402406
repo.private

0 commit comments

Comments
 (0)