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

Commit 7857053

Browse files
perf: Fix TestResults SQL query to not take as long (#880)
1 parent 5a937da commit 7857053

File tree

9 files changed

+719
-200
lines changed

9 files changed

+719
-200
lines changed

graphql_api/tests/test_test_analytics.py

Lines changed: 206 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime
2+
from base64 import b64encode
23

34
from django.test import TransactionTestCase
45
from shared.django_apps.reports.tests.factories import FlakeFactory
@@ -17,6 +18,10 @@
1718
from .helper import GraphQLTestHelper
1819

1920

21+
def base64_encode_string(x: str) -> str:
22+
return b64encode(x.encode()).decode("utf-8")
23+
24+
2025
class TestAnalyticsTestCase(GraphQLTestHelper, TransactionTestCase):
2126
def setUp(self) -> None:
2227
self.owner = OwnerFactory(username="codecov-user")
@@ -119,6 +124,28 @@ def test_branch_filter_on_test_results(self) -> None:
119124
)
120125
assert res["testResults"] == {"edges": [{"node": {"name": test.name}}]}
121126

127+
def test_interval_filter_on_test_results(self) -> None:
128+
repo = RepositoryFactory(author=self.owner, active=True, private=True)
129+
test = TestFactory(repository=repo)
130+
test2 = TestFactory(repository=repo)
131+
_ = DailyTestRollupFactory(
132+
test=test,
133+
date=datetime.datetime.now() - datetime.timedelta(days=7),
134+
repoid=repo.repoid,
135+
branch="main",
136+
)
137+
_ = DailyTestRollupFactory(
138+
test=test2,
139+
date=datetime.datetime.now(),
140+
repoid=repo.repoid,
141+
branch="feature",
142+
)
143+
res = self.fetch_test_analytics(
144+
repo.name,
145+
"""testResults(filters: { interval: INTERVAL_1_DAY }) { edges { node { name } } }""",
146+
)
147+
assert res["testResults"] == {"edges": [{"node": {"name": test2.name}}]}
148+
122149
def test_flaky_filter_on_test_results(self) -> None:
123150
repo = RepositoryFactory(author=self.owner, active=True, private=True)
124151
test = TestFactory(repository=repo)
@@ -167,6 +194,7 @@ def test_failed_filter_on_test_results(self) -> None:
167194
assert res["testResults"] == {"edges": [{"node": {"name": test2.name}}]}
168195

169196
def test_skipped_filter_on_test_results(self) -> None:
197+
# note - this test guards against division by zero errors for the failure/flake rate
170198
repo = RepositoryFactory(author=self.owner, active=True, private=True)
171199
test = TestFactory(repository=repo)
172200
test2 = TestFactory(repository=repo)
@@ -367,8 +395,8 @@ def test_last_duration_ordering_on_test_results(self) -> None:
367395
)
368396
assert res["testResults"] == {
369397
"edges": [
370-
{"node": {"name": test.name, "lastDuration": 0.0}},
371-
{"node": {"name": test_2.name, "lastDuration": 0.0}},
398+
{"node": {"name": test.name, "lastDuration": 2.0}},
399+
{"node": {"name": test_2.name, "lastDuration": 3.0}},
372400
]
373401
}
374402

@@ -402,8 +430,8 @@ def test_desc_last_duration_ordering_on_test_results(self) -> None:
402430
)
403431
assert res["testResults"] == {
404432
"edges": [
405-
{"node": {"name": test_2.name, "lastDuration": 0.0}},
406-
{"node": {"name": test.name, "lastDuration": 0.0}},
433+
{"node": {"name": test_2.name, "lastDuration": 3.0}},
434+
{"node": {"name": test.name, "lastDuration": 2.0}},
407435
]
408436
}
409437

@@ -548,6 +576,142 @@ def test_desc_failure_rate_ordering_on_test_results(self) -> None:
548576
]
549577
}
550578

