Skip to content

Commit 3f84401

Browse files
authored
Merge branch 'main' into mar_09_ba_upload
2 parents 3a4a2af + 87f463e commit 3f84401

26 files changed

+830
-118
lines changed

.github/workflows/mypy.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ on:
1111
jobs:
1212
patch-typing-check:
1313
name: Run Patch Type Check
14-
uses: codecov/gha-workflows/.github/workflows/mypy.yml@00043f8fbe820934312f7fade4ea72ea92231b5e
14+
uses: codecov/gha-workflows/.github/workflows/mypy.yml@v1.2.33

conftest.py

+2
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ def mock_configuration(mocker):
204204
"hash_key": "88f572f4726e4971827415efa8867978",
205205
"secret_access_key": "codecov-default-secret",
206206
"verify_ssl": False,
207+
"host": "minio",
208+
"port": 9000,
207209
},
208210
"smtp": {
209211
"host": "mailhog",

django_scaffold/settings.py

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"shared.django_apps.labelanalysis",
3636
"shared.django_apps.reports",
3737
"shared.django_apps.staticanalysis",
38+
"shared.django_apps.ta_timeseries",
3839
]
3940

4041
TELEMETRY_VANILLA_DB = "default"

docker-compose.yml

+16
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ services:
77
- postgres
88
- redis
99
- timescale
10+
- minio
1011
volumes:
1112
- ./:/app/apps/worker
1213
- ./docker/test_codecov_config.yml:/config/codecov.yml
@@ -38,7 +39,22 @@ services:
3839
- POSTGRES_USER=postgres
3940
- POSTGRES_HOST_AUTH_METHOD=trust
4041
- POSTGRES_PASSWORD=password
42+
volumes:
43+
- ./docker/init_db.sql:/docker-entrypoint-initdb.d/init_db.sql
4144

45+
minio:
46+
image: minio/minio:latest
47+
command: server /export
48+
ports:
49+
- "${MINIO_PORT:-9000}:9000"
50+
environment:
51+
- MINIO_ACCESS_KEY=codecov-default-key
52+
- MINIO_SECRET_KEY=codecov-default-secret
53+
volumes:
54+
- type: tmpfs
55+
target: /export
56+
tmpfs:
57+
size: 256M
4258
redis:
4359
image: redis:6-alpine
4460

docker/init_db.sql

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CREATE DATABASE test_analytics;

docker/test_codecov_config.yml

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ setup:
55
encryption_secret: "zp^P9*i8aR3"
66
timeseries:
77
enabled: true
8+
ta_timeseries:
9+
enabled: true
810

911
services:
1012
database_url: postgres://postgres:password@postgres:5432/postgres
@@ -16,6 +18,8 @@ services:
1618
access_key_id: codecov-default-key
1719
secret_access_key: codecov-default-secret
1820
verify_ssl: false
21+
port: 9000
22+
host: minio
1923
smtp:
2024
host: mailhog
2125
port: 1025

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,4 @@ dev-dependencies = [
8383
[tool.uv.sources]
8484
timestring = { git = "https://github.com/codecov/timestring", rev = "d37ceacc5954dff3b5bd2f887936a98a668dda42" }
8585
test-results-parser = { git = "https://github.com/codecov/test-results-parser", rev = "190bbc8a911099749928e13d5fe57f6027ca1e74" }
86-
shared = { git = "https://github.com/codecov/shared", rev = "290557e977c4ff60d5d9dcb9ee54afea1c1df83f" }
86+
shared = { git = "https://github.com/codecov/shared", rev = "a93b0f1299841e56d8ceb591813974e97bec75b9" }

services/comparison/__init__.py

+1-26
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import sentry_sdk
66
from asgiref.sync import async_to_sync
7-
from shared.reports.changes import get_changes_using_rust, run_comparison_using_rust
7+
from shared.reports.changes import run_comparison_using_rust
88
from shared.reports.types import Change, ReportTotals
99
from shared.torngit.base import TorngitBaseAdapter
1010
from shared.torngit.exceptions import TorngitClientGeneralError
@@ -169,31 +169,6 @@ def get_changes(self) -> list[Change] | None:
169169
self.comparison.head.report,
170170
diff,
171171
)
172-
if (
173-
self._changes
174-
and self.comparison.project_coverage_base.report is not None
175-
and self.comparison.head.report is not None
176-
and self.comparison.project_coverage_base.report.rust_report is not None
177-
and self.comparison.head.report.rust_report is not None
178-
):
179-
rust_changes = get_changes_using_rust(
180-
self.comparison.project_coverage_base.report,
181-
self.comparison.head.report,
182-
diff,
183-
)
184-
original_paths = set([c.path for c in self._changes])
185-
new_paths = set([c.path for c in rust_changes])
186-
if original_paths != new_paths:
187-
only_on_new = sorted(new_paths - original_paths)
188-
only_on_original = sorted(original_paths - new_paths)
189-
log.info(
190-
"There are differences between python changes and rust changes",
191-
extra=dict(
192-
only_on_new=only_on_new[:100],
193-
only_on_original=only_on_original[:100],
194-
repoid=self.head.commit.repoid,
195-
),
196-
)
197172

