-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add test results finisher #234
Add test results finisher #234
Conversation
Codecov ReportAttention: @@ Coverage Diff @@
## main #234 +/- ##
==========================================
- Coverage 98.10% 98.10% -0.01%
==========================================
Files 375 377 +2
Lines 31013 31428 +415
==========================================
+ Hits 30425 30832 +407
- Misses 588 596 +8
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #234 +/- ##
==========================================
- Coverage 98.10% 98.10% -0.01%
==========================================
Files 375 377 +2
Lines 31013 31428 +415
==========================================
+ Hits 30425 30832 +407
- Misses 588 596 +8
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Codecov Report
@@ Coverage Diff @@
## main #234 +/- ##
==========================================
- Coverage 98.10% 98.10% -0.01%
==========================================
Files 375 377 +2
Lines 31013 31428 +415
==========================================
+ Hits 30425 30832 +407
- Misses 588 596 +8
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Codecov ReportAttention:
Changes have been made to critical files, which contain lines commonly executed in production. Learn more Additional details and impacted files@@ Coverage Diff @@
## main #234 +/- ##
==========================================
- Coverage 98.07% 98.07% -0.01%
==========================================
Files 406 408 +2
Lines 31714 32129 +415
==========================================
+ Hits 31105 31512 +407
- Misses 609 617 +8
Flags with carried forward coverage won't be shown. Click here to find out more.
|
6facf1f
to
3563af6
Compare
joseph/failed-test-ingestion...joseph/add-test-results-finisher-to-upload this PR includes the test result processor PR, here is a URL to compare the two branches separately |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
started a group DM about a design tweak for performance reasons. need to sync on that before accept/request changes decision
services/test_results.py
Outdated
len_of_table_value = len(table_value) | ||
for i in range((len_of_table_value) // 70): | ||
table_value = ( | ||
table_value[: (i + 1) * 70] + "<br>" + table_value[(i + 1) * 70 :] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mentioned that the "<br>"
takes up 4 characters which messes with the line lengths. you can avoid that with something like:
line_size = 70
lines = [table_value[i:i+line_size] for i in range(0, len(table_value), line_size)]
return "<br>".join(lines)
services/test_results.py
Outdated
return current_report_row | ||
|
||
# support flags in test results | ||
def create_report_upload( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we put the flags stuff in the base class and not have to override this here?
with lock_manager.locked( | ||
LockType.NOTIFICATION, | ||
retry_num=self.request.retries, | ||
): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this lock need to surround the whole finisher task or just the part where we work on the PR comment?
tasks/test_results_finisher.py
Outdated
) | ||
assert commit, "commit not found" | ||
|
||
notify = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nothing ever sets this to false, it's only read in one place where we make the return dict. you could just inline True
there
database/models/reports.py
Outdated
upload_id = Column(types.Integer, ForeignKey("reports_upload.id")) | ||
upload = relationship("Upload", backref=backref("testinstances")) | ||
failure_message = Column(types.Text) | ||
active = Column(types.Boolean) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we order by created time or join the uploads table and order by upload time to get the newest? that way we just need a single atomic INSERT to insert a new test instance, we don't need to also run an UPDATE to set the previous one to false that we have to make sure runs in the same transaction to avoid races
database/models/reports.py
Outdated
repository = relationship("Repository", backref=backref("tests")) | ||
name = Column(types.String(256), nullable=False) | ||
testsuite = Column(types.String(256), nullable=False) | ||
env = Column(types.String(256), nullable=False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i just left a comment on #197 (review) about changing the id field of this table to be a hash instead. in my comment over there i said it should be a hash of the test suite + test name, but i suppose it should also include the inputs to env
as well since env
is a similar concept
tasks/notify.py
Outdated
|
||
# check if there were any test failures | ||
|
||
num_of_failed_test_instances = ( | ||
db_session.query(TestInstance) | ||
.join(Upload) | ||
.join(CommitReport) | ||
.filter( | ||
CommitReport.commit_id == commit.id_, | ||
TestInstance.outcome == int(Outcome.Failure), | ||
TestInstance.active is True, | ||
) | ||
.count() | ||
) | ||
|
||
if num_of_failed_test_instances > 0: | ||
return { | ||
"notify_attempted": False, | ||
"notifications": None, | ||
"reason": "test_failures", | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be in should_send_notifications()
?
Line 409 in 3276511
def should_send_notifications(self, current_yaml, commit, ci_passed, report): |
b026451
to
31dba51
Compare
8742bbe
to
a390c59
Compare
731ea75
to
7d9aa9a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for all the iteration on these changes, i think we will long-term be happy we took the time. just nits left, some old comments + a couple new ones
tasks/notify.py
Outdated
) | ||
|
||
if any( | ||
[test.outcome == int(Outcome.Failure) for test in latest_test_instances] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think test.outcome
is a string now, is the int cast correct here?
tasks/test_results_finisher.py
Outdated
.join(Upload) | ||
.join(CommitReport) | ||
.join(Commit) | ||
.filter(Commit.id_ == commit.id_) | ||
.order_by(TestInstance.test_id) | ||
.order_by(desc(Upload.created_at)) | ||
.distinct(TestInstance.test_id) | ||
.all() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider moving this query to a helper function on TestInstance
or something since you use it in more than one place so that the logic definitely stays the same
tasks/test_results_finisher.py
Outdated
) | ||
return {"notify_attempted": False, "notify_succeeded": False} | ||
|
||
success = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this here if it's overwritten two lines down?
tasks/test_results_finisher.py
Outdated
|
||
def check_if_no_failures(self, testrun_list): | ||
return all( | ||
[instance.outcome != int(Outcome.Failure) for instance in testrun_list] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same Q re instance.outcome
being a string and int()
maybe not being correct
This class creates the PR comment message based on the test instances passed to it and posts/edits the comment on the PR. Signed-off-by: joseph-sentry <joseph.sawaya@sentry.io>
The notify task will exit early now, if the latest test instances relevant to the notify's commit contain failures Signed-off-by: joseph-sentry <joseph.sawaya@sentry.io>
This task will check if failures exist in the latest round of test instances for this commit. If they do it will run the test results notifier, else it will trigger the notify task to run. This task takes the same lock as the notify task so that they don't try writing to the PR comment at the same time. Signed-off-by: joseph-sentry <joseph.sawaya@sentry.io>
The test result finisher runs after the test results processors are done running. This is achieved using a celery chord. Signed-off-by: joseph-sentry <joseph.sawaya@sentry.io>
Signed-off-by: joseph-sentry <joseph.sawaya@sentry.io>
Signed-off-by: joseph-sentry <joseph.sawaya@sentry.io>
Signed-off-by: joseph-sentry <joseph.sawaya@sentry.io>
this commit changes the int conversions of Outcome to str conversions. moves the db query for latest test instances for a given commit to a separate function and cleans up some unused vars Signed-off-by: joseph-sentry <joseph.sawaya@sentry.io>
efa626e
to
5d625ed
Compare
Depends on: #197
Summary
This PR adds functionality for the test results PR comment feature, and contains the necessary improvements to the test result processor task to achieve this.
The first change to the processor is that it's no longer writing to the db anymore. This is because the processor tasks run concurrently, and concurrent writes to the DB were making it difficult to ensure that there multiple Test objects weren't being created at the same time. The writes to the DB to persist
Test
andTestInstance
objects were moved to the finisher.The second change to the processor is that it now returns a processed list of "Testrun dicts", which contains some metadata (
env
andrun_number
) and a list of "testruns" which the finisher will use to decide whatTest
andTestInstance
objects to create.The
TestResultsFinisher
task is created in this PR. This task is responsible for gathering the results of the processing step, adding newTest
andTestInstance
objects, updating existingTestInstance
objects that need to be updated, and finally either writing the test results PR comment or triggering the notify task to write the coverage PR comment.The upload task was updated to use a chord to run the processor tasks concurrently and to pass their results to the finisher task that will run when all the processor tasks are completed.
The notify task now checks if there are any failed tests that exist on a report attached to the commit it is trying to notify for, if there exist failed tests, it will no longer notify.
Edge cases
CI Re-run
This is the edge case where a user might not make any changes to the code, but re-runs the CI. This may happen if there is a flaky test that sometimes fails. We must handle this because when this happens we want to update the outcomes of the tests that ran previously. The
run_number
metadata field that the processor passes to the finisher is used to handle this edge case. Therun_number
corresponds to thebuild_code
in the upload. This helps us distinguish between different runs of the same workflow. Therun_number
tells the finisher that if aTestInstance
exists on the current commit and maps to the sameTest
as the one we are currently examining, we should look at therun_number
to determine which one should be used. The notify task being called if there are no failures in the test result finisher, is also part of the solution to this edge case, because of the case where during the re run, the coverage notifier runs before the test result finisherSame test, different env
This is a case where a user may be running the same test in the same CI run multiple times under a different environment eg. running a tests under multiple python versions. We need to be able to distinguish between these tests. We do this by adding the
env
field to theTest
object which comprises a hash of theflag_names
and thejob_code
of upload where theTest
was first created. The uniqueness constraint onTest
objects includes thetestsuite
,name
,repoid
andenv
fields which means there can only be oneTest
with the same name, testsuite and env for each repo.Subsequent uploads that contain the same test being run with the same env will map to the existing
Test
object in the DB.Possible improvements
ReportLevelTotals
)TestInstance
data to GCS in sqlite files