Skip to content
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

feat: add models and task for failed test ingestion #197

Merged
merged 33 commits into from
Jan 25, 2024

Conversation

joseph-sentry
Copy link
Contributor

@joseph-sentry joseph-sentry commented Dec 1, 2023

@codecov-qa
Copy link

codecov-qa bot commented Dec 1, 2023

Codecov Report

Attention: 13 lines in your changes are missing coverage. Please review.

Comparison is base (292c555) 98.13% compared to head (ea5afbf) 98.12%.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #197      +/-   ##
==========================================
- Coverage   98.13%   98.12%   -0.01%     
==========================================
  Files         370      373       +3     
  Lines       29996    30480     +484     
==========================================
+ Hits        29437    29909     +472     
- Misses        559      571      +12     
Flag Coverage Δ
integration 98.12% <97.32%> (-0.01%) ⬇️
latest-uploader-overall 98.12% <97.32%> (-0.01%) ⬇️
unit 98.12% <97.32%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
NonTestCode 96.18% <94.06%> (-0.04%) ⬇️
OutsideTasks 97.91% <88.88%> (-0.03%) ⬇️
Files Coverage Δ
database/models/reports.py 99.35% <100.00%> (+0.14%) ⬆️
...sks/tests/unit/test_test_results_processor_task.py 100.00% <100.00%> (ø)
tasks/tests/unit/test_upload_task.py 99.49% <100.00%> (+0.01%) ⬆️
tasks/upload.py 97.93% <91.66%> (-0.57%) ⬇️
tasks/test_results_processor.py 97.56% <97.56%> (ø)
services/test_results.py 81.39% <81.39%> (ø)

... and 1 file with indirect coverage changes

Copy link

codecov bot commented Dec 1, 2023

Codecov Report

Attention: 13 lines in your changes are missing coverage. Please review.

Comparison is base (292c555) 98.11% compared to head (ea5afbf) 98.12%.

Changes have been made to critical files, which contain lines commonly executed in production. Learn more

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #197      +/-   ##
==========================================
+ Coverage   98.11%   98.12%   +0.01%     
==========================================
  Files         401      373      -28     
  Lines       30697    30480     -217     
==========================================
- Hits        30117    29909     -208     
+ Misses        580      571       -9     
Flag Coverage Δ
integration 98.12% <97.32%> (-0.01%) ⬇️
latest-uploader-overall 98.12% <97.32%> (-0.01%) ⬇️
onlysomelabels ?
unit 98.12% <97.32%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
NonTestCode 96.18% <94.06%> (+0.06%) ⬆️
OutsideTasks 97.91% <88.88%> (-0.03%) ⬇️
Files Coverage Δ
database/models/reports.py 99.35% <100.00%> (+0.14%) ⬆️
...sks/tests/unit/test_test_results_processor_task.py 100.00% <100.00%> (ø)
tasks/tests/unit/test_upload_task.py 99.49% <100.00%> (+0.01%) ⬆️
tasks/upload.py Critical 97.93% <91.66%> (-0.59%) ⬇️
tasks/test_results_processor.py 97.56% <97.56%> (ø)
services/test_results.py 81.39% <81.39%> (ø)

... and 74 files with indirect coverage changes

This change has been scanned for critical changes. Learn more

@joseph-sentry joseph-sentry force-pushed the joseph/failed-test-ingestion branch 2 times, most recently from a351e98 to 99fe006 Compare December 8, 2023 22:27
@joseph-sentry joseph-sentry changed the title feat: add models for failed test ingestion feat: add models and task for failed test ingestion Dec 8, 2023
@joseph-sentry joseph-sentry force-pushed the joseph/failed-test-ingestion branch 2 times, most recently from 82cc9a5 to 76ea63d Compare December 14, 2023 17:36
@joseph-sentry joseph-sentry force-pushed the joseph/failed-test-ingestion branch 2 times, most recently from a68c001 to fb700f2 Compare January 3, 2024 14:22
@codecov-staging
Copy link

codecov-staging bot commented Jan 3, 2024

Codecov Report

Attention: 13 lines in your changes are missing coverage. Please review.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #197      +/-   ##
==========================================
- Coverage   98.13%   98.12%   -0.01%     
==========================================
  Files         370      373       +3     
  Lines       29996    30480     +484     