198173
return self._changes
199174

services/notification/notifiers/mixins/message/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,8 @@ def write_section_to_msg(
207207
def write_cross_pollination_message(self, write: Callable):
208208
extra_message = []
209209

210-
ta_message = "- [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems."
211-
ba_message = "- 📦 [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges."
210+
ta_message = "- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems."
211+
ba_message = "- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges."
212212

213213
if not self.repository.test_analytics_enabled:
214214
extra_message.append(ta_message)
@@ -220,7 +220,7 @@ def write_cross_pollination_message(self, write: Callable):
220220

221221
if extra_message:
222222
for i in [
223-
"<details><summary>🚀 New features to boost your workflow: </summary>",
223+
"<details><summary> :rocket: New features to boost your workflow: </summary>",
224224
"",
225225
*extra_message,
226226
"</details>",

services/notification/notifiers/tests/unit/test_comment.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -3088,9 +3088,9 @@ def test_build_cross_pollination_message(
30883088
dbsession.flush()
30893089
result = notifier.build_message(comparison)
30903090

3091-
header = "<details><summary>🚀 New features to boost your workflow: </summary>"
3092-
ta_message = "- [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems."
3093-
ba_message = "- 📦 [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges."
3091+
header = "<details><summary> :rocket: New features to boost your workflow: </summary>"
3092+
ta_message = "- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems."
3093+
ba_message = "- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges."
30943094

30953095
end_of_message = []
30963096

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from shared.reports.types import Change
2-
31
from services.comparison import ComparisonProxy, FilteredComparison
42

53

@@ -14,18 +12,3 @@ def test_get_existing_statuses(self, mocker):
1412
assert isinstance(filtered_comparison, FilteredComparison)
1513
res = filtered_comparison.get_existing_statuses()
1614
assert res == mocked_get_existing_statuses.return_value
17-
18-
def test_get_changes_rust_vs_python(self, mocker):
19-
mocker.patch.object(ComparisonProxy, "get_diff")
20-
mocker.patch(
21-
"services.comparison.get_changes",
22-
return_value=[Change(path="apple"), Change(path="pear")],
23-
)
24-
mocker.patch(
25-
"services.comparison.get_changes_using_rust",
26-
return_value=[Change(path="banana"), Change(path="pear")],
27-
)
28-
comparison = ComparisonProxy(mocker.MagicMock())
29-
res = comparison.get_changes()
30-
expected_result = [Change(path="apple"), Change(path="pear")]
31-
assert expected_result == res
+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
from typing import Any
5+
6+
import sentry_sdk
7+
import test_results_parser
8+
from shared.config import get_config
9+
from shared.django_apps.core.models import Commit, Repository
10+
from shared.django_apps.reports.models import ReportSession, UploadError
11+
12+
from services.archive import ArchiveService
13+
from services.test_analytics.ta_timeseries import get_flaky_tests_set, insert_testrun
14+
from services.yaml import UserYaml, read_yaml_field
15+
16+
17+
@dataclass
18+
class TAProcInfo:
19+
repository: Repository
20+
branch: str | None
21+
user_yaml: UserYaml
22+
23+
24+
def handle_file_not_found(upload: ReportSession):
25+
upload.state = "processed"
26+
upload.save()
27+
UploadError.objects.create(
28+
report_session=upload,
29+
error_code="file_not_in_storage",
30+
error_params={},
31+
)
32+
33+
34+
def handle_parsing_error(upload: ReportSession, exc: Exception):
35+
sentry_sdk.capture_exception(exc, tags={"upload_state": upload.state})
36+
upload.state = "processed"
37+
upload.save()
38+
UploadError.objects.create(
39+
report_session=upload,
40+
error_code="unsupported_file_format",
41+
error_params={"error_message": str(exc)},
42+
)
43+
44+
45+
def get_ta_processing_info(
46+
repoid: int,
47+
commitid: str,
48+
commit_yaml: dict[str, Any],
49+
) -> TAProcInfo:
50+
repository = Repository.objects.get(repoid=repoid)
51+
52+
commit = Commit.objects.get(repository=repository, commitid=commitid)
53+
branch = commit.branch
54+
if branch is None:
55+
raise ValueError("Branch is None")
56+
57+
user_yaml: UserYaml = UserYaml(commit_yaml)
58+
return TAProcInfo(
59+
repository,
60+
branch,
61+
user_yaml,
62+
)
63+
64+
65+
def should_delete_archive_settings(user_yaml: UserYaml) -> bool:
66+
if get_config("services", "minio", "expire_raw_after_n_days"):
67+
return True
68+
return not read_yaml_field(user_yaml, ("codecov", "archive", "uploads"), _else=True)
69+
70+
71+
def rewrite_or_delete_upload(
72+
archive_service: ArchiveService,
73+
user_yaml: UserYaml,
74+
upload: ReportSession,
75+
readable_file: bytes,
76+
):
77+
if should_delete_archive_settings(user_yaml):
78+
archive_url = upload.storage_path
79+
if archive_url and not archive_url.startswith("http"):
80+
archive_service.delete_file(archive_url)
81+
else:
82+
archive_service.write_file(upload.storage_path, bytes(readable_file))
83+
84+
85+
def insert_testruns_timeseries(
86+
repoid: int,
87+
commitid: str,
88+
branch: str | None,
89+
upload: ReportSession,
90+
parsing_infos: list[test_results_parser.ParsingInfo],
91+
):
92+
flaky_test_set = get_flaky_tests_set(repoid)
93+
94+
for parsing_info in parsing_infos:
95+
insert_testrun(
96+
timestamp=upload.created_at,
97+
repo_id=repoid,
98+
commit_sha=commitid,
99+
branch=branch,
100+
upload_id=upload.id,
101+
flags=upload.flag_names,
102+
parsing_info=parsing_info,
103+
flaky_test_ids=flaky_test_set,
104+
)
+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import logging
2+
from typing import Any
3+
4+
from shared.django_apps.reports.models import ReportSession
5+
from shared.storage.exceptions import FileNotInStorageError
6+
from test_results_parser import parse_raw_upload
7+
8+
from services.archive import ArchiveService
9+
from services.processing.types import UploadArguments
10+
from services.test_analytics.ta_processing import (
11+
get_ta_processing_info,
12+
handle_file_not_found,
13+
handle_parsing_error,
14+
insert_testruns_timeseries,
15+
rewrite_or_delete_upload,
16+
)
17+
18+
log = logging.getLogger(__name__)
19+
20+
21+
def ta_processor_impl(
22+
repoid: int,
23+
commitid: str,
24+
commit_yaml: dict[str, Any],
25+
argument: UploadArguments,
26+
update_state: bool = False,
27+
) -> bool:
28+
log.info(
29+
"Processing single TA argument",
30+
extra=dict(
31+
upload_id=argument.get("upload_id"),
32+
repoid=repoid,
33+
commitid=commitid,
34+
),
35+
)
36+
37+
upload_id = argument.get("upload_id")
38+
if upload_id is None:
39+
return False
40+
41+
upload = ReportSession.objects.get(id=upload_id)
42+
if upload.state == "processed":
43+
# don't need to process again because the intermediate result should already be in redis
44+
return False
45+
46+
if upload.storage_path is None:
47+
if update_state:
48+
handle_file_not_found(upload)
49+
return False
50+
51+
ta_proc_info = get_ta_processing_info(repoid, commitid, commit_yaml)
52+
53+
archive_service = ArchiveService(ta_proc_info.repository)
54+
55+
try:
56+
payload_bytes = archive_service.read_file(upload.storage_path)
57+
except FileNotInStorageError:
58+
if update_state:
59+
handle_file_not_found(upload)
60+
return False
61+
62+
try:
63+
parsing_infos, readable_file = parse_raw_upload(payload_bytes)
64+
except RuntimeError as exc:
65+
if update_state:
66+
handle_parsing_error(upload, exc)
67+
return False
68+
69+
insert_testruns_timeseries(
70+
repoid, commitid, ta_proc_info.branch, upload, parsing_infos
71+
)
72+
73+
if update_state:
74+
upload.state = "processed"
75+
upload.save()
76+
77+
rewrite_or_delete_upload(
78+
archive_service, ta_proc_info.user_yaml, upload, readable_file
79+
)
80+
return True

0 commit comments

Comments
 (0)