Skip to content

Commit 5ae548a

Browse files
check uploads for notify (#558)
* check uploads for notify * update tests * add test * add changes back * one more test * use db for truth * tests * cleanup test
1 parent 6d14cbf commit 5ae548a

File tree

4 files changed

+145
-10
lines changed

4 files changed

+145
-10
lines changed

apps/worker/tasks/manual_trigger.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,12 @@ def process_impl_within_lock(
9292
| (CommitReport.report_type == ReportType.COVERAGE.value),
9393
)
9494
)
95+
9596
still_processing = 0
9697
for upload in uploads:
9798
if not upload.state or upload.state_id == UploadState.UPLOADED.db_id:
9899
still_processing += 1
100+
99101
if still_processing == 0:
100102
self.trigger_notifications(repoid, commitid, commit_yaml)
101103
if commit.pullid:
@@ -108,18 +110,23 @@ def process_impl_within_lock(
108110
# reschedule the task
109111
try:
110112
log.info(
111-
"Retrying ManualTriggerTask. Some uploads are still being processed."
113+
"Retrying ManualTriggerTask. Some uploads are still being processed.",
114+
extra={
115+
"repoid": repoid,
116+
"commitid": commitid,
117+
"uploads_still_processing": still_processing,
118+
},
112119
)
113120
retry_in = 60 * 3**self.request.retries
114121
self.retry(max_retries=5, countdown=retry_in)
115122
except MaxRetriesExceededError:
116123
log.warning(
117124
"Not attempting to wait for all uploads to get processed since we already retried too many times",
118125
extra={
119-
"repoid": commit.repoid,
120126
"commit": commit.commitid,
121127
"max_retries": 5,
122128
"next_countdown_would_be": retry_in,
129+
"repoid": commit.repoid,
123130
},
124131
)
125132
return {

apps/worker/tasks/tests/unit/test_manual_trigger.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ def test_manual_upload_completion_trigger(
3434

3535
dbsession.add(commit)
3636

37-
upload = UploadFactory.create(report__commit=commit)
37+
# Upload is complete in database (state = PROCESSED)
38+
upload = UploadFactory.create(
39+
report__commit=commit,
40+
state="processed",
41+
state_id=UploadState.PROCESSED.db_id,
42+
)
3843
compared_to = CommitFactory.create(repository=commit.repository)
3944
pull.compared_to = compared_to.commitid
4045

@@ -105,3 +110,45 @@ def test_manual_upload_completion_trigger_uploads_still_processing(
105110
"notifications_called": False,
106111
"message": "Uploads are still in process and the task got retired so many times. Not triggering notifications.",
107112
} == result
113+
114+
def test_manual_upload_completion_trigger_with_uploaded_state(
115+
self,
116+
mocker,
117+
mock_configuration,
118+
dbsession,
119+
mock_storage,
120+
mock_redis,
121+
celery_app,
122+
):
123+
"""Test that task retries when DB shows uploads in UPLOADED state"""
124+
mocker.patch.object(
125+
ManualTriggerTask,
126+
"app",
127+
celery_app,
128+
)
129+
commit = CommitFactory.create()
130+
# One upload is still in UPLOADED state (being processed)
131+
upload1 = UploadFactory.create(
132+
report__commit=commit,
133+
state="started",
134+
state_id=UploadState.UPLOADED.db_id,
135+
)
136+
# Another upload is complete
137+
upload2 = UploadFactory.create(
138+
report__commit=commit,
139+
state="processed",
140+
state_id=UploadState.PROCESSED.db_id,
141+
)
142+
dbsession.add(commit)
143+
dbsession.add(upload1)
144+
dbsession.add(upload2)
145+
dbsession.flush()
146+
147+
# Should retry because DB shows upload still in UPLOADED state
148+
with pytest.raises(Retry):
149+
ManualTriggerTask().run_impl(
150+
dbsession,
151+
repoid=commit.repoid,
152+
commitid=commit.commitid,
153+
current_yaml={},
154+
)

apps/worker/tasks/tests/unit/test_upload_finisher_task.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from datetime import UTC, datetime
12
from pathlib import Path
23
from unittest.mock import ANY, call
34

@@ -180,6 +181,7 @@ def test_upload_finisher_task_call(
180181
branch="thisbranch",
181182
ci_passed=True,
182183
repository__branch="thisbranch",
184+
repository__updatestamp=None,
183185
repository__author__unencrypted_oauth_token="testulk3d54rlhxkjyzomq2wh8b7np47xabcrkx8",
184186
repository__author__username="ThiagoCodecov",
185187
repository__author__service="github",
@@ -208,6 +210,11 @@ def test_upload_finisher_task_call(
208210
dbsession.refresh(commit)
209211
assert commit.message == "dsidsahdsahdsa"
210212

213+
# Verify repository timestamp is updated
214+
dbsession.refresh(commit.repository)
215+
assert commit.repository.updatestamp is not None
216+
assert (datetime.now(tz=UTC) - commit.repository.updatestamp).seconds < 60
217+
211218
mock_checkpoint_submit.assert_any_call(
212219
"batch_processing_duration",
213220
UploadFlow.INITIAL_PROCESSING_COMPLETE,
@@ -394,7 +401,7 @@ def test_upload_finisher_task_call_different_branch(
394401
]
395402
)
396403

397-
def test_should_call_notifications(self, dbsession):
404+
def test_should_call_notifications(self, dbsession, mocker):
398405
commit_yaml = {"codecov": {"max_report_age": "1y ago"}}
399406
commit = CommitFactory.create(
400407
message="dsidsahdsahdsa",
@@ -432,7 +439,7 @@ def test_should_call_notifications_manual_trigger(self, dbsession):
432439
== ShouldCallNotifyResult.DO_NOT_NOTIFY
433440
)
434441

435-
def test_should_call_notifications_manual_trigger_off(self, dbsession):
442+
def test_should_call_notifications_manual_trigger_off(self, dbsession, mocker):
436443
commit_yaml = {
437444
"codecov": {"max_report_age": "1y ago", "notify": {"manual_trigger": False}}
438445
}
@@ -463,7 +470,7 @@ def test_should_call_notifications_manual_trigger_off(self, dbsession):
463470
],
464471
)
465472
def test_should_call_notifications_no_successful_reports(
466-
self, dbsession, notify_error, result
473+
self, dbsession, mocker, notify_error, result
467474
):
468475
commit_yaml = {
469476
"codecov": {
@@ -544,6 +551,44 @@ def test_should_call_notifications_more_than_enough_builds(self, dbsession, mock
544551
== ShouldCallNotifyResult.NOTIFY
545552
)
546553

554+
def test_should_call_notifications_with_pending_uploads_in_db(
555+
self, dbsession, mocker
556+
):
557+
"""Test that notifications are not called when DB shows pending uploads"""
558+
commit_yaml = {"codecov": {"max_report_age": "1y ago"}}
559+
commit = CommitFactory.create(
560+
message="dsidsahdsahdsa",
561+
commitid="abf6d4df662c47e32460020ab14abf9303581429",
562+
repository__author__unencrypted_oauth_token="testulk3d54rlhxkjyzomq2wh8b7np47xabcrkx8",
563+
repository__author__username="ThiagoCodecov",
564+
repository__yaml=commit_yaml,
565+
)
566+
# Create uploads in UPLOADED state (still being processed)
567+
upload1 = UploadFactory.create(
568+
report__commit=commit,
569+
state="started",
570+
state_id=UploadState.UPLOADED.db_id,
571+
)
572+
upload2 = UploadFactory.create(
573+
report__commit=commit,
574+
state="started",
575+
state_id=UploadState.UPLOADED.db_id,
576+
)
577+
dbsession.add(commit)
578+
dbsession.add(upload1)
579+
dbsession.add(upload2)
580+
dbsession.flush()
581+
582+
assert (
583+
UploadFinisherTask().should_call_notifications(
584+
commit,
585+
commit_yaml,
586+
[{"arguments": {"url": "url"}, "successful": True}],
587+
db_session=dbsession,
588+
)
589+
== ShouldCallNotifyResult.DO_NOT_NOTIFY
590+
)
591+
547592
def test_finish_reports_processing(self, dbsession, mocker, mock_self_app):
548593
commit_yaml = {}
549594
commit = CommitFactory.create(

apps/worker/tasks/upload_finisher.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
load_intermediate_reports,
2828
)
2929
from services.processing.merging import merge_reports, update_uploads
30-
from services.processing.state import ProcessingState, should_trigger_postprocessing
30+
from services.processing.state import ProcessingState
3131
from services.processing.types import ProcessingResult
3232
from services.report import ReportService
3333
from services.repository import get_repo_provider_service
@@ -319,8 +319,24 @@ def run_impl(
319319
state,
320320
)
321321

322-
if not should_trigger_postprocessing(state.get_upload_numbers()):
323-
log.info("run_impl: Postprocessing should not be triggered")
322+
# Check if there are still unprocessed uploads in the database
323+
# Use DB as source of truth - if any uploads are still in UPLOADED state,
324+
# another finisher will process them and we shouldn't send notifications yet
325+
remaining_uploads = (
326+
db_session.query(Upload)
327+
.join(Upload.report)
328+
.filter(
329+
Upload.report.has(commit=commit),
330+
Upload.state_id == UploadState.UPLOADED.db_id,
331+
)
332+
.count()
333+
)
334+
335+
if remaining_uploads > 0:
336+
log.info(
337+
"run_impl: Postprocessing should not be triggered - uploads still pending",
338+
extra={"remaining_uploads": remaining_uploads},
339+
)
324340
UploadFlow.log(UploadFlow.PROCESSING_COMPLETE)
325341
UploadFlow.log(UploadFlow.SKIPPING_NOTIFICATION)
326342
self._call_upload_breadcrumb_task(
@@ -556,7 +572,7 @@ def finish_reports_processing(
556572
notifications_called = False
557573
if not regexp_ci_skip.search(commit.message or ""):
558574
should_call_notifications = self.should_call_notifications(
559-
commit, commit_yaml, processing_results
575+
commit, commit_yaml, processing_results, db_session
560576
)
561577
log.info(
562578
"finish_reports_processing: should_call_notifications",
@@ -667,6 +683,7 @@ def should_call_notifications(
667683
commit: Commit,
668684
commit_yaml: UserYaml,
669685
processing_results: list[ProcessingResult],
686+
db_session=None,
670687
) -> ShouldCallNotifyResult:
671688
extra_dict = {
672689
"repoid": commit.repoid,
@@ -676,6 +693,25 @@ def should_call_notifications(
676693
"parent_task": self.request.parent_id,
677694
}
678695

696+
# Check if there are still pending uploads in the database
697+
# Use DB as source of truth for upload completion status
698+
if db_session:
699+
remaining_uploads = (
700+
db_session.query(Upload)
701+
.join(Upload.report)
702+
.filter(
703+
Upload.report.has(commit=commit),
704+
Upload.state_id == UploadState.UPLOADED.db_id,
705+
)
706+
.count()
707+
)
708+
if remaining_uploads > 0:
709+
log.info(
710+
"Not scheduling notify because there are still pending uploads",
711+
extra={**extra_dict, "remaining_uploads": remaining_uploads},
712+
)
713+
return ShouldCallNotifyResult.DO_NOT_NOTIFY
714+
679715
manual_trigger = read_yaml_field(
680716
commit_yaml, ("codecov", "notify", "manual_trigger")
681717
)

0 commit comments

Comments
 (0)