==========================================
+ Hits        29437    29909     +472     
- Misses        559      571      +12     
Flag Coverage Δ
integration 98.12% <97.32%> (-0.01%) ⬇️
latest-uploader-overall 98.12% <97.32%> (-0.01%) ⬇️
unit 98.12% <97.32%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
NonTestCode 96.18% <94.06%> (-0.04%) ⬇️
OutsideTasks 97.91% <88.88%> (-0.03%) ⬇️
Files Coverage Δ
database/models/reports.py 99.35% <100.00%> (+0.14%) ⬆️
...sks/tests/unit/test_test_results_processor_task.py 100.00% <100.00%> (ø)
tasks/tests/unit/test_upload_task.py 99.49% <100.00%> (+0.01%) ⬆️
tasks/upload.py 97.93% <91.66%> (-0.57%) ⬇️
tasks/test_results_processor.py 97.56% <97.56%> (ø)
services/test_results.py 81.39% <81.39%> (ø)

... and 1 file with indirect coverage changes

Copy link

codecov-public-qa bot commented Jan 3, 2024

Codecov Report

Merging #197 (ea5afbf) into main (292c555) will decrease coverage by 0.01%.
The diff coverage is 97.32%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #197      +/-   ##
==========================================
- Coverage   98.13%   98.12%   -0.01%     
==========================================
  Files         370      373       +3     
  Lines       29996    30480     +484     
==========================================
+ Hits        29437    29909     +472     
- Misses        559      571      +12     
Flag Coverage Δ
integration 98.12% <97.32%> (-0.01%) ⬇️
latest-uploader-overall 98.12% <97.32%> (-0.01%) ⬇️
unit 98.12% <97.32%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
NonTestCode 96.18% <94.06%> (-0.04%) ⬇️
OutsideTasks 97.91% <88.88%> (-0.03%) ⬇️
Files Coverage Δ
database/models/reports.py 99.35% <100.00%> (+0.14%) ⬆️
...sks/tests/unit/test_test_results_processor_task.py 100.00% <100.00%> (ø)
tasks/tests/unit/test_upload_task.py 99.49% <100.00%> (+0.01%) ⬆️
tasks/upload.py 97.93% <91.66%> (-0.57%) ⬇️
tasks/test_results_processor.py 97.56% <97.56%> (ø)
services/test_results.py 81.39% <81.39%> (ø)

... and 1 file with indirect coverage changes

):
for testrun in testrun_list:
test = (
db_session.query(Test)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an n+1 query. Query for the list of tests outside the loop. Then check for existence in the loop.

tasks/test_results_processor.py Outdated Show resolved Hide resolved
tasks/test_results_processor.py Outdated Show resolved Hide resolved
elif b"pytest" in first_line:
testrun_list = parse_pytest_reportlog(file_content)
else:
testrun_list = parse_vitest_json(file_content)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we maybe match something for vitest and then "else" raise a not supported error?

This might get weird otherwise.

@joseph-sentry joseph-sentry force-pushed the joseph/failed-test-ingestion branch from 769d55a to b493ab3 Compare January 4, 2024 20:47
parsed_testruns: List[Testrun] = self.process_individual_arg(
upload_obj, upload_obj.report.commit.repository
)
except Exception:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine to leave a catch all exception for starters so we avoid crashing the task while we battle test it. But please be explicit about it, as having a catch-all exception is not good practice.

Also the warning message won't be helpful when debugging. Make sure to include the stack trace so we know what to fix (in case there's anything)

return {"successful": False}

# concat existing and new test information
testrun_list += parsed_testruns
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the upload task it seems that you decided to go with a group, that is parallel in nature (meaning we might be processing multiple uploads at the same time for the same commit).

This indicates that it's ok to be saving data concurrently for the same commit. I wonder then if it's necessary to accumulate the parsing results in testrun_list prior to saving them. I think this list might become possibly big (we know of users that have +100K tests, for example), and this seems like it could use a fairly large amount of data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could save the instances as soon as we parse them, but for tests I think it's better to avoid doing it on every iteration since we will be getting the list of all tests for that repository

test_set = set()
for test in repo_tests:
test_set.add(f"{test.testsuite}::{test.name}")
for testrun in testrun_list:
Copy link
Contributor

@giovanni-guidini giovanni-guidini Jan 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can leverage https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.merge session.merge here to offload this logic to the database...

I have a feeling it'd be more efficient, if possible.

EDIT probably we can't cause it looks at the primary key of the instance and ... well we don't have an instance yet 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# TODO: improve report matching capabilities
# use file extensions?
# maybe do the matching in the testing result parser lib?
first_line = bytes("".join(first_line.decode("utf-8").split()), "utf-8")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] report matching should be in its own function