579+
def test_desc_failure_rate_ordering_on_test_results_with_after(self) -> None:
580+
repo = RepositoryFactory(author=self.owner, active=True, private=True)
581+
test = TestFactory(repository=repo)
582+
_ = DailyTestRollupFactory(
583+
test=test,
584+
date=datetime.date.today() - datetime.timedelta(days=1),
585+
repoid=repo.repoid,
586+
pass_count=1,
587+
fail_count=1,
588+
)
589+
_ = DailyTestRollupFactory(
590+
test=test,
591+
date=datetime.date.today(),
592+
repoid=repo.repoid,
593+
pass_count=3,
594+
fail_count=0,
595+
)
596+
test_2 = TestFactory(repository=repo)
597+
_ = DailyTestRollupFactory(
598+
test=test_2,
599+
date=datetime.date.today(),
600+
repoid=repo.repoid,
601+
pass_count=2,
602+
fail_count=3,
603+
)
604+
res = self.fetch_test_analytics(
605+
repo.name,
606+
"""testResults(ordering: { parameter: FAILURE_RATE, direction: DESC }, first: 1) { edges { node { name failureRate } }, pageInfo { hasNextPage, hasPreviousPage, startCursor, endCursor }, totalCount }""",
607+
)
608+
609+
assert res["testResults"] == {
610+
"edges": [
611+
{"node": {"name": test_2.name, "failureRate": 0.6}},
612+
],
613+
"pageInfo": {
614+
"endCursor": base64_encode_string(f"0.6|{test_2.name}"),
615+
"hasNextPage": True,
616+
"hasPreviousPage": False,
617+
"startCursor": base64_encode_string(f"0.6|{test_2.name}"),
618+
},
619+
"totalCount": 2,
620+
}
621+
622+
res = self.fetch_test_analytics(
623+
repo.name,
624+
"""testResults(ordering: { parameter: FAILURE_RATE, direction: DESC }, first: 1, after: "%s") { edges { node { name failureRate } }, pageInfo { hasNextPage, hasPreviousPage, startCursor, endCursor }, totalCount }"""
625+
% res["testResults"]["pageInfo"]["endCursor"],
626+
)
627+
628+
assert res["testResults"] == {
629+
"edges": [
630+
{"node": {"name": test.name, "failureRate": 0.2}},
631+
],
632+
"pageInfo": {
633+
"endCursor": base64_encode_string(f"0.2|{test.name}"),
634+
"hasNextPage": False,
635+
"hasPreviousPage": False,
636+
"startCursor": base64_encode_string(f"0.2|{test.name}"),
637+
},
638+
"totalCount": 2,
639+
}
640+
641+
res = self.fetch_test_analytics(
642+
repo.name,
643+
"""testResults(ordering: { parameter: FAILURE_RATE, direction: ASC }, first: 1) { edges { node { name failureRate } }, pageInfo { hasNextPage, hasPreviousPage, startCursor, endCursor }, totalCount }""",
644+
)
645+
646+
assert res["testResults"] == {
647+
"edges": [
648+
{"node": {"name": test.name, "failureRate": 0.2}},
649+
],
650+
"pageInfo": {
651+
"endCursor": base64_encode_string(f"0.2|{test.name}"),
652+
"hasNextPage": True,
653+
"hasPreviousPage": False,
654+
"startCursor": base64_encode_string(f"0.2|{test.name}"),
655+
},
656+
"totalCount": 2,
657+
}
658+
659+
res = self.fetch_test_analytics(
660+
repo.name,
661+
"""testResults(ordering: { parameter: FAILURE_RATE, direction: ASC }, first: 1, after: "%s") { edges { node { name failureRate } }, pageInfo { hasNextPage, hasPreviousPage, startCursor, endCursor }, totalCount }"""
662+
% res["testResults"]["pageInfo"]["endCursor"],
663+
)
664+
665+
assert res["testResults"] == {
666+
"edges": [
667+
{"node": {"name": test_2.name, "failureRate": 0.6}},
668+
],
669+
"pageInfo": {
670+
"endCursor": base64_encode_string(f"0.6|{test_2.name}"),
671+
"hasNextPage": False,
672+
"hasPreviousPage": False,
673+
"startCursor": base64_encode_string(f"0.6|{test_2.name}"),
674+
},
675+
"totalCount": 2,
676+
}
677+
678+
res = self.fetch_test_analytics(
679+
repo.name,
680+
"""testResults(ordering: { parameter: FAILURE_RATE, direction: ASC }, last: 2) { edges { node { name failureRate } }, pageInfo { hasNextPage, hasPreviousPage, startCursor, endCursor }, totalCount }""",
681+
)
682+
683+
assert res["testResults"] == {
684+
"edges": [
685+
{"node": {"name": test_2.name, "failureRate": 0.6}},
686+
{"node": {"name": test.name, "failureRate": 0.2}},
687+
],
688+
"pageInfo": {
689+
"endCursor": base64_encode_string(f"0.2|{test.name}"),
690+
"hasNextPage": False,
691+
"hasPreviousPage": False,
692+
"startCursor": base64_encode_string(f"0.6|{test_2.name}"),
693+
},
694+
"totalCount": 2,
695+
}
696+
697+
res = self.fetch_test_analytics(
698+
repo.name,
699+
"""testResults(ordering: { parameter: FAILURE_RATE, direction: ASC }, last: 1) { edges { node { name failureRate } }, pageInfo { hasNextPage, hasPreviousPage, startCursor, endCursor }, totalCount }""",
700+
)
701+
702+
assert res["testResults"] == {
703+
"edges": [
704+
{"node": {"name": test_2.name, "failureRate": 0.6}},
705+
],
706+
"pageInfo": {
707+
"endCursor": base64_encode_string(f"0.6|{test_2.name}"),
708+
"hasNextPage": False,
709+
"hasPreviousPage": True,
710+
"startCursor": base64_encode_string(f"0.6|{test_2.name}"),
711+
},
712+
"totalCount": 2,
713+
}
714+
551715
def test_flake_rate_filtering_on_test_results(self) -> None:
552716
repo = RepositoryFactory(author=self.owner, active=True, private=True)
553717
test = TestFactory(repository=repo)
@@ -753,6 +917,43 @@ def test_test_results_aggregates_no_history(self) -> None:
753917
"totalSlowTestsPercentChange": None,
754918
}
755919

920+
def test_test_results_aggregates_no_history_7_days(self) -> None:
921+
repo = RepositoryFactory(
922+
author=self.owner, active=True, private=True, branch="main"
923+
)
924+
925+
for i in range(0, 7):
926+
test = TestFactory(repository=repo)
927+
_ = DailyTestRollupFactory(
928+
test=test,
929+
repoid=repo.repoid,
930+
branch="main",
931+
fail_count=1 if i % 3 == 0 else 0,
932+
skip_count=1 if i % 6 == 0 else 0,
933+
pass_count=1,
934+
avg_duration_seconds=float(i),
935+
last_duration_seconds=float(i),
936+
date=datetime.date.today() - datetime.timedelta(days=i),
937+
)
938+
939+
res = self.fetch_test_analytics(
940+
repo.name,
941+
"""testResultsAggregates(interval: INTERVAL_7_DAY) { totalDuration, slowestTestsDuration, totalFails, totalSkips, totalSlowTests, totalDurationPercentChange, slowestTestsDurationPercentChange, totalFailsPercentChange, totalSkipsPercentChange, totalSlowTestsPercentChange }""",
942+
)
943+
944+
assert res["testResultsAggregates"] == {
945+
"totalDuration": 30.0,
946+
"totalDurationPercentChange": None,
947+
"slowestTestsDuration": 12.0,
948+
"slowestTestsDurationPercentChange": None,
949+
"totalFails": 3,
950+
"totalFailsPercentChange": None,
951+
"totalSkips": 2,
952+
"totalSkipsPercentChange": None,
953+
"totalSlowTests": 1,
954+
"totalSlowTestsPercentChange": None,
955+
}
956+
756957
def test_flake_aggregates(self) -> None:
757958
repo = RepositoryFactory(
758959
author=self.owner, active=True, private=True, branch="main"
@@ -924,7 +1125,7 @@ def test_flake_aggregates_7_days(self) -> None:
9241125

9251126
res = self.fetch_test_analytics(
9261127
repo.name,
927-
"""flakeAggregates(history: INTERVAL_7_DAY) { flakeCount, flakeRate, flakeCountPercentChange, flakeRatePercentChange }""",
1128+
"""flakeAggregates(interval: INTERVAL_7_DAY) { flakeCount, flakeRate, flakeCountPercentChange, flakeRatePercentChange }""",
9281129
)
9291130

9301131
assert res["flakeAggregates"] == {

graphql_api/tests/test_test_result.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ def test_fetch_test_result_last_duration(self) -> None:
234234
result["owner"]["repository"]["testAnalytics"]["testResults"]["edges"][0][
235235
"node"
236236
]["lastDuration"]
237-
== 0.0
237+
== 1.0
238238
)
239239

240240
def test_fetch_test_result_avg_duration(self) -> None:

graphql_api/types/inputs/test_results_filters.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ input TestResultsFilters {
33
parameter: TestResultsFilterParameter
44
test_suites: [String!]
55
flags: [String!]
6-
history: MeasurementInterval
6+
interval: MeasurementInterval
77
term: String
88
}
99

graphql_api/types/test_analytics/test_analytics.graphql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ type TestAnalytics {
1313
): TestResultConnection! @cost(complexity: 10, multipliers: ["first", "last"])
1414

1515
"Test results aggregates are analytics data totals across all tests"
16-
testResultsAggregates(history: MeasurementInterval): TestResultsAggregates
16+
testResultsAggregates(interval: MeasurementInterval): TestResultsAggregates
1717

1818
"Flake aggregates are flake totals across all tests"
19-
flakeAggregates(history: MeasurementInterval): FlakeAggregates
19+
flakeAggregates(interval: MeasurementInterval): FlakeAggregates
2020
}
2121

2222
type TestResultConnection {

0 commit comments

Comments
 (0)