elif first_line.startswith(b'"{"numTotalTestSuites":'):
testrun_list = parse_vitest_json(file_content)
except Exception as e:
log.warning(f"Error parsing: {file_content.decode()}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a static message an add extra args to the logger. That will make it easier to search for logs and it's the norm for worker logs

{
"duration_seconds": 0.001,
"name": "api.temp.calculator.test_calculator::test_add",
"outcome": "Outcome.Pass",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this "Outcome.Pass" defined? In the model the field is an Integer, but I can't find the Enum for it....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's defined in the test-results-parser library

Copy link
Contributor

@giovanni-guidini giovanni-guidini Jan 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we import the type definition into the python code? nice... works like an enum or does it think it's just a string?

EDIT ah I see the docs https://pyo3.rs/main/class
very nice very nice

@joseph-sentry joseph-sentry force-pushed the joseph/failed-test-ingestion branch 2 times, most recently from f296188 to 37f85b1 Compare January 9, 2024 21:31
Copy link
Contributor

@matt-codecov matt-codecov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first off: awesome work on the feature, i think the code is designed pretty well and i think we'll be able to build on it well for flaky test detection, etc

just a callout: we don't have a pattern in place or all the missing pieces we need to avoid loading everything into memory here, so beware that this implementation is at risk of the same performance problems coverage is until we address that

  • archive_service.read_file() reads into memory, we'd need to save to disk
  • json.loads() doesn't stream, we'd need another json parser
  • (above is enough to avoid loading more than one results file into memory at once)
  • (if we are worried individual results files will be enormous we also need the following)
  • zlib appears to have a streaming decompression util https://docs.python.org/3/library/zlib.html#zlib.decompressobj
  • a streaming base64 decoder
  • a rework of the rust code to take a bytes stream abstraction instead of a concrete Vec<u8> or &[u8]

log.error(
"File did not match any parser format",
extra=dict(
file_content=file_bytes.read().decode(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will blow up, maybe only print the first 300 chars or something

log.error(
"Error parsing test result file",
extra=dict(
file_content=file_content.decode(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will blow up, maybe limit to 300 chars or something

test_set = set()
for test in repo_tests:
test_set.add(f"{test.testsuite}::{test.name}")
for testrun in testrun_list:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joseph-sentry joseph-sentry force-pushed the joseph/failed-test-ingestion branch 2 times, most recently from d522306 to 28fd629 Compare January 12, 2024 18:54
testsuite = Column(types.String(256), nullable=False)

__table_args__ = (
UniqueConstraint(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think since API runs migrations we'll need a change there to actually create this constraint if that hasn't been done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if id_ contains all of these things and it's a proper PK then this constraint may not be necessary

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codecov/codecov-api#357 because id_ is just a sha256 hash of all of those

Copy link
Contributor

@matt-codecov matt-codecov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

un-accepting until we talk about whether this perf change is feasible quickly, worth blocking for, or should be deferred


class TestInstance(CodecovBaseModel, MixinBaseClass):
__tablename__ = "reports_testinstance"
test_id = Column(types.Integer, ForeignKey("reports_test.id"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hold up, if we change this to a hash of the test suite + test name we might be able to unlock performance improvements

the reason we can't do the db work in the processor task is because we need to synchronize to get/create records of the Test model because we need to put its id on the TestInstance model. if the ID could be computed from the test suite + test name, then we don't need to synchronize with other tasks to create our TestInstance records

as for creating the corresponding Test records, we could do INSERT ... ON CONFLICT DO NOTHING or INSERT ... WHERE NOT EXISTS (...) to attempt inserts in a fire-and-forget way and the database will handle synchronizing internally

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we would have to hash the env as well but I agree that this may get rid of the issue where we want to use a returning clause

if processor_task_group:
checkpoint_data = None
if checkpoints:
checkpoints.log(UploadFlow.INITIAL_PROCESSING_COMPLETE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we need to use different flows for coverage, bundles, and test results. we have totally different expectations/norms for how long each takes, and putting them together in one pool makes the metrics useless

Signed-off-by: joseph-sentry <joseph.sawaya@sentry.io>
…_list

Going to move the save report part to the finisher to avoid concurrency issues
This commit moves the writes in the test result finisher back to the
test result processor task.

The first step is to change the primary key of the Test object to a
text field. This is so we can use a computed primary key for Test
objects.

By computing the test ID using the generate_test_id function,
we avoid having to use a returning clause from the insert on conflict
do nothing for Test objects.

The timestamp field on the TestInstance object has also been removed
as its replaced by using the upload created_at to determine the order
of TestInstance objects to be showed in the PR comment.

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>
Signed-off-by: joseph-sentry <joseph.sawaya@sentry.io>
@joseph-sentry joseph-sentry force-pushed the joseph/failed-test-ingestion branch from 0deb7c9 to e37e0e6 Compare January 25, 2024 17:41
we don't want to fail the entire upload parsing because one
test result file failed to be parsed.

Signed-off-by: joseph-sentry <joseph.sawaya@sentry.io>
@joseph-sentry joseph-sentry merged commit d686ff9 into main Jan 25, 2024
16 of 31 checks passed
@joseph-sentry joseph-sentry deleted the joseph/failed-test-ingestion branch January 25, 2024 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants