From 14741f0e17ba56e5a5bd8fa46556780c5a3c65d5 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Fri, 27 Sep 2024 14:09:55 +0200 Subject: [PATCH 01/30] Slightly improve `FileFinder` typing (#509) This fixes a bunch of typing errors, as well as auto-reformats a bunch of stuff via the precommit hook. --- codecov_cli/helpers/folder_searcher.py | 24 ++--- codecov_cli/services/upload/file_finder.py | 20 ++-- tests/ci_adapters/test_azure_pipelines.py | 2 +- tests/commands/test_process_test_results.py | 18 +++- tests/helpers/test_git.py | 2 - tests/helpers/test_network_finder.py | 93 +++++++++++++++++-- tests/helpers/test_request.py | 15 ++- tests/helpers/test_upload_sender.py | 8 +- .../empty_upload/test_empty_upload.py | 18 +++- tests/services/report/test_report_service.py | 9 +- .../test_static_analysis_service.py | 2 +- .../upload/test_coverage_file_finder.py | 8 +- tests/services/upload/test_upload_service.py | 46 +++++++-- 13 files changed, 205 insertions(+), 60 deletions(-) diff --git a/codecov_cli/helpers/folder_searcher.py b/codecov_cli/helpers/folder_searcher.py index dc28a572..cc87238a 100644 --- a/codecov_cli/helpers/folder_searcher.py +++ b/codecov_cli/helpers/folder_searcher.py @@ -2,13 +2,13 @@ import os import pathlib import re -import typing from fnmatch import translate +from typing import Generator, List, Optional, Pattern def _is_included( - filename_include_regex: typing.Pattern, - multipart_include_regex: typing.Optional[typing.Pattern], + filename_include_regex: Pattern, + multipart_include_regex: Optional[Pattern], path: pathlib.Path, ): return filename_include_regex.match(path.name) and ( @@ -18,8 +18,8 @@ def _is_included( def _is_excluded( - filename_exclude_regex: typing.Optional[typing.Pattern], - multipart_exclude_regex: typing.Optional[typing.Pattern], + filename_exclude_regex: Optional[Pattern], + multipart_exclude_regex: Optional[Pattern], path: pathlib.Path, ): return ( @@ -31,14 +31,14 @@ def _is_excluded( def search_files( folder_to_search: pathlib.Path, - folders_to_ignore: typing.List[str], + folders_to_ignore: List[str], *, - filename_include_regex: typing.Pattern, - filename_exclude_regex: typing.Optional[typing.Pattern] = None, - multipart_include_regex: typing.Optional[typing.Pattern] = None, - multipart_exclude_regex: typing.Optional[typing.Pattern] = None, + filename_include_regex: Pattern, + filename_exclude_regex: Optional[Pattern] = None, + multipart_include_regex: Optional[Pattern] = None, + multipart_exclude_regex: Optional[Pattern] = None, search_for_directories: bool = False, -) -> typing.Generator[pathlib.Path, None, None]: +) -> Generator[pathlib.Path, None, None]: """ " Searches for files or directories in a given folder @@ -85,7 +85,7 @@ def search_files( yield file_path -def globs_to_regex(patterns: typing.List[str]) -> typing.Optional[typing.Pattern]: +def globs_to_regex(patterns: List[str]) -> Optional[Pattern]: """ Converts a list of glob patterns to a combined ORed regex diff --git a/codecov_cli/services/upload/file_finder.py b/codecov_cli/services/upload/file_finder.py index a0a1db45..9c0a89cc 100644 --- a/codecov_cli/services/upload/file_finder.py +++ b/codecov_cli/services/upload/file_finder.py @@ -1,7 +1,7 @@ import logging import os -import typing from pathlib import Path +from typing import Iterable, List, Optional, Pattern from codecov_cli.helpers.folder_searcher import globs_to_regex, search_files from codecov_cli.types import UploadCollectionResultFile @@ -183,9 +183,9 @@ class FileFinder(object): def __init__( self, - search_root: Path = None, - folders_to_ignore: typing.List[str] = None, - explicitly_listed_files: typing.List[Path] = None, + search_root: Optional[Path] = None, + folders_to_ignore: Optional[List[str]] = None, + explicitly_listed_files: Optional[List[Path]] = None, disable_search: bool = False, report_type: str = "coverage", ): @@ -195,7 +195,7 @@ def __init__( self.disable_search = disable_search self.report_type = report_type - def find_files(self) -> typing.List[UploadCollectionResultFile]: + def find_files(self) -> List[UploadCollectionResultFile]: if self.report_type == "coverage": files_excluded_patterns = coverage_files_excluded_patterns files_patterns = coverage_files_patterns @@ -203,21 +203,21 @@ def find_files(self) -> typing.List[UploadCollectionResultFile]: files_excluded_patterns = test_results_files_excluded_patterns files_patterns = test_results_files_patterns regex_patterns_to_exclude = globs_to_regex(files_excluded_patterns) - files_paths = [] + assert regex_patterns_to_exclude # this is never `None` + files_paths: Iterable[Path] = [] user_files_paths = [] if self.explicitly_listed_files: user_files_paths = self.get_user_specified_files(regex_patterns_to_exclude) if not self.disable_search: regex_patterns_to_include = globs_to_regex(files_patterns) + assert regex_patterns_to_include # this is never `None` files_paths = search_files( self.search_root, default_folders_to_ignore + self.folders_to_ignore, filename_include_regex=regex_patterns_to_include, filename_exclude_regex=regex_patterns_to_exclude, ) - result_files = [ - UploadCollectionResultFile(path) for path in files_paths if files_paths - ] + result_files = [UploadCollectionResultFile(path) for path in files_paths] user_result_files = [ UploadCollectionResultFile(path) for path in user_files_paths @@ -226,7 +226,7 @@ def find_files(self) -> typing.List[UploadCollectionResultFile]: return list(set(result_files + user_result_files)) - def get_user_specified_files(self, regex_patterns_to_exclude): + def get_user_specified_files(self, regex_patterns_to_exclude: Pattern): user_filenames_to_include = [] files_excluded_but_user_includes = [] for file in self.explicitly_listed_files: diff --git a/tests/ci_adapters/test_azure_pipelines.py b/tests/ci_adapters/test_azure_pipelines.py index 5dd71d0f..6a635bfb 100644 --- a/tests/ci_adapters/test_azure_pipelines.py +++ b/tests/ci_adapters/test_azure_pipelines.py @@ -47,7 +47,7 @@ def test_detect(self, env_dict, expected, mocker): ( { AzurePipelinesEnvEnum.BUILD_SOURCEVERSION: "123456789000111", - AzurePipelinesEnvEnum.SYSTEM_PULLREQUEST_SOURCECOMMITID: "111000987654321" + AzurePipelinesEnvEnum.SYSTEM_PULLREQUEST_SOURCECOMMITID: "111000987654321", }, "111000987654321", ), diff --git a/tests/commands/test_process_test_results.py b/tests/commands/test_process_test_results.py index ca073037..97c6d956 100644 --- a/tests/commands/test_process_test_results.py +++ b/tests/commands/test_process_test_results.py @@ -44,12 +44,22 @@ def test_process_test_results( assert result.exit_code == 0 - mocked_post.assert_called_with( url="https://api.github.com/repos/fake/repo/issues/pull/comments", data={ "body": "### :x: Failed Test Results: \nCompleted 4 tests with **`1 failed`**, 3 passed and 0 skipped.\n
View the full list of failed tests\n\n| **Test Description** | **Failure message** |\n| :-- | :-- |\n|
Testsuite:
api.temp.calculator.test_calculator::test_divide

Test name:
pytest
|
def
test_divide():
> assert Calculator.divide(1, 2) == 0.5
E assert 1.0 == 0.5
E + where 1.0 = <function Calculator.divide at 0x104c9eb90>(1, 2)
E + where <function Calculator.divide at 0x104c9eb90> = Calculator.divide
.../temp/calculator/test_calculator.py:30: AssertionError
|", - "cli_args": {'auto_load_params_from': None, 'codecov_yml_path': None, 'enterprise_url': None, 'verbose': False, 'version': 'cli-0.7.4', 'command': 'process-test-results', 'provider_token': 'whatever', 'disable_search': True, 'dir': os.getcwd(), 'exclude_folders': ()}, + "cli_args": { + "auto_load_params_from": None, + "codecov_yml_path": None, + "enterprise_url": None, + "verbose": False, + "version": "cli-0.7.4", + "command": "process-test-results", + "provider_token": "whatever", + "disable_search": True, + "dir": os.getcwd(), + "exclude_folders": (), + }, }, headers={ "Accept": "application/vnd.github+json", @@ -59,7 +69,6 @@ def test_process_test_results( ) - def test_process_test_results_non_existent_file(mocker, tmpdir): tmp_file = tmpdir.mkdir("folder").join("summary.txt") @@ -94,7 +103,7 @@ def test_process_test_results_non_existent_file(mocker, tmpdir): assert result.exit_code == 1 expected_logs = [ "ci service found", - 'Some files were not found', + "Some files were not found", ] for log in expected_logs: assert log in result.output @@ -183,7 +192,6 @@ def test_process_test_results_missing_ref(mocker, tmpdir): assert log in result.output - def test_process_test_results_missing_step_summary(mocker, tmpdir): tmp_file = tmpdir.mkdir("folder").join("summary.txt") diff --git a/tests/helpers/test_git.py b/tests/helpers/test_git.py index fdcc778e..cca8a60b 100644 --- a/tests/helpers/test_git.py +++ b/tests/helpers/test_git.py @@ -133,5 +133,3 @@ def test_get_git_service_class(): assert isinstance(git.get_git_service("github"), Github) assert git.get_git_service("gitlab") == None assert git.get_git_service("bitbucket") == None - - diff --git a/tests/helpers/test_network_finder.py b/tests/helpers/test_network_finder.py index 859abc7d..afbb08c2 100644 --- a/tests/helpers/test_network_finder.py +++ b/tests/helpers/test_network_finder.py @@ -12,11 +12,36 @@ def test_find_files(mocker, tmp_path): mocked_vs = MagicMock() mocked_vs.list_relevant_files.return_value = filenames - assert NetworkFinder(versioning_system=mocked_vs, network_filter=None, network_prefix=None, network_root_folder=tmp_path).find_files() == filenames - assert NetworkFinder(versioning_system=mocked_vs, network_filter="hello", network_prefix="bello", network_root_folder=tmp_path).find_files(False) == filtered_filenames - assert NetworkFinder(versioning_system=mocked_vs, network_filter="hello", network_prefix="bello", network_root_folder=tmp_path).find_files(True) == filenames + assert ( + NetworkFinder( + versioning_system=mocked_vs, + network_filter=None, + network_prefix=None, + network_root_folder=tmp_path, + ).find_files() + == filenames + ) + assert ( + NetworkFinder( + versioning_system=mocked_vs, + network_filter="hello", + network_prefix="bello", + network_root_folder=tmp_path, + ).find_files(False) + == filtered_filenames + ) + assert ( + NetworkFinder( + versioning_system=mocked_vs, + network_filter="hello", + network_prefix="bello", + network_root_folder=tmp_path, + ).find_files(True) + == filenames + ) mocked_vs.list_relevant_files.assert_called_with(tmp_path) + def test_find_files_with_filter(mocker, tmp_path): filenames = ["hello/a.txt", "hello/c.txt", "bello/b.txt"] filtered_filenames = ["hello/a.txt", "hello/c.txt"] @@ -24,10 +49,27 @@ def test_find_files_with_filter(mocker, tmp_path): mocked_vs = MagicMock() mocked_vs.list_relevant_files.return_value = filenames - assert NetworkFinder(versioning_system=mocked_vs, network_filter="hello", network_prefix=None, network_root_folder=tmp_path).find_files() == filtered_filenames - assert NetworkFinder(versioning_system=mocked_vs, network_filter="hello", network_prefix="bello", network_root_folder=tmp_path).find_files(True) == filenames + assert ( + NetworkFinder( + versioning_system=mocked_vs, + network_filter="hello", + network_prefix=None, + network_root_folder=tmp_path, + ).find_files() + == filtered_filenames + ) + assert ( + NetworkFinder( + versioning_system=mocked_vs, + network_filter="hello", + network_prefix="bello", + network_root_folder=tmp_path, + ).find_files(True) + == filenames + ) mocked_vs.list_relevant_files.assert_called_with(tmp_path) + def test_find_files_with_prefix(mocker, tmp_path): filenames = ["hello/a.txt", "hello/c.txt", "bello/b.txt"] filtered_filenames = ["hellohello/a.txt", "hellohello/c.txt", "hellobello/b.txt"] @@ -35,10 +77,27 @@ def test_find_files_with_prefix(mocker, tmp_path): mocked_vs = MagicMock() mocked_vs.list_relevant_files.return_value = filenames - assert NetworkFinder(versioning_system=mocked_vs, network_filter=None, network_prefix="hello", network_root_folder=tmp_path).find_files() == filtered_filenames - assert NetworkFinder(versioning_system=mocked_vs, network_filter="hello", network_prefix="bello", network_root_folder=tmp_path).find_files(True) == filenames + assert ( + NetworkFinder( + versioning_system=mocked_vs, + network_filter=None, + network_prefix="hello", + network_root_folder=tmp_path, + ).find_files() + == filtered_filenames + ) + assert ( + NetworkFinder( + versioning_system=mocked_vs, + network_filter="hello", + network_prefix="bello", + network_root_folder=tmp_path, + ).find_files(True) + == filenames + ) mocked_vs.list_relevant_files.assert_called_with(tmp_path) + def test_find_files_with_filter_and_prefix(mocker, tmp_path): filenames = ["hello/a.txt", "hello/c.txt", "bello/b.txt"] filtered_filenames = ["bellohello/a.txt", "bellohello/c.txt"] @@ -46,6 +105,22 @@ def test_find_files_with_filter_and_prefix(mocker, tmp_path): mocked_vs = MagicMock() mocked_vs.list_relevant_files.return_value = filenames - assert NetworkFinder(versioning_system=mocked_vs, network_filter="hello", network_prefix="bello", network_root_folder=tmp_path).find_files() == filtered_filenames - assert NetworkFinder(versioning_system=mocked_vs, network_filter="hello", network_prefix="bello", network_root_folder=tmp_path).find_files(True) == filenames + assert ( + NetworkFinder( + versioning_system=mocked_vs, + network_filter="hello", + network_prefix="bello", + network_root_folder=tmp_path, + ).find_files() + == filtered_filenames + ) + assert ( + NetworkFinder( + versioning_system=mocked_vs, + network_filter="hello", + network_prefix="bello", + network_root_folder=tmp_path, + ).find_files(True) + == filenames + ) mocked_vs.list_relevant_files.assert_called_with(tmp_path) diff --git a/tests/helpers/test_request.py b/tests/helpers/test_request.py index 1125f93f..92dd6e6d 100644 --- a/tests/helpers/test_request.py +++ b/tests/helpers/test_request.py @@ -60,10 +60,12 @@ def test_log_result_without_token(mocker): error=None, warnings=[], status_code=201, - text="{\"message\":\"commit\",\"timestamp\":\"2024-03-25T15:41:07Z\",\"ci_passed\":true,\"state\":\"complete\",\"repository\":{\"name\":\"repo\",\"is_private\":false,\"active\":true,\"language\":\"python\",\"yaml\":null},\"author\":{\"avatar_url\":\"https://example.com\",\"service\":\"github\",\"username\":null,\"name\":\"dependabot[bot]\",\"ownerid\":2780265},\"commitid\":\"commit\",\"parent_commit_id\":\"parent\",\"pullid\":1,\"branch\":\"main\"}" + text='{"message":"commit","timestamp":"2024-03-25T15:41:07Z","ci_passed":true,"state":"complete","repository":{"name":"repo","is_private":false,"active":true,"language":"python","yaml":null},"author":{"avatar_url":"https://example.com","service":"github","username":null,"name":"dependabot[bot]","ownerid":2780265},"commitid":"commit","parent_commit_id":"parent","pullid":1,"branch":"main"}', ) log_warnings_and_errors_if_any(result, "Commit creating", False) - mock_log_debug.assert_called_with('Commit creating result', extra={'extra_log_attributes': {'result': result}}) + mock_log_debug.assert_called_with( + "Commit creating result", extra={"extra_log_attributes": {"result": result}} + ) def test_log_result_with_token(mocker): @@ -72,10 +74,10 @@ def test_log_result_with_token(mocker): error=None, warnings=[], status_code=201, - text="{\"message\": \"commit\", \"timestamp\": \"2024-07-16T20:51:07Z\", \"ci_passed\": true, \"state\": \"complete\", \"repository\": {\"name\": \"repo\", \"is_private\": false, \"active\": true, \"language\": \"python\", \"yaml\": {\"codecov\": {\"token\": \"faketoken\"}}, \"author\": {\"avatar_url\": \"https://example.com\", \"service\": \"github\", \"username\": \"author\", \"name\": \"author\", \"ownerid\": 3461769}, \"commitid\": \"commit\", \"parent_commit_id\": \"parent_commit\", \"pullid\": null, \"branch\": \"main\"}}" + text='{"message": "commit", "timestamp": "2024-07-16T20:51:07Z", "ci_passed": true, "state": "complete", "repository": {"name": "repo", "is_private": false, "active": true, "language": "python", "yaml": {"codecov": {"token": "faketoken"}}, "author": {"avatar_url": "https://example.com", "service": "github", "username": "author", "name": "author", "ownerid": 3461769}, "commitid": "commit", "parent_commit_id": "parent_commit", "pullid": null, "branch": "main"}}', ) - expected_text = "{\"message\": \"commit\", \"timestamp\": \"2024-07-16T20:51:07Z\", \"ci_passed\": true, \"state\": \"complete\", \"repository\": {\"name\": \"repo\", \"is_private\": false, \"active\": true, \"language\": \"python\", \"yaml\": {\"codecov\": {\"token\": \"f******************\"}}, \"author\": {\"avatar_url\": \"https://example.com\", \"service\": \"github\", \"username\": \"author\", \"name\": \"author\", \"ownerid\": 3461769}, \"commitid\": \"commit\", \"parent_commit_id\": \"parent_commit\", \"pullid\": null, \"branch\": \"main\"}}" + expected_text = '{"message": "commit", "timestamp": "2024-07-16T20:51:07Z", "ci_passed": true, "state": "complete", "repository": {"name": "repo", "is_private": false, "active": true, "language": "python", "yaml": {"codecov": {"token": "f******************"}}, "author": {"avatar_url": "https://example.com", "service": "github", "username": "author", "name": "author", "ownerid": 3461769}, "commitid": "commit", "parent_commit_id": "parent_commit", "pullid": null, "branch": "main"}}' expected = RequestResult( error=None, warnings=[], @@ -83,7 +85,9 @@ def test_log_result_with_token(mocker): text=expected_text, ) log_warnings_and_errors_if_any(result, "Commit creating", False) - mock_log_debug.assert_called_with('Commit creating result', extra={'extra_log_attributes': {'result': expected}}) + mock_log_debug.assert_called_with( + "Commit creating result", extra={"extra_log_attributes": {"result": expected}} + ) def test_get_token_header_or_fail(): @@ -102,6 +106,7 @@ def test_get_token_header_or_fail(): == "Codecov token not found. Please provide Codecov token with -t flag." ) + def test_get_token_header(): # Test with a valid UUID token token = uuid.uuid4() diff --git a/tests/helpers/test_upload_sender.py b/tests/helpers/test_upload_sender.py index 9033082c..68dd840b 100644 --- a/tests/helpers/test_upload_sender.py +++ b/tests/helpers/test_upload_sender.py @@ -301,10 +301,14 @@ def test_upload_sender_result_fail_post_400( assert sender.warnings is not None - @pytest.mark.parametrize("error_code", [500, 502]) def test_upload_sender_result_fail_post_500s( - self, mocker, mocked_responses, mocked_legacy_upload_endpoint, capsys, error_code + self, + mocker, + mocked_responses, + mocked_legacy_upload_endpoint, + capsys, + error_code, ): mocker.patch("codecov_cli.helpers.request.sleep") mocked_legacy_upload_endpoint.status = error_code diff --git a/tests/services/empty_upload/test_empty_upload.py b/tests/services/empty_upload/test_empty_upload.py index 16f9f946..b49d055e 100644 --- a/tests/services/empty_upload/test_empty_upload.py +++ b/tests/services/empty_upload/test_empty_upload.py @@ -21,7 +21,14 @@ def test_empty_upload_with_warnings(mocker): runner = CliRunner() with runner.isolation() as outstreams: res = empty_upload_logic( - "commit_sha", "owner/repo", uuid.uuid4(), "service", None, False, False, None + "commit_sha", + "owner/repo", + uuid.uuid4(), + "service", + None, + False, + False, + None, ) out_bytes = parse_outstreams_into_log_lines(outstreams[0].getvalue()) assert out_bytes == [ @@ -50,7 +57,14 @@ def test_empty_upload_with_error(mocker): runner = CliRunner() with runner.isolation() as outstreams: res = empty_upload_logic( - "commit_sha", "owner/repo", uuid.uuid4(), "service", None, False, False, None + "commit_sha", + "owner/repo", + uuid.uuid4(), + "service", + None, + False, + False, + None, ) out_bytes = parse_outstreams_into_log_lines(outstreams[0].getvalue()) diff --git a/tests/services/report/test_report_service.py b/tests/services/report/test_report_service.py index b3a0f04a..ac4c222b 100644 --- a/tests/services/report/test_report_service.py +++ b/tests/services/report/test_report_service.py @@ -124,5 +124,12 @@ def test_create_report_command_with_error(mocker): warnings=[], ) mock_send_report_data.assert_called_with( - "commit_sha", "code", "github", "token", "owner::::repo", "enterprise_url", 1, None + "commit_sha", + "code", + "github", + "token", + "owner::::repo", + "enterprise_url", + 1, + None, ) diff --git a/tests/services/static_analysis/test_static_analysis_service.py b/tests/services/static_analysis/test_static_analysis_service.py index 262a3130..635eecf9 100644 --- a/tests/services/static_analysis/test_static_analysis_service.py +++ b/tests/services/static_analysis/test_static_analysis_service.py @@ -136,7 +136,7 @@ async def side_effect(*args, **kwargs): should_force=False, folders_to_exclude=[], enterprise_url=None, - args=None + args=None, ) mock_file_finder.assert_called_with({}) mock_file_finder.return_value.find_files.assert_called() diff --git a/tests/services/upload/test_coverage_file_finder.py b/tests/services/upload/test_coverage_file_finder.py index afe717ff..29261fa1 100644 --- a/tests/services/upload/test_coverage_file_finder.py +++ b/tests/services/upload/test_coverage_file_finder.py @@ -220,12 +220,16 @@ def test_find_coverage_files_with_file_in_parent( for file in coverage_files: file.touch() - coverage_file_finder.explicitly_listed_files = [project_root.parent / "coverage.xml"] + coverage_file_finder.explicitly_listed_files = [ + project_root.parent / "coverage.xml" + ] result = sorted( [file.get_filename() for file in coverage_file_finder.find_files()] ) - expected = [UploadCollectionResultFile(Path(f"{project_root.parent}/coverage.xml"))] + expected = [ + UploadCollectionResultFile(Path(f"{project_root.parent}/coverage.xml")) + ] expected_paths = sorted([file.get_filename() for file in expected]) assert result == expected_paths diff --git a/tests/services/upload/test_upload_service.py b/tests/services/upload/test_upload_service.py index 4bba13e9..081ee2bb 100644 --- a/tests/services/upload/test_upload_service.py +++ b/tests/services/upload/test_upload_service.py @@ -84,7 +84,12 @@ def test_do_upload_logic_happy_path_legacy_uploader(mocker): cli_config, ["first_plugin", "another", "forth"] ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") - mock_select_network_finder.assert_called_with(versioning_system, network_filter=None, network_prefix=None, network_root_folder=None) + mock_select_network_finder.assert_called_with( + versioning_system, + network_filter=None, + network_prefix=None, + network_root_folder=None, + ) mock_generate_upload_data.assert_called_with("coverage") mock_send_upload_data.assert_called_with( mock_generate_upload_data.return_value, @@ -174,7 +179,12 @@ def test_do_upload_logic_happy_path(mocker): cli_config, ["first_plugin", "another", "forth"] ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") - mock_select_network_finder.assert_called_with(versioning_system, network_filter=None, network_prefix=None, network_root_folder=None) + mock_select_network_finder.assert_called_with( + versioning_system, + network_filter=None, + network_prefix=None, + network_root_folder=None, + ) mock_generate_upload_data.assert_called_with("coverage") mock_send_upload_data.assert_called_with( mock_generate_upload_data.return_value, @@ -251,7 +261,12 @@ def test_do_upload_logic_dry_run(mocker): ) out_bytes = parse_outstreams_into_log_lines(outstreams[0].getvalue()) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") - mock_select_network_finder.assert_called_with(versioning_system, network_filter=None, network_prefix=None, network_root_folder=None) + mock_select_network_finder.assert_called_with( + versioning_system, + network_filter=None, + network_prefix=None, + network_root_folder=None, + ) assert mock_generate_upload_data.call_count == 1 assert mock_send_upload_data.call_count == 0 mock_select_preparation_plugins.assert_called_with( @@ -406,7 +421,12 @@ def side_effect(*args, **kwargs): cli_config, ["first_plugin", "another", "forth"] ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") - mock_select_network_finder.assert_called_with(versioning_system, network_filter=None, network_prefix=None, network_root_folder=None) + mock_select_network_finder.assert_called_with( + versioning_system, + network_filter=None, + network_prefix=None, + network_root_folder=None, + ) mock_generate_upload_data.assert_called_with("coverage") mock_upload_completion_call.assert_called_with( commit_sha="commit_sha", @@ -480,7 +500,12 @@ def side_effect(*args, **kwargs): cli_config, ["first_plugin", "another", "forth"] ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") - mock_select_network_finder.assert_called_with(versioning_system, network_filter=None, network_prefix=None, network_root_folder=None) + mock_select_network_finder.assert_called_with( + versioning_system, + network_filter=None, + network_prefix=None, + network_root_folder=None, + ) mock_generate_upload_data.assert_called_with("coverage") @@ -537,7 +562,7 @@ def test_do_upload_logic_happy_path_test_results(mocker): pull_request_number="pr", git_service="git_service", enterprise_url=None, - args={"args": "fake_args"} + args={"args": "fake_args"}, ) out_bytes = parse_outstreams_into_log_lines(outstreams[0].getvalue()) assert out_bytes == [ @@ -549,7 +574,12 @@ def test_do_upload_logic_happy_path_test_results(mocker): assert res == UploadSender.send_upload_data.return_value mock_select_preparation_plugins.assert_not_called mock_select_file_finder.assert_called_with(None, None, None, False, "test_results") - mock_select_network_finder.assert_called_with(versioning_system, network_filter="some_dir", network_prefix="hello/", network_root_folder="root/") + mock_select_network_finder.assert_called_with( + versioning_system, + network_filter="some_dir", + network_prefix="hello/", + network_root_folder="root/", + ) mock_generate_upload_data.assert_called_with("test_results") mock_send_upload_data.assert_called_with( mock_generate_upload_data.return_value, @@ -569,5 +599,5 @@ def test_do_upload_logic_happy_path_test_results(mocker): "service", "git_service", None, - {"args": "fake_args"} + {"args": "fake_args"}, ) From ab27ada14dd7de6f1dd4fdfb71dccbd4a78f9f06 Mon Sep 17 00:00:00 2001 From: michelletran-codecov <167130096+michelletran-codecov@users.noreply.github.com> Date: Fri, 27 Sep 2024 13:41:45 -0400 Subject: [PATCH 02/30] Aggregate the test results files in CI (#507) --- .github/workflows/ci.yml | 67 ++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9d26e76..f98b4ce9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,12 +76,18 @@ jobs: pip install -r tests/requirements.txt - name: Test with pytest run: | - pytest --cov --junitxml=junit.xml + pytest --cov --junitxml=${{matrix.python-version}}junit.xml - name: Dogfooding codecov-cli if: ${{ !github.event.pull_request.head.repo.fork && github.repository_owner == 'codecov' }} run: | codecovcli -v do-upload --fail-on-error -t ${{ secrets.CODECOV_TOKEN }} --plugin pycoverage --flag python${{matrix.python-version}} codecovcli do-upload --report-type test_results --fail-on-error -t ${{ secrets.CODECOV_TOKEN }} --plugin pycoverage --flag python${{matrix.python-version}} + - name: Upload artifacts for test-results-processing + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: ${{matrix.python-version}}junit.xml + path: ${{matrix.python-version}}junit.xml static-analysis: runs-on: ubuntu-latest @@ -131,38 +137,31 @@ jobs: run: | codecovcli --codecov-yml-path=codecov.yml do-upload --plugin pycoverage --plugin compress-pycoverage --fail-on-error -t ${{ secrets.CODECOV_TOKEN }} --flag smart-labels - test-process-test-results-cmd: + process-test-results: + if: ${{ always() }} + needs: build-test-upload runs-on: ubuntu-latest - permissions: - pull-requests: write - strategy: - fail-fast: false - matrix: - include: - - python-version: "3.12" - - python-version: "3.11" - - python-version: "3.10" - - python-version: "3.9" - - python-version: "3.8" steps: - - uses: actions/checkout@v4 - with: - submodules: true - fetch-depth: 2 - - name: Set up Python ${{matrix.python-version}} - uses: actions/setup-python@v5 - with: - python-version: "${{matrix.python-version}}" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - python -m pip install -e . - pip install -r tests/requirements.txt - - name: Test with pytest - run: | - pytest --cov --junitxml=junit.xml - - name: Dogfooding codecov-cli - if: ${{ !cancelled() }} - run: | - codecovcli process-test-results --provider-token ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + - uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 2 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install dependencies for Dogfooding + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + python -m pip install -e . + pip install -r tests/requirements.txt + - name: Download all test results + uses: actions/download-artifact@v4 + with: + pattern: "*junit.xml" + path: "test_results" + merge-multiple: true + - name: Dogfooding codecov-cli + if: ${{ !cancelled() }} + run: | + codecovcli process-test-results --provider-token ${{ secrets.GITHUB_TOKEN }} --dir test_results From f4c17e1996d1a06db7c1c135f82b9be2aa7a85f1 Mon Sep 17 00:00:00 2001 From: Tom Hu <88201630+thomasrockhu-codecov@users.noreply.github.com> Date: Tue, 1 Oct 2024 09:53:44 -0500 Subject: [PATCH 03/30] fix: make versionc all in tests dynamic (#517) --- tests/commands/test_process_test_results.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/commands/test_process_test_results.py b/tests/commands/test_process_test_results.py index 97c6d956..1baf73b2 100644 --- a/tests/commands/test_process_test_results.py +++ b/tests/commands/test_process_test_results.py @@ -3,6 +3,7 @@ from click.testing import CliRunner +from codecov_cli import __version__ from codecov_cli.main import cli from codecov_cli.types import RequestResult @@ -53,7 +54,7 @@ def test_process_test_results( "codecov_yml_path": None, "enterprise_url": None, "verbose": False, - "version": "cli-0.7.4", + "version": f"cli-{__version__}", "command": "process-test-results", "provider_token": "whatever", "disable_search": True, From b63a8a101bdb78957bbce43976a8d0cdc1b42bc8 Mon Sep 17 00:00:00 2001 From: Codecov Releaser Date: Tue, 1 Oct 2024 11:00:14 -0400 Subject: [PATCH 04/30] Prepare release 0.7.5 (#516) Co-authored-by: Tom Hu <88201630+thomasrockhu-codecov@users.noreply.github.com> --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bb92b319..2102345e 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name="codecov-cli", - version="0.7.4", + version="0.7.5", packages=find_packages(exclude=["contrib", "docs", "tests*"]), description="Codecov Command Line Interface", long_description=long_description, From 5fb28a6fc3273bd8b754dd734855fe84b1e2dd1d Mon Sep 17 00:00:00 2001 From: michelletran-codecov <167130096+michelletran-codecov@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:20:48 -0400 Subject: [PATCH 05/30] Only run test results parser for PRs (#512) We don't want this to run when merging. So, check that the Github Ref is available before running the command. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f98b4ce9..be24dc08 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,7 +83,7 @@ jobs: codecovcli -v do-upload --fail-on-error -t ${{ secrets.CODECOV_TOKEN }} --plugin pycoverage --flag python${{matrix.python-version}} codecovcli do-upload --report-type test_results --fail-on-error -t ${{ secrets.CODECOV_TOKEN }} --plugin pycoverage --flag python${{matrix.python-version}} - name: Upload artifacts for test-results-processing - if: ${{ always() }} + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: ${{matrix.python-version}}junit.xml @@ -162,6 +162,6 @@ jobs: path: "test_results" merge-multiple: true - name: Dogfooding codecov-cli - if: ${{ !cancelled() }} + if: ${{ !cancelled() && github.ref }} run: | codecovcli process-test-results --provider-token ${{ secrets.GITHUB_TOKEN }} --dir test_results From a22eaf077bee6a3031d59879d3b7b75267500103 Mon Sep 17 00:00:00 2001 From: michelletran-codecov <167130096+michelletran-codecov@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:22:15 -0400 Subject: [PATCH 06/30] Update test-results-parser (#524) * Update test-results-parser This will include the new formatted comment * Use local version of codecov-cli for label analysis Since the tests are running on the "official" version of the CLI, it is failing for any interface changes between the official version and the version currently in review. So, changing it to also use the local CLI for running the tests. --- .github/workflows/ci.yml | 8 ++++--- codecov_cli/commands/process_test_results.py | 2 +- requirements.txt | 4 ++-- setup.py | 2 +- tests/commands/test_process_test_results.py | 24 +------------------- 5 files changed, 10 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be24dc08..0076094a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -124,10 +124,12 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.12" - - name: Install CLI + - name: Install dependencies for Dogfooding run: | - pip install -r requirements.txt -r tests/requirements.txt - pip install codecov-cli + python -m pip install --upgrade pip + pip install -r requirements.txt + python -m pip install -e . + pip install -r tests/requirements.txt - name: Label Analysis run: | BASE_SHA=$(git merge-base HEAD^ origin/main) diff --git a/codecov_cli/commands/process_test_results.py b/codecov_cli/commands/process_test_results.py index f887c5b9..d2ab1b9d 100644 --- a/codecov_cli/commands/process_test_results.py +++ b/codecov_cli/commands/process_test_results.py @@ -173,7 +173,7 @@ def generate_message_payload(upload_collection_results): try: logger.info(f"Parsing {result.get_filename()}") testruns = parse_junit_xml(result.get_content()) - for testrun in testruns: + for testrun in testruns.testruns: if ( testrun.outcome == Outcome.Failure or testrun.outcome == Outcome.Error diff --git a/requirements.txt b/requirements.txt index d40822af..781e4223 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # pip-compile setup.py @@ -43,7 +43,7 @@ sniffio==1.3.0 # anyio # httpcore # httpx -test-results-parser==0.1.0 +test-results-parser==0.5.1 # via codecov-cli (setup.py) tree-sitter==0.20.2 # via codecov-cli (setup.py) diff --git a/setup.py b/setup.py index 2102345e..e3e6a86f 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ "pyyaml==6.*", "responses==0.21.*", "tree-sitter==0.20.*", - "test-results-parser==0.1.*", + "test-results-parser==0.5.*", "regex", ], entry_points={ diff --git a/tests/commands/test_process_test_results.py b/tests/commands/test_process_test_results.py index 1baf73b2..2a6a0575 100644 --- a/tests/commands/test_process_test_results.py +++ b/tests/commands/test_process_test_results.py @@ -45,29 +45,7 @@ def test_process_test_results( assert result.exit_code == 0 - mocked_post.assert_called_with( - url="https://api.github.com/repos/fake/repo/issues/pull/comments", - data={ - "body": "### :x: Failed Test Results: \nCompleted 4 tests with **`1 failed`**, 3 passed and 0 skipped.\n
View the full list of failed tests\n\n| **Test Description** | **Failure message** |\n| :-- | :-- |\n|
Testsuite:
api.temp.calculator.test_calculator::test_divide

Test name:
pytest
|
def
test_divide():
&gt; assert Calculator.divide(1, 2) == 0.5
E assert 1.0 == 0.5
E + where 1.0 = &lt;function Calculator.divide at 0x104c9eb90&gt;(1, 2)
E + where &lt;function Calculator.divide at 0x104c9eb90&gt; = Calculator.divide
.../temp/calculator/test_calculator.py:30: AssertionError
|", - "cli_args": { - "auto_load_params_from": None, - "codecov_yml_path": None, - "enterprise_url": None, - "verbose": False, - "version": f"cli-{__version__}", - "command": "process-test-results", - "provider_token": "whatever", - "disable_search": True, - "dir": os.getcwd(), - "exclude_folders": (), - }, - }, - headers={ - "Accept": "application/vnd.github+json", - "Authorization": "Bearer whatever", - "X-GitHub-Api-Version": "2022-11-28", - }, - ) + mocked_post.assert_called_once() def test_process_test_results_non_existent_file(mocker, tmpdir): From 372aa00af031bf3faed2ee898cd958d395729f6a Mon Sep 17 00:00:00 2001 From: Rohan Bhaumik Date: Thu, 10 Oct 2024 11:55:37 -0400 Subject: [PATCH 07/30] Update README.md (#525) Better formatting of options for the `empty-upload` command --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1a09683b..457a2307 100644 --- a/README.md +++ b/README.md @@ -240,14 +240,14 @@ are ignored by codecov (including README and configuration files) `Usage: codecovcli empty-upload [OPTIONS]` -Options: - -C, --sha, --commit-sha TEXT Commit SHA (with 40 chars) [required] - -Z, --fail-on-error Exit with non-zero code in case of error - --git-service [github|gitlab|bitbucket|github_enterprise|gitlab_enterprise|bitbucket_server] - -t, --token TEXT Codecov upload token - -r, --slug TEXT owner/repo slug used instead of the private - repo token in Self-hosted - -h, --help Show this message and exit. +| Options | Description | usage +| :---: | :---: | :---: | + | -C, --sha, --commit-sha TEXT |Commit SHA (with 40 chars) | Required + | -t, --token TEXT |Codecov upload token |Required +| -r, --slug TEXT | owner/repo slug used instead of the private repo token in Self-hosted | Optional +| -Z, --fail-on-error | Exit with non-zero code in case of error|Optional +| --git-service| Options: github, gitlab, bitbucket, github_enterprise, gitlab_enterprise, bitbucket_server | Optional +| -h, --help | Show this message and exit.|Optional # How to Use Local Upload From 5fb183704854873f94a8ba1df4956f3350b03362 Mon Sep 17 00:00:00 2001 From: michelletran-codecov <167130096+michelletran-codecov@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:46:33 -0400 Subject: [PATCH 08/30] Rewrite to the same comment for offline test analytics (#522) * Ensure that we run offline test analytics on PRs The ref can also reference a branch when it's merged. Ensure that we're only running this for a PR. * Add ability to update existing GitHub Actions comment * Remove writing to GitHub Actions Summary To have feature parity with the existing Test Analytics, we don't actually want to publish to the GitHub Actions Summary. --- .github/workflows/ci.yml | 5 +- codecov_cli/commands/process_test_results.py | 148 ++++++++++---- codecov_cli/helpers/request.py | 7 + requirements.txt | 2 +- tests/commands/test_process_test_results.py | 195 +++++++++++++++---- 5 files changed, 277 insertions(+), 80 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0076094a..2e96fda3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,7 +163,8 @@ jobs: pattern: "*junit.xml" path: "test_results" merge-multiple: true + - name: Dogfooding codecov-cli - if: ${{ !cancelled() && github.ref }} + if: ${{ !cancelled() && github.ref && contains(github.ref, 'pull') }} run: | - codecovcli process-test-results --provider-token ${{ secrets.GITHUB_TOKEN }} --dir test_results + codecovcli process-test-results --dir test_results --github-token ${{ secrets.GITHUB_TOKEN }} diff --git a/codecov_cli/commands/process_test_results.py b/codecov_cli/commands/process_test_results.py index d2ab1b9d..6d3d89f2 100644 --- a/codecov_cli/commands/process_test_results.py +++ b/codecov_cli/commands/process_test_results.py @@ -1,8 +1,9 @@ +import json import logging import os import pathlib from dataclasses import dataclass -from typing import List +from typing import Any, Dict, List, Optional import click from test_results_parser import ( @@ -16,13 +17,17 @@ from codecov_cli.helpers.args import get_cli_args from codecov_cli.helpers.request import ( log_warnings_and_errors_if_any, + send_get_request, send_post_request, ) from codecov_cli.services.upload.file_finder import select_file_finder -from codecov_cli.types import CommandContext +from codecov_cli.types import CommandContext, RequestResult, UploadCollectionResultFile logger = logging.getLogger("codecovcli") +# Search marker so that we can find the comment when looking for previously created comments +CODECOV_SEARCH_MARKER = "" + _process_test_results_options = [ click.option( @@ -61,8 +66,8 @@ default=False, ), click.option( - "--provider-token", - help="Token used to make calls to Repo provider API", + "--github-token", + help="If specified, output the message to the specified GitHub PR.", type=str, default=None, ), @@ -92,65 +97,133 @@ def process_test_results( files=None, exclude_folders=None, disable_search=None, - provider_token=None, + github_token=None, ): - if provider_token is None: - raise click.ClickException( - "Provider token was not provided. Make sure to pass --provider-token option with the contents of the GITHUB_TOKEN secret, so we can make a comment." - ) + file_finder = select_file_finder( + dir, exclude_folders, files, disable_search, report_type="test_results" + ) - summary_file_path = os.getenv("GITHUB_STEP_SUMMARY") - if summary_file_path is None: + upload_collection_results: List[ + UploadCollectionResultFile + ] = file_finder.find_files() + if len(upload_collection_results) == 0: raise click.ClickException( - "Error getting step summary file path from environment. Can't find GITHUB_STEP_SUMMARY environment variable." + "No JUnit XML files were found. Make sure to specify them using the --file option." ) + payload: TestResultsNotificationPayload = generate_message_payload( + upload_collection_results + ) + + message: str = f"{build_message(payload)} {CODECOV_SEARCH_MARKER}" + + args: Dict[str, str] = get_cli_args(ctx) + + maybe_write_to_github_action(message, github_token, args) + + click.echo(message) + + +def maybe_write_to_github_action( + message: str, github_token: str, args: Dict[str, str] +) -> None: + if github_token is None: + # If no token is passed, then we will assume users are not running in a GitHub Action + return + + maybe_write_to_github_comment(message, github_token, args) + + +def maybe_write_to_github_comment( + message: str, github_token: str, args: Dict[str, str] +) -> None: slug = os.getenv("GITHUB_REPOSITORY") if slug is None: raise click.ClickException( - "Error getting repo slug from environment. Can't find GITHUB_REPOSITORY environment variable." + "Error getting repo slug from environment. " + "Can't find GITHUB_REPOSITORY environment variable." ) ref = os.getenv("GITHUB_REF") if ref is None or "pull" not in ref: raise click.ClickException( - "Error getting PR number from environment. Can't find GITHUB_REF environment variable." + "Error getting PR number from environment. " + "Can't find GITHUB_REF environment variable." ) + # GITHUB_REF is documented here: https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + pr_number = ref.split("/")[2] - file_finder = select_file_finder( - dir, exclude_folders, files, disable_search, report_type="test_results" + existing_comment = find_existing_github_comment(github_token, slug, pr_number) + comment_id = None + if existing_comment is not None: + comment_id = existing_comment.get("id") + + create_or_update_github_comment( + github_token, slug, pr_number, message, comment_id, args ) - upload_collection_results = file_finder.find_files() - if len(upload_collection_results) == 0: - raise click.ClickException( - "No JUnit XML files were found. Make sure to specify them using the --file option." - ) - payload = generate_message_payload(upload_collection_results) +def find_existing_github_comment( + github_token: str, repo_slug: str, pr_number: int +) -> Optional[Dict[str, Any]]: + url = f"https://api.github.com/repos/{repo_slug}/issues/{pr_number}/comments" - message = build_message(payload) + headers = { + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {github_token}", + "X-GitHub-Api-Version": "2022-11-28", + } + page = 1 - # write to step summary file - with open(summary_file_path, "w") as f: - f.write(message) + results = get_github_response_or_error(url, headers, page) + while results != []: + for comment in results: + comment_user = comment.get("user") + if ( + CODECOV_SEARCH_MARKER in comment.get("body", "") + and comment_user + and comment_user.get("login", "") == "github-actions[bot]" + ): + return comment - # GITHUB_REF is documented here: https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables - pr_number = ref.split("/")[2] + page += 1 + results = get_github_response_or_error(url, headers, page) - args = get_cli_args(ctx) - create_github_comment(provider_token, slug, pr_number, message, args) + # No matches, return None + return None -def create_github_comment(token, repo_slug, pr_number, message, args): - url = f"https://api.github.com/repos/{repo_slug}/issues/{pr_number}/comments" +def get_github_response_or_error( + url: str, headers: Dict[str, str], page: int +) -> Dict[str, Any]: + request_results: RequestResult = send_get_request( + url, headers, params={"page": page} + ) + if request_results.status_code != 200: + raise click.ClickException("Cannot find existing GitHub comment for PR.") + results = json.loads(request_results.text) + return results + + +def create_or_update_github_comment( + token: str, + repo_slug: str, + pr_number: str, + message: str, + comment_id: Optional[str], + args: Dict[str, Any], +) -> None: + if comment_id is not None: + url = f"https://api.github.com/repos/{repo_slug}/issues/comments/{comment_id}" + else: + url = f"https://api.github.com/repos/{repo_slug}/issues/{pr_number}/comments" headers = { "Accept": "application/vnd.github+json", "Authorization": f"Bearer {token}", "X-GitHub-Api-Version": "2022-11-28", } - logger.info("Posting github comment") + logger.info(f"Posting GitHub comment {comment_id}") log_warnings_and_errors_if_any( send_post_request( @@ -165,15 +238,16 @@ def create_github_comment(token, repo_slug, pr_number, message, args): ) -def generate_message_payload(upload_collection_results): +def generate_message_payload( + upload_collection_results: List[UploadCollectionResultFile], +) -> TestResultsNotificationPayload: payload = TestResultsNotificationPayload(failures=[]) for result in upload_collection_results: - testruns = [] try: logger.info(f"Parsing {result.get_filename()}") - testruns = parse_junit_xml(result.get_content()) - for testrun in testruns.testruns: + parsed_info = parse_junit_xml(result.get_content()) + for testrun in parsed_info.testruns: if ( testrun.outcome == Outcome.Failure or testrun.outcome == Outcome.Error diff --git a/codecov_cli/helpers/request.py b/codecov_cli/helpers/request.py index dbafc9a3..6c153bd5 100644 --- a/codecov_cli/helpers/request.py +++ b/codecov_cli/helpers/request.py @@ -95,6 +95,13 @@ def send_post_request( return request_result(post(url=url, data=data, headers=headers, params=params)) +@retry_request +def send_get_request( + url: str, headers: dict = None, params: dict = None +) -> RequestResult: + return request_result(get(url=url, headers=headers, params=params)) + + def get_token_header_or_fail(token: str) -> dict: if token is None: raise click.ClickException( diff --git a/requirements.txt b/requirements.txt index 781e4223..2d33a3d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # # pip-compile setup.py diff --git a/tests/commands/test_process_test_results.py b/tests/commands/test_process_test_results.py index 2a6a0575..e583159d 100644 --- a/tests/commands/test_process_test_results.py +++ b/tests/commands/test_process_test_results.py @@ -1,9 +1,8 @@ -import logging +import json import os from click.testing import CliRunner -from codecov_cli import __version__ from codecov_cli.main import cli from codecov_cli.types import RequestResult @@ -20,7 +19,6 @@ def test_process_test_results( { "GITHUB_REPOSITORY": "fake/repo", "GITHUB_REF": "pull/fake/pull", - "GITHUB_STEP_SUMMARY": tmp_file.dirname + tmp_file.basename, }, ) mocked_post = mocker.patch( @@ -34,8 +32,6 @@ def test_process_test_results( cli, [ "process-test-results", - "--provider-token", - "whatever", "--file", "samples/junit.xml", "--disable-search", @@ -44,21 +40,100 @@ def test_process_test_results( ) assert result.exit_code == 0 + # Ensure that there's an output + assert result.output - mocked_post.assert_called_once() +def test_process_test_results_create_github_message( + mocker, + tmpdir, +): + tmp_file = tmpdir.mkdir("folder").join("summary.txt") + + mocker.patch.dict( + os.environ, + { + "GITHUB_REPOSITORY": "fake/repo", + "GITHUB_REF": "pull/fake/123", + }, + ) + + mocker.patch( + "codecov_cli.commands.process_test_results.send_get_request", + return_value=RequestResult(status_code=200, error=None, warnings=[], text="[]"), + ) + + mocked_post = mocker.patch( + "codecov_cli.commands.process_test_results.send_post_request", + return_value=RequestResult( + status_code=200, error=None, warnings=[], text="yay it worked" + ), + ) + runner = CliRunner() + result = runner.invoke( + cli, + [ + "process-test-results", + "--github-token", + "fake-token", + "--file", + "samples/junit.xml", + "--disable-search", + ], + obj={}, + ) + + assert result.exit_code == 0 + assert ( + mocked_post.call_args.kwargs["url"] + == "https://api.github.com/repos/fake/repo/issues/123/comments" + ) + + +def test_process_test_results_update_github_message( + mocker, + tmpdir, +): -def test_process_test_results_non_existent_file(mocker, tmpdir): tmp_file = tmpdir.mkdir("folder").join("summary.txt") mocker.patch.dict( os.environ, { "GITHUB_REPOSITORY": "fake/repo", - "GITHUB_REF": "pull/fake/pull", - "GITHUB_STEP_SUMMARY": tmp_file.dirname + tmp_file.basename, + "GITHUB_REF": "pull/fake/123", }, ) + + github_fake_comments1 = [ + {"id": 54321, "user": {"login": "fake"}, "body": "some text"}, + ] + github_fake_comments2 = [ + { + "id": 12345, + "user": {"login": "github-actions[bot]"}, + "body": " and some other fake body", + }, + ] + + mocker.patch( + "codecov_cli.commands.process_test_results.send_get_request", + side_effect=[ + RequestResult( + status_code=200, + error=None, + warnings=[], + text=json.dumps(github_fake_comments1), + ), + RequestResult( + status_code=200, + error=None, + warnings=[], + text=json.dumps(github_fake_comments2), + ), + ], + ) + mocked_post = mocker.patch( "codecov_cli.commands.process_test_results.send_post_request", return_value=RequestResult( @@ -70,36 +145,80 @@ def test_process_test_results_non_existent_file(mocker, tmpdir): cli, [ "process-test-results", - "--provider-token", - "whatever", + "--github-token", + "fake-token", "--file", - "samples/fake.xml", + "samples/junit.xml", + "--disable-search", + ], + obj={}, + ) + + assert result.exit_code == 0 + assert ( + mocked_post.call_args.kwargs["url"] + == "https://api.github.com/repos/fake/repo/issues/comments/12345" + ) + + +def test_process_test_results_errors_getting_comments( + mocker, + tmpdir, +): + + tmp_file = tmpdir.mkdir("folder").join("summary.txt") + + mocker.patch.dict( + os.environ, + { + "GITHUB_REPOSITORY": "fake/repo", + "GITHUB_REF": "pull/fake/123", + }, + ) + + mocker.patch( + "codecov_cli.commands.process_test_results.send_get_request", + return_value=RequestResult( + status_code=400, + error=None, + warnings=[], + text="", + ), + ) + + mocked_post = mocker.patch( + "codecov_cli.commands.process_test_results.send_post_request", + return_value=RequestResult( + status_code=200, error=None, warnings=[], text="yay it worked" + ), + ) + runner = CliRunner() + result = runner.invoke( + cli, + [ + "process-test-results", + "--github-token", + "fake-token", + "--file", + "samples/junit.xml", "--disable-search", ], obj={}, ) assert result.exit_code == 1 - expected_logs = [ - "ci service found", - "Some files were not found", - ] - for log in expected_logs: - assert log in result.output -def test_process_test_results_missing_repo(mocker, tmpdir): +def test_process_test_results_non_existent_file(mocker, tmpdir): tmp_file = tmpdir.mkdir("folder").join("summary.txt") mocker.patch.dict( os.environ, { + "GITHUB_REPOSITORY": "fake/repo", "GITHUB_REF": "pull/fake/pull", - "GITHUB_STEP_SUMMARY": tmp_file.dirname + tmp_file.basename, }, ) - if "GITHUB_REPOSITORY" in os.environ: - del os.environ["GITHUB_REPOSITORY"] mocked_post = mocker.patch( "codecov_cli.commands.process_test_results.send_post_request", return_value=RequestResult( @@ -111,10 +230,8 @@ def test_process_test_results_missing_repo(mocker, tmpdir): cli, [ "process-test-results", - "--provider-token", - "whatever", "--file", - "samples/junit.xml", + "samples/fake.xml", "--disable-search", ], obj={}, @@ -123,25 +240,23 @@ def test_process_test_results_missing_repo(mocker, tmpdir): assert result.exit_code == 1 expected_logs = [ "ci service found", - "Error: Error getting repo slug from environment. Can't find GITHUB_REPOSITORY environment variable.", + "Some files were not found", ] for log in expected_logs: assert log in result.output -def test_process_test_results_missing_ref(mocker, tmpdir): +def test_process_test_results_missing_repo(mocker, tmpdir): tmp_file = tmpdir.mkdir("folder").join("summary.txt") mocker.patch.dict( os.environ, { - "GITHUB_REPOSITORY": "fake/repo", - "GITHUB_STEP_SUMMARY": tmp_file.dirname + tmp_file.basename, + "GITHUB_REF": "pull/fake/pull", }, ) - - if "GITHUB_REF" in os.environ: - del os.environ["GITHUB_REF"] + if "GITHUB_REPOSITORY" in os.environ: + del os.environ["GITHUB_REPOSITORY"] mocked_post = mocker.patch( "codecov_cli.commands.process_test_results.send_post_request", return_value=RequestResult( @@ -153,7 +268,7 @@ def test_process_test_results_missing_ref(mocker, tmpdir): cli, [ "process-test-results", - "--provider-token", + "--github-token", "whatever", "--file", "samples/junit.xml", @@ -165,24 +280,24 @@ def test_process_test_results_missing_ref(mocker, tmpdir): assert result.exit_code == 1 expected_logs = [ "ci service found", - "Error: Error getting PR number from environment. Can't find GITHUB_REF environment variable.", + "Error: Error getting repo slug from environment. Can't find GITHUB_REPOSITORY environment variable.", ] for log in expected_logs: assert log in result.output -def test_process_test_results_missing_step_summary(mocker, tmpdir): +def test_process_test_results_missing_ref(mocker, tmpdir): tmp_file = tmpdir.mkdir("folder").join("summary.txt") mocker.patch.dict( os.environ, { "GITHUB_REPOSITORY": "fake/repo", - "GITHUB_REF": "pull/fake/pull", }, ) - if "GITHUB_STEP_SUMMARY" in os.environ: - del os.environ["GITHUB_STEP_SUMMARY"] + + if "GITHUB_REF" in os.environ: + del os.environ["GITHUB_REF"] mocked_post = mocker.patch( "codecov_cli.commands.process_test_results.send_post_request", return_value=RequestResult( @@ -194,7 +309,7 @@ def test_process_test_results_missing_step_summary(mocker, tmpdir): cli, [ "process-test-results", - "--provider-token", + "--github-token", "whatever", "--file", "samples/junit.xml", @@ -206,7 +321,7 @@ def test_process_test_results_missing_step_summary(mocker, tmpdir): assert result.exit_code == 1 expected_logs = [ "ci service found", - "Error: Error getting step summary file path from environment. Can't find GITHUB_STEP_SUMMARY environment variable.", + "Error: Error getting PR number from environment. Can't find GITHUB_REF environment variable.", ] for log in expected_logs: assert log in result.output From 0f2ea9ce52305af4ede7647c1ad77a8bdf322880 Mon Sep 17 00:00:00 2001 From: Codecov Releaser Date: Fri, 11 Oct 2024 13:50:43 -0400 Subject: [PATCH 09/30] Prepare release 0.7.6 (#526) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e3e6a86f..f9e604bc 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name="codecov-cli", - version="0.7.5", + version="0.7.6", packages=find_packages(exclude=["contrib", "docs", "tests*"]), description="Codecov Command Line Interface", long_description=long_description, From 6efe50c4d4c4b51a9c7799e48c1042288ba64378 Mon Sep 17 00:00:00 2001 From: Trent Schmidt Date: Fri, 11 Oct 2024 14:39:34 -0400 Subject: [PATCH 10/30] Adding small ci job (#527) --- .github/workflows/ci-job.yml | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/ci-job.yml diff --git a/.github/workflows/ci-job.yml b/.github/workflows/ci-job.yml new file mode 100644 index 00000000..2379156c --- /dev/null +++ b/.github/workflows/ci-job.yml @@ -0,0 +1,41 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: CLI CI Job + +on: + pull_request: + push: + branches: + - main + +jobs: + build-test-upload: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 2 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + python -m pip install -e . + pip install -r tests/requirements.txt + - name: Test with pytest + run: | + pytest --cov --junitxml=3.12junit.xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage.xml + flags: python3.12 + fail_ci_if_error: true + verbose: true From 3333bcbb47d71525df657eebd20b72522d552aa3 Mon Sep 17 00:00:00 2001 From: Tom Hu <88201630+thomasrockhu-codecov@users.noreply.github.com> Date: Thu, 17 Oct 2024 08:37:48 -0500 Subject: [PATCH 11/30] feat: add gcov capabilities (#536) * first pass * fix: update tests --- codecov_cli/commands/upload.py | 24 ++++++ codecov_cli/commands/upload_process.py | 74 ++++++++++--------- codecov_cli/plugins/__init__.py | 22 ++++-- codecov_cli/plugins/gcov.py | 24 +++--- codecov_cli/services/upload/__init__.py | 22 +++++- .../services/upload/upload_collector.py | 2 + tests/commands/test_invoke_upload_process.py | 4 + tests/plugins/test_instantiation.py | 19 +++-- .../services/upload/test_upload_collector.py | 14 ++-- tests/services/upload/test_upload_service.py | 64 +++++++++++----- 10 files changed, 187 insertions(+), 82 deletions(-) diff --git a/codecov_cli/commands/upload.py b/codecov_cli/commands/upload.py index dfbfe431..e191fae0 100644 --- a/codecov_cli/commands/upload.py +++ b/codecov_cli/commands/upload.py @@ -178,6 +178,22 @@ def _turn_env_vars_into_dict(ctx, params, value): "--network-prefix", help="Specify a prefix on files listed in the network section of the Codecov report. Useful to help resolve path fixing", ), + click.option( + "--gcov-args", + help="Extra arguments to pass to gcov", + ), + click.option( + "--gcov-ignore", + help="Paths to ignore during gcov gathering", + ), + click.option( + "--gcov-include", + help="Paths to include during gcov gathering", + ), + click.option( + "--gcov-executable", + help="gcov executable to run. Defaults to 'gcov'", + ), ] @@ -207,6 +223,10 @@ def do_upload( files_search_explicitly_listed_files: typing.List[pathlib.Path], files_search_root_folder: pathlib.Path, flags: typing.List[str], + gcov_args: typing.Optional[str], + gcov_executable: typing.Optional[str], + gcov_ignore: typing.Optional[str], + gcov_include: typing.Optional[str], git_service: typing.Optional[str], handle_no_reports_found: bool, job_code: typing.Optional[str], @@ -251,6 +271,10 @@ def do_upload( files_search_explicitly_listed_files=list(files_search_explicitly_listed_files), files_search_root_folder=files_search_root_folder, flags=flags, + gcov_args=gcov_args, + gcov_executable=gcov_executable, + gcov_ignore=gcov_ignore, + gcov_include=gcov_include, git_service=git_service, handle_no_reports_found=handle_no_reports_found, job_code=job_code, diff --git a/codecov_cli/commands/upload_process.py b/codecov_cli/commands/upload_process.py index e6efecc2..b30cdb27 100644 --- a/codecov_cli/commands/upload_process.py +++ b/codecov_cli/commands/upload_process.py @@ -25,34 +25,38 @@ @click.pass_context def upload_process( ctx: CommandContext, - commit_sha: str, - report_code: str, + branch: typing.Optional[str], build_code: typing.Optional[str], build_url: typing.Optional[str], - job_code: typing.Optional[str], + commit_sha: str, + disable_file_fixes: bool, + disable_search: bool, + dry_run: bool, env_vars: typing.Dict[str, str], + fail_on_error: bool, + files_search_exclude_folders: typing.List[pathlib.Path], + files_search_explicitly_listed_files: typing.List[pathlib.Path], + files_search_root_folder: pathlib.Path, flags: typing.List[str], + gcov_args: typing.Optional[str], + gcov_executable: typing.Optional[str], + gcov_ignore: typing.Optional[str], + gcov_include: typing.Optional[str], + git_service: typing.Optional[str], + handle_no_reports_found: bool, + job_code: typing.Optional[str], name: typing.Optional[str], network_filter: typing.Optional[str], network_prefix: typing.Optional[str], network_root_folder: pathlib.Path, - files_search_root_folder: pathlib.Path, - files_search_exclude_folders: typing.List[pathlib.Path], - files_search_explicitly_listed_files: typing.List[pathlib.Path], - disable_search: bool, - disable_file_fixes: bool, - token: typing.Optional[str], + parent_sha: typing.Optional[str], plugin_names: typing.List[str], - branch: typing.Optional[str], - slug: typing.Optional[str], pull_request_number: typing.Optional[str], - use_legacy_uploader: bool, - fail_on_error: bool, - dry_run: bool, - git_service: typing.Optional[str], - parent_sha: typing.Optional[str], - handle_no_reports_found: bool, + report_code: str, report_type: str, + slug: typing.Optional[str], + token: typing.Optional[str], + use_legacy_uploader: bool, ): args = get_cli_args(ctx) logger.debug( @@ -85,31 +89,35 @@ def upload_process( ) ctx.invoke( do_upload, - commit_sha=commit_sha, - report_code=report_code, + branch=branch, build_code=build_code, build_url=build_url, - job_code=job_code, + commit_sha=commit_sha, + disable_file_fixes=disable_file_fixes, + disable_search=disable_search, + dry_run=dry_run, env_vars=env_vars, + fail_on_error=fail_on_error, + files_search_exclude_folders=files_search_exclude_folders, + files_search_explicitly_listed_files=files_search_explicitly_listed_files, + files_search_root_folder=files_search_root_folder, flags=flags, + gcov_args=gcov_args, + gcov_executable=gcov_executable, + gcov_ignore=gcov_ignore, + gcov_include=gcov_include, + git_service=git_service, + handle_no_reports_found=handle_no_reports_found, + job_code=job_code, name=name, network_filter=network_filter, network_prefix=network_prefix, network_root_folder=network_root_folder, - files_search_root_folder=files_search_root_folder, - files_search_exclude_folders=files_search_exclude_folders, - files_search_explicitly_listed_files=files_search_explicitly_listed_files, - disable_search=disable_search, - token=token, plugin_names=plugin_names, - branch=branch, - slug=slug, pull_request_number=pull_request_number, - use_legacy_uploader=use_legacy_uploader, - fail_on_error=fail_on_error, - dry_run=dry_run, - git_service=git_service, - handle_no_reports_found=handle_no_reports_found, - disable_file_fixes=disable_file_fixes, + report_code=report_code, report_type=report_type, + slug=slug, + token=token, + use_legacy_uploader=use_legacy_uploader, ) diff --git a/codecov_cli/plugins/__init__.py b/codecov_cli/plugins/__init__.py index 2ce8cb6d..df17c8ab 100644 --- a/codecov_cli/plugins/__init__.py +++ b/codecov_cli/plugins/__init__.py @@ -17,12 +17,17 @@ def run_preparation(self, collector): pass -def select_preparation_plugins(cli_config: typing.Dict, plugin_names: typing.List[str]): - plugins = [_get_plugin(cli_config, p) for p in plugin_names] +def select_preparation_plugins( + cli_config: typing.Dict, plugin_names: typing.List[str], plugin_config: typing.Dict +): + plugins = [_get_plugin(cli_config, p, plugin_config) for p in plugin_names] logger.debug( "Selected preparation plugins", extra=dict( - extra_log_attributes=dict(selected_plugins=list(map(type, plugins))) + extra_log_attributes=dict( + selected_plugins=list(map(type, plugins)), + cli_config=cli_config, + ) ), ) return plugins @@ -59,11 +64,18 @@ def _load_plugin_from_yaml(plugin_dict: typing.Dict): return NoopPlugin() -def _get_plugin(cli_config, plugin_name): +def _get_plugin(cli_config, plugin_name, plugin_config): if plugin_name == "noop": return NoopPlugin() if plugin_name == "gcov": - return GcovPlugin() + return GcovPlugin( + plugin_config.get("project_root", None), + plugin_config.get("folders_to_ignore", None), + plugin_config.get("gcov_executable", "gcov"), + plugin_config.get("gcov_include", None), + plugin_config.get("gcov_ignore", None), + plugin_config.get("gcov_args", None), + ) if plugin_name == "pycoverage": config = cli_config.get("plugins", {}).get("pycoverage", {}) return Pycoverage(config) diff --git a/codecov_cli/plugins/gcov.py b/codecov_cli/plugins/gcov.py index d807ab6b..668095b2 100644 --- a/codecov_cli/plugins/gcov.py +++ b/codecov_cli/plugins/gcov.py @@ -15,24 +15,26 @@ class GcovPlugin(object): def __init__( self, project_root: typing.Optional[pathlib.Path] = None, + folders_to_ignore: typing.Optional[typing.List[str]] = None, + executable: typing.Optional[str] = "gcov", patterns_to_include: typing.Optional[typing.List[str]] = None, patterns_to_ignore: typing.Optional[typing.List[str]] = None, - folders_to_ignore: typing.Optional[typing.List[str]] = None, extra_arguments: typing.Optional[typing.List[str]] = None, ): - self.project_root = project_root or pathlib.Path(os.getcwd()) - self.patterns_to_include = patterns_to_include or [] - self.patterns_to_ignore = patterns_to_ignore or [] - self.folders_to_ignore = folders_to_ignore or [] + self.executable = executable or "gcov" self.extra_arguments = extra_arguments or [] + self.folders_to_ignore = folders_to_ignore or [] + self.patterns_to_ignore = patterns_to_ignore or [] + self.patterns_to_include = patterns_to_include or [] + self.project_root = project_root or pathlib.Path(os.getcwd()) def run_preparation(self, collector) -> PreparationPluginReturn: logger.debug( - "Running gcov plugin...", + f"Running {self.executable} plugin...", ) - if shutil.which("gcov") is None: - logger.warning("gcov is not installed or can't be found.") + if shutil.which(self.executable) is None: + logger.warning(f"{self.executable} is not installed or can't be found.") return filename_include_regex = globs_to_regex(["*.gcno", *self.patterns_to_include]) @@ -49,15 +51,15 @@ def run_preparation(self, collector) -> PreparationPluginReturn: ] if not matched_paths: - logger.warning("No gcov data found.") + logger.warning(f"No {self.executable} data found.") return - logger.warning("Running gcov on the following list of files:") + logger.warning(f"Running {self.executable} on the following list of files:") for path in matched_paths: logger.warning(path) s = subprocess.run( - ["gcov", "-pb", *self.extra_arguments, *matched_paths], + [self.executable, "-pb", *self.extra_arguments, *matched_paths], cwd=self.project_root, capture_output=True, ) diff --git a/codecov_cli/services/upload/__init__.py b/codecov_cli/services/upload/__init__.py index 411c820c..c9f2b779 100644 --- a/codecov_cli/services/upload/__init__.py +++ b/codecov_cli/services/upload/__init__.py @@ -40,6 +40,10 @@ def do_upload_logic( files_search_explicitly_listed_files: typing.List[Path], files_search_root_folder: Path, flags: typing.List[str], + gcov_args: typing.Optional[str], + gcov_executable: typing.Optional[str], + gcov_ignore: typing.Optional[str], + gcov_include: typing.Optional[str], git_service: typing.Optional[str], handle_no_reports_found: bool = False, job_code: typing.Optional[str], @@ -55,8 +59,18 @@ def do_upload_logic( upload_file_type: str = "coverage", use_legacy_uploader: bool = False, ): + plugin_config = { + "folders_to_ignore": files_search_exclude_folders, + "gcov_args": gcov_args, + "gcov_executable": gcov_executable, + "gcov_ignore": gcov_ignore, + "gcov_include": gcov_include, + "project_root": files_search_root_folder, + } if upload_file_type == "coverage": - preparation_plugins = select_preparation_plugins(cli_config, plugin_names) + preparation_plugins = select_preparation_plugins( + cli_config, plugin_names, plugin_config + ) elif upload_file_type == "test_results": preparation_plugins = [] file_selector = select_file_finder( @@ -73,7 +87,11 @@ def do_upload_logic( network_root_folder=network_root_folder, ) collector = UploadCollector( - preparation_plugins, network_finder, file_selector, disable_file_fixes + preparation_plugins, + network_finder, + file_selector, + disable_file_fixes, + plugin_config, ) try: upload_data = collector.generate_upload_data(upload_file_type) diff --git a/codecov_cli/services/upload/upload_collector.py b/codecov_cli/services/upload/upload_collector.py index 47a99670..53dcd860 100644 --- a/codecov_cli/services/upload/upload_collector.py +++ b/codecov_cli/services/upload/upload_collector.py @@ -29,12 +29,14 @@ def __init__( preparation_plugins: typing.List[PreparationPluginInterface], network_finder: NetworkFinder, file_finder: FileFinder, + plugin_config: dict, disable_file_fixes: bool = False, ): self.preparation_plugins = preparation_plugins self.network_finder = network_finder self.file_finder = file_finder self.disable_file_fixes = disable_file_fixes + self.plugin_config = plugin_config def _produce_file_fixes( self, files: typing.List[str] diff --git a/tests/commands/test_invoke_upload_process.py b/tests/commands/test_invoke_upload_process.py index ec3981b2..8f6fcbdc 100644 --- a/tests/commands/test_invoke_upload_process.py +++ b/tests/commands/test_invoke_upload_process.py @@ -126,6 +126,10 @@ def test_upload_process_options(mocker): " --network-prefix TEXT Specify a prefix on files listed in the", " network section of the Codecov report. Useful", " to help resolve path fixing", + " --gcov-args TEXT Extra arguments to pass to gcov", + " --gcov-ignore TEXT Paths to ignore during gcov gathering", + " --gcov-include TEXT Paths to include during gcov gathering", + " --gcov-executable TEXT gcov executable to run. Defaults to 'gcov'", " --parent-sha TEXT SHA (with 40 chars) of what should be the", " parent of this commit", " -h, --help Show this message and exit.", diff --git a/tests/plugins/test_instantiation.py b/tests/plugins/test_instantiation.py index caaa3516..97e5b68a 100644 --- a/tests/plugins/test_instantiation.py +++ b/tests/plugins/test_instantiation.py @@ -106,40 +106,46 @@ def __init__(self): def test_get_plugin_gcov(): - res = _get_plugin({}, "gcov") + res = _get_plugin({}, "gcov", {}) + assert isinstance(res, GcovPlugin) + + res = _get_plugin({}, "gcov", { + 'gcov_executable': 'lcov', + }) assert isinstance(res, GcovPlugin) def test_get_plugin_xcode(): - res = _get_plugin({}, "xcode") + res = _get_plugin({}, "xcode", {}) assert isinstance(res, XcodePlugin) def test_get_plugin_noop(): - res = _get_plugin({}, "noop") + res = _get_plugin({}, "noop", {}) assert isinstance(res, NoopPlugin) def test_get_plugin_pycoverage(): - res = _get_plugin({}, "pycoverage") + res = _get_plugin({}, "pycoverage", {}) assert isinstance(res, Pycoverage) assert res.config == PycoverageConfig() assert res.config.report_type == "xml" pycoverage_config = {"project_root": "project/root", "report_type": "json"} - res = _get_plugin({"plugins": {"pycoverage": pycoverage_config}}, "pycoverage") + res = _get_plugin({"plugins": {"pycoverage": pycoverage_config}}, "pycoverage", {}) assert isinstance(res, Pycoverage) assert res.config == PycoverageConfig(pycoverage_config) assert res.config.report_type == "json" def test_get_plugin_compress_pycoverage(): - res = _get_plugin({}, "compress-pycoverage") + res = _get_plugin({}, "compress-pycoverage", {}) assert isinstance(res, CompressPycoverageContexts) res = _get_plugin( {"plugins": {"compress-pycoverage": {"file_to_compress": "something.json"}}}, "compress-pycoverage", + {}, ) assert isinstance(res, CompressPycoverageContexts) assert str(res.file_to_compress) == "something.json" @@ -180,6 +186,7 @@ def __init__(self, banana=None): } }, ["gcov", "something", "otherthing", "second", "lalalala"], + {} ) assert len(res) == 5 assert isinstance(res[0], GcovPlugin) diff --git a/tests/services/upload/test_upload_collector.py b/tests/services/upload/test_upload_collector.py index a433b904..39124d0e 100644 --- a/tests/services/upload/test_upload_collector.py +++ b/tests/services/upload/test_upload_collector.py @@ -11,7 +11,7 @@ def test_fix_kt_files(): kt_file = Path("tests/data/files_to_fix_examples/sample.kt") - col = UploadCollector(None, None, None) + col = UploadCollector(None, None, None, None) fixes = col._produce_file_fixes([kt_file]) @@ -31,7 +31,7 @@ def test_fix_kt_files(): def test_fix_go_files(): go_file = Path("tests/data/files_to_fix_examples/sample.go") - col = UploadCollector(None, None, None) + col = UploadCollector(None, None, None, None) fixes = col._produce_file_fixes([go_file]) @@ -57,7 +57,7 @@ def test_fix_bad_encoding_files(mock_open): mock_open.side_effect = UnicodeDecodeError("", bytes(), 0, 0, "") go_file = Path("tests/data/files_to_fix_examples/bad_encoding.go") - col = UploadCollector(None, None, None) + col = UploadCollector(None, None, None, None) fixes = col._produce_file_fixes([go_file]) assert len(fixes) == 1 @@ -70,7 +70,7 @@ def test_fix_bad_encoding_files(mock_open): def test_fix_php_files(): php_file = Path("tests/data/files_to_fix_examples/sample.php") - col = UploadCollector(None, None, None) + col = UploadCollector(None, None, None, None) fixes = col._produce_file_fixes([php_file]) @@ -85,7 +85,7 @@ def test_fix_php_files(): def test_fix_for_cpp_swift_vala(tmp_path): cpp_file = Path("tests/data/files_to_fix_examples/sample.cpp") - col = UploadCollector(None, None, None) + col = UploadCollector(None, None, None, None) fixes = col._produce_file_fixes([cpp_file]) @@ -107,7 +107,7 @@ def test_fix_for_cpp_swift_vala(tmp_path): def test_fix_when_disabled_fixes(tmp_path): cpp_file = Path("tests/data/files_to_fix_examples/sample.cpp") - col = UploadCollector(None, None, None, True) + col = UploadCollector(None, None, None, None, True) fixes = col._produce_file_fixes([cpp_file]) @@ -166,7 +166,7 @@ def test_generate_upload_data(tmp_path): network_finder = NetworkFinder(GitVersioningSystem(), None, None, None) - collector = UploadCollector([], network_finder, file_finder) + collector = UploadCollector([], network_finder, file_finder, None) res = collector.generate_upload_data() diff --git a/tests/services/upload/test_upload_service.py b/tests/services/upload/test_upload_service.py index 081ee2bb..b6beca3f 100644 --- a/tests/services/upload/test_upload_service.py +++ b/tests/services/upload/test_upload_service.py @@ -55,6 +55,10 @@ def test_do_upload_logic_happy_path_legacy_uploader(mocker): job_code="job_code", env_vars=None, flags=None, + gcov_args=None, + gcov_executable=None, + gcov_ignore=None, + gcov_include=None, name="name", network_filter=None, network_prefix=None, @@ -81,7 +85,7 @@ def test_do_upload_logic_happy_path_legacy_uploader(mocker): assert res == LegacyUploadSender.send_upload_data.return_value mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"] + cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None} ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") mock_select_network_finder.assert_called_with( @@ -152,6 +156,10 @@ def test_do_upload_logic_happy_path(mocker): job_code="job_code", env_vars=None, flags=None, + gcov_args=None, + gcov_executable=None, + gcov_ignore=None, + gcov_include=None, name="name", network_filter=None, network_prefix=None, @@ -176,7 +184,7 @@ def test_do_upload_logic_happy_path(mocker): assert res == UploadSender.send_upload_data.return_value mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"] + cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None} ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") mock_select_network_finder.assert_called_with( @@ -243,6 +251,10 @@ def test_do_upload_logic_dry_run(mocker): job_code="job_code", env_vars=None, flags=None, + gcov_args=None, + gcov_executable=None, + gcov_ignore=None, + gcov_include=None, name="name", network_filter=None, network_prefix=None, @@ -270,7 +282,7 @@ def test_do_upload_logic_dry_run(mocker): assert mock_generate_upload_data.call_count == 1 assert mock_send_upload_data.call_count == 0 mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"] + cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None} ) assert out_bytes == [ ("info", "dry-run option activated. NOT sending data to Codecov."), @@ -303,30 +315,34 @@ def test_do_upload_logic_verbose(mocker, use_verbose_option): cli_config, versioning_system, ci_adapter, - upload_file_type="coverage", - commit_sha="commit_sha", - report_code="report_code", + branch="branch", build_code="build_code", build_url="build_url", - job_code="job_code", + commit_sha="commit_sha", + dry_run=True, + enterprise_url=None, env_vars=None, + files_search_exclude_folders=None, + files_search_explicitly_listed_files=None, + files_search_root_folder=None, flags=None, + gcov_args=None, + gcov_executable=None, + gcov_ignore=None, + gcov_include=None, + git_service="git_service", + job_code="job_code", name="name", network_filter=None, network_prefix=None, network_root_folder=None, - files_search_root_folder=None, - files_search_exclude_folders=None, - files_search_explicitly_listed_files=None, plugin_names=["first_plugin", "another", "forth"], - token="token", - branch="branch", + pull_request_number="pr", + report_code="report_code", slug="slug", + token="token", + upload_file_type="coverage", use_legacy_uploader=True, - pull_request_number="pr", - dry_run=True, - git_service="git_service", - enterprise_url=None, ) out_bytes = parse_outstreams_into_log_lines(outstreams[0].getvalue()) assert out_bytes == [ @@ -388,6 +404,10 @@ def side_effect(*args, **kwargs): job_code="job_code", env_vars=None, flags=None, + gcov_args=None, + gcov_executable=None, + gcov_ignore=None, + gcov_include=None, name="name", network_filter=None, network_prefix=None, @@ -418,7 +438,7 @@ def side_effect(*args, **kwargs): text="No coverage reports found. Triggering notificaions without uploading.", ) mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"] + cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None} ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") mock_select_network_finder.assert_called_with( @@ -476,6 +496,10 @@ def side_effect(*args, **kwargs): job_code="job_code", env_vars=None, flags=None, + gcov_args=None, + gcov_executable=None, + gcov_ignore=None, + gcov_include=None, name="name", network_filter=None, network_prefix=None, @@ -497,7 +521,7 @@ def side_effect(*args, **kwargs): == "No coverage reports found. Please make sure you're generating reports successfully." ) mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"] + cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None} ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") mock_select_network_finder.assert_called_with( @@ -548,6 +572,10 @@ def test_do_upload_logic_happy_path_test_results(mocker): job_code="job_code", env_vars=None, flags=None, + gcov_args=None, + gcov_executable=None, + gcov_ignore=None, + gcov_include=None, name="name", network_filter="some_dir", network_prefix="hello/", From 5e6fee000be1916f246e08f3f8f3eeb9c2ecae23 Mon Sep 17 00:00:00 2001 From: Tom Hu <88201630+thomasrockhu-codecov@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:48:09 -0500 Subject: [PATCH 12/30] feat: add xcode functionality (#537) * first pass * fix: update tests * feat: allow for swift coverage * fix: rebase and tests --- codecov_cli/commands/upload.py | 6 +++ codecov_cli/commands/upload_process.py | 2 + codecov_cli/plugins/__init__.py | 4 +- codecov_cli/plugins/xcode.py | 9 ++-- codecov_cli/services/upload/__init__.py | 2 + tests/commands/test_invoke_upload_process.py | 1 + tests/services/upload/test_upload_service.py | 43 ++++++++++++-------- 7 files changed, 44 insertions(+), 23 deletions(-) diff --git a/codecov_cli/commands/upload.py b/codecov_cli/commands/upload.py index e191fae0..2b78f687 100644 --- a/codecov_cli/commands/upload.py +++ b/codecov_cli/commands/upload.py @@ -194,6 +194,10 @@ def _turn_env_vars_into_dict(ctx, params, value): "--gcov-executable", help="gcov executable to run. Defaults to 'gcov'", ), + click.option( + "--swift-project", + help="Specify the swift project", + ), ] @@ -238,6 +242,7 @@ def do_upload( pull_request_number: typing.Optional[str], report_type: str, slug: typing.Optional[str], + swift_project: typing.Optional[str], token: typing.Optional[str], use_legacy_uploader: bool, ): @@ -286,6 +291,7 @@ def do_upload( pull_request_number=pull_request_number, report_code=report_code, slug=slug, + swift_project=swift_project, token=token, upload_file_type=report_type, use_legacy_uploader=use_legacy_uploader, diff --git a/codecov_cli/commands/upload_process.py b/codecov_cli/commands/upload_process.py index b30cdb27..1ee77eca 100644 --- a/codecov_cli/commands/upload_process.py +++ b/codecov_cli/commands/upload_process.py @@ -55,6 +55,7 @@ def upload_process( report_code: str, report_type: str, slug: typing.Optional[str], + swift_project: typing.Optional[str], token: typing.Optional[str], use_legacy_uploader: bool, ): @@ -118,6 +119,7 @@ def upload_process( report_code=report_code, report_type=report_type, slug=slug, + swift_project=swift_project, token=token, use_legacy_uploader=use_legacy_uploader, ) diff --git a/codecov_cli/plugins/__init__.py b/codecov_cli/plugins/__init__.py index df17c8ab..db7a8a4d 100644 --- a/codecov_cli/plugins/__init__.py +++ b/codecov_cli/plugins/__init__.py @@ -80,7 +80,9 @@ def _get_plugin(cli_config, plugin_name, plugin_config): config = cli_config.get("plugins", {}).get("pycoverage", {}) return Pycoverage(config) if plugin_name == "xcode": - return XcodePlugin() + return XcodePlugin( + plugin_config.get("swift_project", None), + ) if plugin_name == "compress-pycoverage": config = cli_config.get("plugins", {}).get("compress-pycoverage", {}) return CompressPycoverageContexts(config) diff --git a/codecov_cli/plugins/xcode.py b/codecov_cli/plugins/xcode.py index a0dae751..d8e4d0db 100644 --- a/codecov_cli/plugins/xcode.py +++ b/codecov_cli/plugins/xcode.py @@ -16,12 +16,13 @@ class XcodePlugin(object): def __init__( self, + app_name: typing.Optional[str] = None, derived_data_folder: typing.Optional[pathlib.Path] = None, - app_name: typing.Optional[pathlib.Path] = None, ): - self.derived_data_folder = pathlib.Path( - derived_data_folder or "~/Library/Developer/Xcode/DerivedData" - ).expanduser() + self.derived_data_folder = ( + derived_data_folder + or pathlib.Path("~/Library/Developer/Xcode/DerivedData").expanduser() + ) # this is to speed up processing and to build reports for the project being tested, # if empty the plugin will build reports for every xcode project it finds diff --git a/codecov_cli/services/upload/__init__.py b/codecov_cli/services/upload/__init__.py index c9f2b779..ae1a22a2 100644 --- a/codecov_cli/services/upload/__init__.py +++ b/codecov_cli/services/upload/__init__.py @@ -55,6 +55,7 @@ def do_upload_logic( pull_request_number: typing.Optional[str], report_code: str, slug: typing.Optional[str], + swift_project: typing.Optional[str], token: str, upload_file_type: str = "coverage", use_legacy_uploader: bool = False, @@ -66,6 +67,7 @@ def do_upload_logic( "gcov_ignore": gcov_ignore, "gcov_include": gcov_include, "project_root": files_search_root_folder, + "swift_project": swift_project, } if upload_file_type == "coverage": preparation_plugins = select_preparation_plugins( diff --git a/tests/commands/test_invoke_upload_process.py b/tests/commands/test_invoke_upload_process.py index 8f6fcbdc..59f70851 100644 --- a/tests/commands/test_invoke_upload_process.py +++ b/tests/commands/test_invoke_upload_process.py @@ -130,6 +130,7 @@ def test_upload_process_options(mocker): " --gcov-ignore TEXT Paths to ignore during gcov gathering", " --gcov-include TEXT Paths to include during gcov gathering", " --gcov-executable TEXT gcov executable to run. Defaults to 'gcov'", + " --swift-project TEXT Specify the swift project", " --parent-sha TEXT SHA (with 40 chars) of what should be the", " parent of this commit", " -h, --help Show this message and exit.", diff --git a/tests/services/upload/test_upload_service.py b/tests/services/upload/test_upload_service.py index b6beca3f..7c33aba2 100644 --- a/tests/services/upload/test_upload_service.py +++ b/tests/services/upload/test_upload_service.py @@ -71,6 +71,7 @@ def test_do_upload_logic_happy_path_legacy_uploader(mocker): branch="branch", use_legacy_uploader=True, slug="slug", + swift_project="App", pull_request_number="pr", git_service="git_service", enterprise_url=None, @@ -85,7 +86,7 @@ def test_do_upload_logic_happy_path_legacy_uploader(mocker): assert res == LegacyUploadSender.send_upload_data.return_value mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None} + cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None, 'swift_project': 'App'} ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") mock_select_network_finder.assert_called_with( @@ -171,6 +172,7 @@ def test_do_upload_logic_happy_path(mocker): token="token", branch="branch", slug="slug", + swift_project="App", pull_request_number="pr", git_service="git_service", enterprise_url=None, @@ -184,7 +186,7 @@ def test_do_upload_logic_happy_path(mocker): assert res == UploadSender.send_upload_data.return_value mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None} + cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None, 'swift_project': 'App'} ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") mock_select_network_finder.assert_called_with( @@ -266,6 +268,7 @@ def test_do_upload_logic_dry_run(mocker): token="token", branch="branch", slug="slug", + swift_project="App", pull_request_number="pr", dry_run=True, git_service="git_service", @@ -282,7 +285,7 @@ def test_do_upload_logic_dry_run(mocker): assert mock_generate_upload_data.call_count == 1 assert mock_send_upload_data.call_count == 0 mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None} + cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None, 'swift_project': 'App'} ) assert out_bytes == [ ("info", "dry-run option activated. NOT sending data to Codecov."), @@ -340,6 +343,7 @@ def test_do_upload_logic_verbose(mocker, use_verbose_option): pull_request_number="pr", report_code="report_code", slug="slug", + swift_project="App", token="token", upload_file_type="coverage", use_legacy_uploader=True, @@ -419,6 +423,7 @@ def side_effect(*args, **kwargs): token="token", branch="branch", slug="slug", + swift_project="App", pull_request_number="pr", git_service="git_service", enterprise_url=None, @@ -438,7 +443,7 @@ def side_effect(*args, **kwargs): text="No coverage reports found. Triggering notificaions without uploading.", ) mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None} + cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None, 'swift_project': 'App'} ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") mock_select_network_finder.assert_called_with( @@ -511,6 +516,7 @@ def side_effect(*args, **kwargs): token="token", branch="branch", slug="slug", + swift_project="App", pull_request_number="pr", git_service="git_service", enterprise_url=None, @@ -521,7 +527,7 @@ def side_effect(*args, **kwargs): == "No coverage reports found. Please make sure you're generating reports successfully." ) mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None} + cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None, 'swift_project': 'App'} ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") mock_select_network_finder.assert_called_with( @@ -564,33 +570,34 @@ def test_do_upload_logic_happy_path_test_results(mocker): cli_config, versioning_system, ci_adapter, - upload_file_type="test_results", - commit_sha="commit_sha", - report_code="report_code", + args={"args": "fake_args"}, + branch="branch", build_code="build_code", build_url="build_url", - job_code="job_code", + commit_sha="commit_sha", + enterprise_url=None, env_vars=None, + files_search_exclude_folders=None, + files_search_explicitly_listed_files=None, + files_search_root_folder=None, flags=None, gcov_args=None, gcov_executable=None, gcov_ignore=None, gcov_include=None, + git_service="git_service", + job_code="job_code", name="name", network_filter="some_dir", network_prefix="hello/", network_root_folder="root/", - files_search_root_folder=None, - files_search_exclude_folders=None, - files_search_explicitly_listed_files=None, plugin_names=["first_plugin", "another", "forth"], - token="token", - branch="branch", - slug="slug", pull_request_number="pr", - git_service="git_service", - enterprise_url=None, - args={"args": "fake_args"}, + report_code="report_code", + slug="slug", + swift_project="App", + token="token", + upload_file_type="test_results", ) out_bytes = parse_outstreams_into_log_lines(outstreams[0].getvalue()) assert out_bytes == [ From 08745b94524bb789ad8df39980f6b8ebc45d4a51 Mon Sep 17 00:00:00 2001 From: Codecov Releaser Date: Thu, 17 Oct 2024 12:02:20 -0400 Subject: [PATCH 13/30] Prepare release 0.8.0 (#538) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f9e604bc..b1c59f32 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name="codecov-cli", - version="0.7.6", + version="0.8.0", packages=find_packages(exclude=["contrib", "docs", "tests*"]), description="Codecov Command Line Interface", long_description=long_description, From d5ef60060b72f29965868098644fa3bd275aca87 Mon Sep 17 00:00:00 2001 From: Tom Hu <88201630+thomasrockhu-codecov@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:26:48 -0500 Subject: [PATCH 14/30] fix: move to ingest.codecov.io (#542) * fix: move to ingest.codecov.io * fix: update tests * fix: lint * fix: really lint --- Makefile | 2 +- codecov_cli/helpers/config.py | 1 + codecov_cli/services/commit/__init__.py | 4 +- codecov_cli/services/report/__init__.py | 4 +- codecov_cli/services/upload/upload_sender.py | 4 +- tests/commands/test_process_test_results.py | 1 + tests/helpers/test_upload_sender.py | 10 ++-- tests/plugins/test_instantiation.py | 12 ++-- tests/services/commit/test_commit_service.py | 2 +- tests/services/upload/test_upload_service.py | 60 ++++++++++++++++++-- 10 files changed, 78 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index f70eab15..aed79ccb 100644 --- a/Makefile +++ b/Makefile @@ -16,4 +16,4 @@ else @echo "Tagging new release ${version}" git tag -a ${version} -m "Autogenerated release tag for codecov-cli" git push origin ${version} -endif \ No newline at end of file +endif diff --git a/codecov_cli/helpers/config.py b/codecov_cli/helpers/config.py index 870ac525..2c87ded2 100644 --- a/codecov_cli/helpers/config.py +++ b/codecov_cli/helpers/config.py @@ -9,6 +9,7 @@ logger = logging.getLogger("codecovcli") CODECOV_API_URL = "https://api.codecov.io" +CODECOV_INGEST_URL = "https://ingest.codecov.io" LEGACY_CODECOV_API_URL = "https://codecov.io" # Relative to the project root diff --git a/codecov_cli/services/commit/__init__.py b/codecov_cli/services/commit/__init__.py index 85c4d256..c142e7bf 100644 --- a/codecov_cli/services/commit/__init__.py +++ b/codecov_cli/services/commit/__init__.py @@ -2,7 +2,7 @@ import os import typing -from codecov_cli.helpers.config import CODECOV_API_URL +from codecov_cli.helpers.config import CODECOV_INGEST_URL from codecov_cli.helpers.encoder import decode_slug, encode_slug from codecov_cli.helpers.request import ( get_token_header_or_fail, @@ -71,7 +71,7 @@ def send_commit_data( "pullid": pr, } - upload_url = enterprise_url or CODECOV_API_URL + upload_url = enterprise_url or CODECOV_INGEST_URL url = f"{upload_url}/upload/{service}/{slug}/commits" return send_post_request( url=url, diff --git a/codecov_cli/services/report/__init__.py b/codecov_cli/services/report/__init__.py index a3734ad1..0b29c9d6 100644 --- a/codecov_cli/services/report/__init__.py +++ b/codecov_cli/services/report/__init__.py @@ -5,7 +5,7 @@ import requests from codecov_cli.helpers import request -from codecov_cli.helpers.config import CODECOV_API_URL +from codecov_cli.helpers.config import CODECOV_API_URL, CODECOV_INGEST_URL from codecov_cli.helpers.encoder import decode_slug, encode_slug from codecov_cli.helpers.request import ( get_token_header, @@ -60,7 +60,7 @@ def send_create_report_request( "code": code, } headers = get_token_header(token) - upload_url = enterprise_url or CODECOV_API_URL + upload_url = enterprise_url or CODECOV_INGEST_URL url = f"{upload_url}/upload/{service}/{encoded_slug}/commits/{commit_sha}/reports" return send_post_request(url=url, headers=headers, data=data) diff --git a/codecov_cli/services/upload/upload_sender.py b/codecov_cli/services/upload/upload_sender.py index 22f8924a..bfd5a07f 100644 --- a/codecov_cli/services/upload/upload_sender.py +++ b/codecov_cli/services/upload/upload_sender.py @@ -6,7 +6,7 @@ from typing import Any, Dict from codecov_cli import __version__ as codecov_cli_version -from codecov_cli.helpers.config import CODECOV_API_URL +from codecov_cli.helpers.config import CODECOV_INGEST_URL from codecov_cli.helpers.encoder import encode_slug from codecov_cli.helpers.request import ( get_token_header, @@ -56,7 +56,7 @@ def send_upload_data( } headers = get_token_header(token) encoded_slug = encode_slug(slug) - upload_url = enterprise_url or CODECOV_API_URL + upload_url = enterprise_url or CODECOV_INGEST_URL url, data = self.get_url_and_possibly_update_data( data, upload_file_type, diff --git a/tests/commands/test_process_test_results.py b/tests/commands/test_process_test_results.py index e583159d..9ef89adc 100644 --- a/tests/commands/test_process_test_results.py +++ b/tests/commands/test_process_test_results.py @@ -43,6 +43,7 @@ def test_process_test_results( # Ensure that there's an output assert result.output + def test_process_test_results_create_github_message( mocker, tmpdir, diff --git a/tests/helpers/test_upload_sender.py b/tests/helpers/test_upload_sender.py index 68dd840b..642e2a35 100644 --- a/tests/helpers/test_upload_sender.py +++ b/tests/helpers/test_upload_sender.py @@ -69,7 +69,7 @@ def mocked_legacy_upload_endpoint(mocked_responses): encoded_slug = encode_slug(named_upload_data["slug"]) resp = responses.Response( responses.POST, - f"https://api.codecov.io/upload/github/{encoded_slug}/commits/{random_sha}/reports/{named_upload_data['report_code']}/uploads", + f"https://ingest.codecov.io/upload/github/{encoded_slug}/commits/{random_sha}/reports/{named_upload_data['report_code']}/uploads", status=200, json={ "raw_upload_location": "https://puturl.com", @@ -84,7 +84,7 @@ def mocked_legacy_upload_endpoint(mocked_responses): def mocked_test_results_endpoint(mocked_responses): resp = responses.Response( responses.POST, - f"https://api.codecov.io/upload/test_results/v1", + f"https://ingest.codecov.io/upload/test_results/v1", status=200, json={ "raw_upload_location": "https://puturl.com", @@ -187,7 +187,7 @@ def test_upload_sender_post_called_with_right_parameters( assert response.get("url") == "https://app.codecov.io/commit-url" assert ( post_req_made.url - == f"https://api.codecov.io/upload/github/{encoded_slug}/commits/{random_sha}/reports/{named_upload_data['report_code']}/uploads" + == f"https://ingest.codecov.io/upload/github/{encoded_slug}/commits/{random_sha}/reports/{named_upload_data['report_code']}/uploads" ) assert ( post_req_made.headers.items() >= headers.items() @@ -217,7 +217,7 @@ def test_upload_sender_post_called_with_right_parameters_test_results( post_req_made = mocked_responses.calls[0].request response = json.loads(mocked_responses.calls[0].response.text) assert response.get("raw_upload_location") == "https://puturl.com" - assert post_req_made.url == "https://api.codecov.io/upload/test_results/v1" + assert post_req_made.url == "https://ingest.codecov.io/upload/test_results/v1" assert ( post_req_made.headers.items() >= headers.items() ) # test dict is a subset of the other @@ -254,7 +254,7 @@ def test_upload_sender_post_called_with_right_parameters_tokenless( assert response.get("url") == "https://app.codecov.io/commit-url" assert ( post_req_made.url - == f"https://api.codecov.io/upload/github/{encoded_slug}/commits/{random_sha}/reports/{named_upload_data['report_code']}/uploads" + == f"https://ingest.codecov.io/upload/github/{encoded_slug}/commits/{random_sha}/reports/{named_upload_data['report_code']}/uploads" ) assert ( post_req_made.headers.items() >= headers.items() diff --git a/tests/plugins/test_instantiation.py b/tests/plugins/test_instantiation.py index 97e5b68a..fdf3a842 100644 --- a/tests/plugins/test_instantiation.py +++ b/tests/plugins/test_instantiation.py @@ -109,9 +109,13 @@ def test_get_plugin_gcov(): res = _get_plugin({}, "gcov", {}) assert isinstance(res, GcovPlugin) - res = _get_plugin({}, "gcov", { - 'gcov_executable': 'lcov', - }) + res = _get_plugin( + {}, + "gcov", + { + "gcov_executable": "lcov", + }, + ) assert isinstance(res, GcovPlugin) @@ -186,7 +190,7 @@ def __init__(self, banana=None): } }, ["gcov", "something", "otherthing", "second", "lalalala"], - {} + {}, ) assert len(res) == 5 assert isinstance(res[0], GcovPlugin) diff --git a/tests/services/commit/test_commit_service.py b/tests/services/commit/test_commit_service.py index 3f4ae1fd..31ac3c02 100644 --- a/tests/services/commit/test_commit_service.py +++ b/tests/services/commit/test_commit_service.py @@ -168,7 +168,7 @@ def test_commit_sender_with_forked_repo(mocker): None, ) mocked_response.assert_called_with( - url="https://api.codecov.io/upload/github/codecov::::codecov-cli/commits", + url="https://ingest.codecov.io/upload/github/codecov::::codecov-cli/commits", data={ "branch": "user_forked_repo/codecov-cli:branch", "cli_args": None, diff --git a/tests/services/upload/test_upload_service.py b/tests/services/upload/test_upload_service.py index 7c33aba2..9a38a5f9 100644 --- a/tests/services/upload/test_upload_service.py +++ b/tests/services/upload/test_upload_service.py @@ -86,7 +86,17 @@ def test_do_upload_logic_happy_path_legacy_uploader(mocker): assert res == LegacyUploadSender.send_upload_data.return_value mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None, 'swift_project': 'App'} + cli_config, + ["first_plugin", "another", "forth"], + { + "folders_to_ignore": None, + "gcov_args": None, + "gcov_executable": None, + "gcov_ignore": None, + "gcov_include": None, + "project_root": None, + "swift_project": "App", + }, ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") mock_select_network_finder.assert_called_with( @@ -186,7 +196,17 @@ def test_do_upload_logic_happy_path(mocker): assert res == UploadSender.send_upload_data.return_value mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None, 'swift_project': 'App'} + cli_config, + ["first_plugin", "another", "forth"], + { + "folders_to_ignore": None, + "gcov_args": None, + "gcov_executable": None, + "gcov_ignore": None, + "gcov_include": None, + "project_root": None, + "swift_project": "App", + }, ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") mock_select_network_finder.assert_called_with( @@ -285,7 +305,17 @@ def test_do_upload_logic_dry_run(mocker): assert mock_generate_upload_data.call_count == 1 assert mock_send_upload_data.call_count == 0 mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None, 'swift_project': 'App'} + cli_config, + ["first_plugin", "another", "forth"], + { + "folders_to_ignore": None, + "gcov_args": None, + "gcov_executable": None, + "gcov_ignore": None, + "gcov_include": None, + "project_root": None, + "swift_project": "App", + }, ) assert out_bytes == [ ("info", "dry-run option activated. NOT sending data to Codecov."), @@ -443,7 +473,17 @@ def side_effect(*args, **kwargs): text="No coverage reports found. Triggering notificaions without uploading.", ) mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None, 'swift_project': 'App'} + cli_config, + ["first_plugin", "another", "forth"], + { + "folders_to_ignore": None, + "gcov_args": None, + "gcov_executable": None, + "gcov_ignore": None, + "gcov_include": None, + "project_root": None, + "swift_project": "App", + }, ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") mock_select_network_finder.assert_called_with( @@ -527,7 +567,17 @@ def side_effect(*args, **kwargs): == "No coverage reports found. Please make sure you're generating reports successfully." ) mock_select_preparation_plugins.assert_called_with( - cli_config, ["first_plugin", "another", "forth"], {'folders_to_ignore': None, 'gcov_args': None, 'gcov_executable': None, 'gcov_ignore': None, 'gcov_include': None, 'project_root': None, 'swift_project': 'App'} + cli_config, + ["first_plugin", "another", "forth"], + { + "folders_to_ignore": None, + "gcov_args": None, + "gcov_executable": None, + "gcov_ignore": None, + "gcov_include": None, + "project_root": None, + "swift_project": "App", + }, ) mock_select_file_finder.assert_called_with(None, None, None, False, "coverage") mock_select_network_finder.assert_called_with( From db6ab021b52dc6564910749e77aa622a79cbd964 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Mon, 28 Oct 2024 09:58:24 +0100 Subject: [PATCH 15/30] Document `empty-upload --force` flag (#543) --- README.md | 17 +++++++++-------- codecov_cli/helpers/request.py | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 457a2307..81c2b179 100644 --- a/README.md +++ b/README.md @@ -240,14 +240,15 @@ are ignored by codecov (including README and configuration files) `Usage: codecovcli empty-upload [OPTIONS]` -| Options | Description | usage -| :---: | :---: | :---: | - | -C, --sha, --commit-sha TEXT |Commit SHA (with 40 chars) | Required - | -t, --token TEXT |Codecov upload token |Required -| -r, --slug TEXT | owner/repo slug used instead of the private repo token in Self-hosted | Optional -| -Z, --fail-on-error | Exit with non-zero code in case of error|Optional -| --git-service| Options: github, gitlab, bitbucket, github_enterprise, gitlab_enterprise, bitbucket_server | Optional -| -h, --help | Show this message and exit.|Optional +| Options | Description | usage | +| :--------------------------: | :----------------------------------------------------------------------------------------: | :------: | +| -C, --sha, --commit-sha TEXT | Commit SHA (with 40 chars) | Required | +| -t, --token TEXT | Codecov upload token | Required | +| -r, --slug TEXT | owner/repo slug used instead of the private repo token in Self-hosted | Optional | +| --force | Always emit passing checks regardless of changed files | Optional | +| -Z, --fail-on-error | Exit with non-zero code in case of error | Optional | +| --git-service | Options: github, gitlab, bitbucket, github_enterprise, gitlab_enterprise, bitbucket_server | Optional | +| -h, --help | Show this message and exit. | Optional | # How to Use Local Upload diff --git a/codecov_cli/helpers/request.py b/codecov_cli/helpers/request.py index 6c153bd5..f9cf8ab1 100644 --- a/codecov_cli/helpers/request.py +++ b/codecov_cli/helpers/request.py @@ -125,7 +125,7 @@ def send_put_request( return request_result(put(url=url, data=data, headers=headers)) -def request_result(resp): +def request_result(resp: requests.Response) -> RequestResult: if resp.status_code >= 400: return RequestResult( status_code=resp.status_code, From 880d3fde12a23f3010d5923ba5c9b91dd9c079cd Mon Sep 17 00:00:00 2001 From: Nora Shapiro Date: Thu, 7 Nov 2024 12:00:05 -0800 Subject: [PATCH 16/30] Remove token enforcement for true tokenless endpoints (#533) * setup and cleanup * remove token enforcement for true tokenless endpoints * backwards compatability * remove pytest-mock * typing fixes * fix: bump version * fix: logs * all upload endpoints can do tokenless * fix failing test * remove test log * bump version * revert changes to version * linter changes --------- Co-authored-by: Tom Hu --- codecov_cli/helpers/request.py | 10 ++++- codecov_cli/services/commit/__init__.py | 8 ++-- codecov_cli/services/commit/base_picking.py | 4 +- codecov_cli/services/empty_upload/__init__.py | 4 +- codecov_cli/services/report/__init__.py | 14 +++---- codecov_cli/services/upload/__init__.py | 2 +- codecov_cli/services/upload/upload_sender.py | 2 +- tests/services/commit/test_base_picking.py | 24 ++++++++++++ tests/services/commit/test_commit_service.py | 30 ++++++++++++++ .../empty_upload/test_empty_upload.py | 30 ++++++++++++++ tests/services/report/test_report_results.py | 39 ++++++++++++++++--- tests/services/report/test_report_service.py | 24 +++++++++++- .../test_upload_completion.py | 31 +++++++++++++++ 13 files changed, 195 insertions(+), 27 deletions(-) diff --git a/codecov_cli/helpers/request.py b/codecov_cli/helpers/request.py index f9cf8ab1..e5c04f8f 100644 --- a/codecov_cli/helpers/request.py +++ b/codecov_cli/helpers/request.py @@ -102,7 +102,10 @@ def send_get_request( return request_result(get(url=url, headers=headers, params=params)) -def get_token_header_or_fail(token: str) -> dict: +def get_token_header_or_fail(token: Optional[str]) -> dict: + """ + Rejects requests with no Authorization token. Prevents tokenless uploads. + """ if token is None: raise click.ClickException( "Codecov token not found. Please provide Codecov token with -t flag." @@ -110,7 +113,10 @@ def get_token_header_or_fail(token: str) -> dict: return {"Authorization": f"token {token}"} -def get_token_header(token: str) -> Optional[dict]: +def get_token_header(token: Optional[str]) -> Optional[dict]: + """ + Allows requests with no Authorization token. + """ if token is None: return None return {"Authorization": f"token {token}"} diff --git a/codecov_cli/services/commit/__init__.py b/codecov_cli/services/commit/__init__.py index c142e7bf..54518de7 100644 --- a/codecov_cli/services/commit/__init__.py +++ b/codecov_cli/services/commit/__init__.py @@ -3,9 +3,9 @@ import typing from codecov_cli.helpers.config import CODECOV_INGEST_URL -from codecov_cli.helpers.encoder import decode_slug, encode_slug +from codecov_cli.helpers.encoder import encode_slug from codecov_cli.helpers.request import ( - get_token_header_or_fail, + get_token_header, log_warnings_and_errors_if_any, send_post_request, ) @@ -19,7 +19,7 @@ def create_commit_logic( pr: typing.Optional[str], branch: typing.Optional[str], slug: typing.Optional[str], - token: str, + token: typing.Optional[str], service: typing.Optional[str], enterprise_url: typing.Optional[str] = None, fail_on_error: bool = False, @@ -61,7 +61,7 @@ def send_commit_data( branch = tokenless # type: ignore logger.info("The PR is happening in a forked repo. Using tokenless upload.") else: - headers = get_token_header_or_fail(token) + headers = get_token_header(token) data = { "branch": branch, diff --git a/codecov_cli/services/commit/base_picking.py b/codecov_cli/services/commit/base_picking.py index 20767132..7332a462 100644 --- a/codecov_cli/services/commit/base_picking.py +++ b/codecov_cli/services/commit/base_picking.py @@ -2,7 +2,7 @@ from codecov_cli.helpers.config import CODECOV_API_URL from codecov_cli.helpers.request import ( - get_token_header_or_fail, + get_token_header, log_warnings_and_errors_if_any, send_put_request, ) @@ -15,7 +15,7 @@ def base_picking_logic(base_sha, pr, slug, token, service, enterprise_url, args) "cli_args": args, "user_provided_base_sha": base_sha, } - headers = get_token_header_or_fail(token) + headers = get_token_header(token) upload_url = enterprise_url or CODECOV_API_URL url = f"{upload_url}/api/v1/{service}/{slug}/pulls/{pr}" sending_result = send_put_request(url=url, data=data, headers=headers) diff --git a/codecov_cli/services/empty_upload/__init__.py b/codecov_cli/services/empty_upload/__init__.py index 7c8b0682..587bb756 100644 --- a/codecov_cli/services/empty_upload/__init__.py +++ b/codecov_cli/services/empty_upload/__init__.py @@ -4,7 +4,7 @@ from codecov_cli.helpers.config import CODECOV_API_URL from codecov_cli.helpers.encoder import encode_slug from codecov_cli.helpers.request import ( - get_token_header_or_fail, + get_token_header, log_warnings_and_errors_if_any, send_post_request, ) @@ -23,7 +23,7 @@ def empty_upload_logic( args, ): encoded_slug = encode_slug(slug) - headers = get_token_header_or_fail(token) + headers = get_token_header(token) upload_url = enterprise_url or CODECOV_API_URL url = f"{upload_url}/upload/{git_service}/{encoded_slug}/commits/{commit_sha}/empty-upload" sending_result = send_post_request( diff --git a/codecov_cli/services/report/__init__.py b/codecov_cli/services/report/__init__.py index 0b29c9d6..b19a8c19 100644 --- a/codecov_cli/services/report/__init__.py +++ b/codecov_cli/services/report/__init__.py @@ -1,15 +1,13 @@ import json import logging import time - -import requests +import typing from codecov_cli.helpers import request from codecov_cli.helpers.config import CODECOV_API_URL, CODECOV_INGEST_URL -from codecov_cli.helpers.encoder import decode_slug, encode_slug +from codecov_cli.helpers.encoder import encode_slug from codecov_cli.helpers.request import ( get_token_header, - get_token_header_or_fail, log_warnings_and_errors_if_any, request_result, send_post_request, @@ -24,7 +22,7 @@ def create_report_logic( code: str, slug: str, service: str, - token: str, + token: typing.Optional[str], enterprise_url: str, pull_request_number: int, fail_on_error: bool = False, @@ -70,7 +68,7 @@ def create_report_results_logic( code: str, slug: str, service: str, - token: str, + token: typing.Optional[str], enterprise_url: str, fail_on_error: bool = False, args: dict = None, @@ -103,7 +101,7 @@ def send_reports_result_request( data = { "cli_args": args, } - headers = get_token_header_or_fail(token) + headers = get_token_header(token) upload_url = enterprise_url or CODECOV_API_URL url = f"{upload_url}/upload/{service}/{encoded_slug}/commits/{commit_sha}/reports/{report_code}/results" return send_post_request(url=url, data=data, headers=headers) @@ -118,7 +116,7 @@ def send_reports_result_get_request( enterprise_url, fail_on_error=False, ): - headers = get_token_header_or_fail(token) + headers = get_token_header(token) upload_url = enterprise_url or CODECOV_API_URL url = f"{upload_url}/upload/{service}/{encoded_slug}/commits/{commit_sha}/reports/{report_code}/results" number_tries = 0 diff --git a/codecov_cli/services/upload/__init__.py b/codecov_cli/services/upload/__init__.py index ae1a22a2..7b3c884f 100644 --- a/codecov_cli/services/upload/__init__.py +++ b/codecov_cli/services/upload/__init__.py @@ -56,7 +56,7 @@ def do_upload_logic( report_code: str, slug: typing.Optional[str], swift_project: typing.Optional[str], - token: str, + token: typing.Optional[str], upload_file_type: str = "coverage", use_legacy_uploader: bool = False, ): diff --git a/codecov_cli/services/upload/upload_sender.py b/codecov_cli/services/upload/upload_sender.py index bfd5a07f..84dc8189 100644 --- a/codecov_cli/services/upload/upload_sender.py +++ b/codecov_cli/services/upload/upload_sender.py @@ -27,7 +27,7 @@ def send_upload_data( self, upload_data: UploadCollectionResult, commit_sha: str, - token: str, + token: typing.Optional[str], env_vars: typing.Dict[str, str], report_code: str, upload_file_type: str = "coverage", diff --git a/tests/services/commit/test_base_picking.py b/tests/services/commit/test_base_picking.py index 9b560a51..2c295fe2 100644 --- a/tests/services/commit/test_base_picking.py +++ b/tests/services/commit/test_base_picking.py @@ -139,3 +139,27 @@ def test_base_picking_command_error(mocker): "error", "Base picking failed: Unauthorized", ) in parse_outstreams_into_log_lines(result.output) + + +def test_base_picking_no_token(mocker): + mocked_response = mocker.patch( + "codecov_cli.services.commit.base_picking.send_put_request", + return_value=RequestResult(status_code=200, error=None, warnings=[], text=""), + ) + runner = CliRunner() + result = runner.invoke( + pr_base_picking, + [ + "--pr", + "11", + "--base-sha", + "9a6902ee94c18e8e27561ce316b16d75a02c7bc1", + "--service", + "github", + "--slug", + "owner/repo", + ], + obj=mocker.MagicMock(), # context object + ) + assert result.exit_code == 0 + mocked_response.assert_called_once() diff --git a/tests/services/commit/test_commit_service.py b/tests/services/commit/test_commit_service.py index 31ac3c02..2fd5157d 100644 --- a/tests/services/commit/test_commit_service.py +++ b/tests/services/commit/test_commit_service.py @@ -178,3 +178,33 @@ def test_commit_sender_with_forked_repo(mocker): }, headers=None, ) + + +def test_commit_without_token(mocker): + mocked_response = mocker.patch( + "codecov_cli.services.commit.send_post_request", + return_value=mocker.MagicMock(status_code=200, text="success"), + ) + + send_commit_data( + "commit_sha", + "parent_sha", + "1", + "branch", + "codecov::::codecov-cli", + None, + "github", + None, + None, + ) + mocked_response.assert_called_with( + url="https://ingest.codecov.io/upload/github/codecov::::codecov-cli/commits", + data={ + "branch": "branch", + "cli_args": None, + "commitid": "commit_sha", + "parent_commit_id": "parent_sha", + "pullid": "1", + }, + headers=None, + ) diff --git a/tests/services/empty_upload/test_empty_upload.py b/tests/services/empty_upload/test_empty_upload.py index b49d055e..1e354d50 100644 --- a/tests/services/empty_upload/test_empty_upload.py +++ b/tests/services/empty_upload/test_empty_upload.py @@ -1,6 +1,8 @@ import json import uuid +import click +import pytest from click.testing import CliRunner from codecov_cli.services.empty_upload import empty_upload_logic @@ -147,3 +149,31 @@ def test_empty_upload_force(mocker): assert res.error is None assert res.warnings == [] mocked_response.assert_called_once() + + +def test_empty_upload_no_token(mocker): + res = { + "result": "All changed files are ignored. Triggering passing notifications.", + "non_ignored_files": [], + } + mocked_response = mocker.patch( + "codecov_cli.helpers.request.requests.post", + return_value=RequestResult( + status_code=200, error=None, warnings=[], text=json.dumps(res) + ), + ) + runner = CliRunner() + with runner.isolation() as outstreams: + res = empty_upload_logic( + "commit_sha", "owner/repo", None, "service", None, False, False, None + ) + + out_bytes = parse_outstreams_into_log_lines(outstreams[0].getvalue()) + assert out_bytes == [ + ("info", "Process Empty Upload complete"), + ("info", "All changed files are ignored. Triggering passing notifications."), + ("info", "Non ignored files []"), + ] + assert res.error is None + assert res.warnings == [] + mocked_response.assert_called_once() diff --git a/tests/services/report/test_report_results.py b/tests/services/report/test_report_results.py index 27808d23..268f56e3 100644 --- a/tests/services/report/test_report_results.py +++ b/tests/services/report/test_report_results.py @@ -108,6 +108,19 @@ def test_report_results_request_200(mocker): mocked_response.assert_called_once() +def test_report_results_request_no_token(mocker): + mocked_response = mocker.patch( + "codecov_cli.helpers.request.requests.post", + return_value=mocker.MagicMock(status_code=200), + ) + res = send_reports_result_request( + "commit_sha", "report_code", "encoded_slug", "service", None, None, None + ) + assert res.error is None + assert res.warnings == [] + mocked_response.assert_called_once() + + def test_report_results_403(mocker): mocked_response = mocker.patch( "codecov_cli.helpers.request.requests.post", @@ -127,7 +140,7 @@ def test_report_results_403(mocker): def test_get_report_results_200_completed(mocker, capsys): mocked_response = mocker.patch( - "codecov_cli.services.report.requests.get", + "codecov_cli.helpers.request.requests.get", return_value=mocker.MagicMock( status_code=200, text='{"state": "completed", "result": {"state": "failure","message": "33.33% of diff hit (target 77.77%)"}}', @@ -147,11 +160,27 @@ def test_get_report_results_200_completed(mocker, capsys): ) in output +def test_get_report_results_no_token(mocker, capsys): + mocked_response = mocker.patch( + "codecov_cli.helpers.request.requests.get", + return_value=mocker.MagicMock( + status_code=200, + text='{"state": "completed", "result": {"state": "failure","message": "33.33% of diff hit (target 77.77%)"}}', + ), + ) + res = send_reports_result_get_request( + "commit_sha", "report_code", "encoded_slug", "service", None, None + ) + assert res.error is None + assert res.warnings == [] + mocked_response.assert_called_once() + + @patch("codecov_cli.services.report.MAX_NUMBER_TRIES", 1) def test_get_report_results_200_pending(mocker, capsys): mocker.patch("codecov_cli.services.report.time.sleep") mocked_response = mocker.patch( - "codecov_cli.services.report.requests.get", + "codecov_cli.helpers.request.requests.get", return_value=mocker.MagicMock( status_code=200, text='{"state": "pending", "result": {}}' ), @@ -169,7 +198,7 @@ def test_get_report_results_200_pending(mocker, capsys): def test_get_report_results_200_error(mocker, capsys): mocked_response = mocker.patch( - "codecov_cli.services.report.requests.get", + "codecov_cli.helpers.request.requests.get", return_value=mocker.MagicMock( status_code=200, text='{"state": "error", "result": {}}' ), @@ -190,7 +219,7 @@ def test_get_report_results_200_error(mocker, capsys): def test_get_report_results_200_undefined_state(mocker, capsys): mocked_response = mocker.patch( - "codecov_cli.services.report.requests.get", + "codecov_cli.helpers.request.requests.get", return_value=mocker.MagicMock( status_code=200, text='{"state": "undefined_state", "result": {}}' ), @@ -208,7 +237,7 @@ def test_get_report_results_200_undefined_state(mocker, capsys): def test_get_report_results_401(mocker, capsys): mocked_response = mocker.patch( - "codecov_cli.services.report.requests.get", + "codecov_cli.helpers.request.requests.get", return_value=mocker.MagicMock( status_code=401, text='{"detail": "Invalid token."}' ), diff --git a/tests/services/report/test_report_service.py b/tests/services/report/test_report_service.py index ac4c222b..153e803a 100644 --- a/tests/services/report/test_report_service.py +++ b/tests/services/report/test_report_service.py @@ -9,7 +9,7 @@ def test_send_create_report_request_200(mocker): mocked_response = mocker.patch( - "codecov_cli.services.report.requests.post", + "codecov_cli.helpers.request.requests.post", return_value=mocker.MagicMock(status_code=200), ) res = send_create_report_request( @@ -27,9 +27,29 @@ def test_send_create_report_request_200(mocker): mocked_response.assert_called_once() +def test_send_create_report_request_no_token(mocker): + mocked_response = mocker.patch( + "codecov_cli.helpers.request.requests.post", + return_value=mocker.MagicMock(status_code=200), + ) + res = send_create_report_request( + "commit_sha", + "code", + "github", + None, + "owner::::repo", + "enterprise_url", + 1, + None, + ) + assert res.error is None + assert res.warnings == [] + mocked_response.assert_called_once() + + def test_send_create_report_request_403(mocker): mocked_response = mocker.patch( - "codecov_cli.services.report.requests.post", + "codecov_cli.helpers.request.requests.post", return_value=mocker.MagicMock(status_code=403, text="Permission denied"), ) res = send_create_report_request( diff --git a/tests/services/upload_completion/test_upload_completion.py b/tests/services/upload_completion/test_upload_completion.py index 6ab5ea3b..6a6d5494 100644 --- a/tests/services/upload_completion/test_upload_completion.py +++ b/tests/services/upload_completion/test_upload_completion.py @@ -1,6 +1,8 @@ import json import uuid +import click +import pytest from click.testing import CliRunner from codecov_cli.services.upload_completion import upload_completion_logic @@ -93,6 +95,35 @@ def test_upload_completion_200(mocker): mocked_response.assert_called_once() +def test_upload_completion_no_token(mocker): + res = { + "uploads_total": 2, + "uploads_success": 2, + "uploads_processing": 0, + "uploads_error": 0, + } + mocked_response = mocker.patch( + "codecov_cli.helpers.request.requests.post", + return_value=RequestResult( + status_code=200, error=None, warnings=[], text=json.dumps(res) + ), + ) + runner = CliRunner() + with runner.isolation() as outstreams: + res = upload_completion_logic("commit_sha", "owner/repo", None, "service", None) + out_bytes = parse_outstreams_into_log_lines(outstreams[0].getvalue()) + assert out_bytes == [ + ("info", "Process Upload Completion complete"), + ( + "info", + "{'uploads_total': 2, 'uploads_success': 2, 'uploads_processing': 0, 'uploads_error': 0}", + ), + ] + assert res.error is None + assert res.warnings == [] + mocked_response.assert_called_once() + + def test_upload_completion_403(mocker): mocked_response = mocker.patch( "codecov_cli.helpers.request.requests.post", From 2935526caf5db25d64b02870f9cc45e33a7daa3d Mon Sep 17 00:00:00 2001 From: Codecov Releaser Date: Wed, 13 Nov 2024 14:35:06 -0500 Subject: [PATCH 17/30] Release v0.9.0 (#553) * Prepare release v0.9.0 * Update setup.py --------- Co-authored-by: Tom Hu <88201630+thomasrockhu-codecov@users.noreply.github.com> --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b1c59f32..d905447e 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name="codecov-cli", - version="0.8.0", + version="0.9.0", packages=find_packages(exclude=["contrib", "docs", "tests*"]), description="Codecov Command Line Interface", long_description=long_description, From 01046d1c6d4ab4a6febe9ee03a377496a73441a2 Mon Sep 17 00:00:00 2001 From: joseph-sentry <136376984+joseph-sentry@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:15:24 -0500 Subject: [PATCH 18/30] fix: pass args to send_reports_result_request (#545) * fix: pass args to send_reports_result_request * chore: lint * test: fix tests * fix: typing and tests * fix: typing * chore: make lint * fix: simple import typing fix --- codecov_cli/services/report/__init__.py | 7 +++++-- tests/services/report/test_report_results.py | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/codecov_cli/services/report/__init__.py b/codecov_cli/services/report/__init__.py index b19a8c19..da2b5127 100644 --- a/codecov_cli/services/report/__init__.py +++ b/codecov_cli/services/report/__init__.py @@ -3,6 +3,8 @@ import time import typing +import requests + from codecov_cli.helpers import request from codecov_cli.helpers.config import CODECOV_API_URL, CODECOV_INGEST_URL from codecov_cli.helpers.encoder import encode_slug @@ -26,7 +28,7 @@ def create_report_logic( enterprise_url: str, pull_request_number: int, fail_on_error: bool = False, - args: dict = None, + args: typing.Union[dict, None] = None, ): encoded_slug = encode_slug(slug) sending_result = send_create_report_request( @@ -71,7 +73,7 @@ def create_report_results_logic( token: typing.Optional[str], enterprise_url: str, fail_on_error: bool = False, - args: dict = None, + args: typing.Union[dict, None] = None, ): encoded_slug = encode_slug(slug) sending_result = send_reports_result_request( @@ -81,6 +83,7 @@ def create_report_results_logic( service=service, token=token, enterprise_url=enterprise_url, + args=args, ) log_warnings_and_errors_if_any( diff --git a/tests/services/report/test_report_results.py b/tests/services/report/test_report_results.py index 268f56e3..f713ce6b 100644 --- a/tests/services/report/test_report_results.py +++ b/tests/services/report/test_report_results.py @@ -43,6 +43,7 @@ def test_report_results_command_with_warnings(mocker): assert res == mock_send_reports_result_request.return_value mock_send_reports_result_request.assert_called_with( + args=None, commit_sha="commit_sha", report_code="code", service="service", @@ -85,6 +86,7 @@ def test_report_results_command_with_error(mocker): ] assert res == mock_send_reports_result_request.return_value mock_send_reports_result_request.assert_called_with( + args=None, commit_sha="commit_sha", report_code="code", service="service", From df923a22438716e76df66ac08a37e1e47896117b Mon Sep 17 00:00:00 2001 From: Codecov Releaser Date: Wed, 13 Nov 2024 15:24:06 -0500 Subject: [PATCH 19/30] Prepare release 9.0.1 (#555) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d905447e..741dd3ef 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name="codecov-cli", - version="0.9.0", + version="9.0.1", packages=find_packages(exclude=["contrib", "docs", "tests*"]), description="Codecov Command Line Interface", long_description=long_description, From 15922dfada970bececa31b0bd348e6feda1f1d71 Mon Sep 17 00:00:00 2001 From: Tom Hu <88201630+thomasrockhu-codecov@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:47:21 -0500 Subject: [PATCH 20/30] fix: downgrade pypi action (#556) --- .github/workflows/build_for_pypi.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build_for_pypi.yml b/.github/workflows/build_for_pypi.yml index 05d9793e..9e7ecc9c 100644 --- a/.github/workflows/build_for_pypi.yml +++ b/.github/workflows/build_for_pypi.yml @@ -35,7 +35,4 @@ jobs: python setup.py bdist_wheel --plat-name=win_amd64 - name: Publish package to PyPi if: inputs.publish == true - uses: pypa/gh-action-pypi-publish@release/v1 - - - + uses: pypa/gh-action-pypi-publish@v1.11.0 # Currently an issue with attestations on `release/v1` From be51b525ad488d552174d8f094ac5bda62e15357 Mon Sep 17 00:00:00 2001 From: Codecov Releaser Date: Wed, 13 Nov 2024 15:55:31 -0500 Subject: [PATCH 21/30] Prepare release 9.0.2 (#557) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 741dd3ef..d93811c8 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name="codecov-cli", - version="9.0.1", + version="9.0.2", packages=find_packages(exclude=["contrib", "docs", "tests*"]), description="Codecov Command Line Interface", long_description=long_description, From 639aef803387d11d8a1e85df816a2a2d5794bb30 Mon Sep 17 00:00:00 2001 From: Tom Hu <88201630+thomasrockhu-codecov@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:08:58 -0500 Subject: [PATCH 22/30] fix: add verbose logging (#558) --- .github/workflows/build_for_pypi.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_for_pypi.yml b/.github/workflows/build_for_pypi.yml index 9e7ecc9c..ffc0ee8f 100644 --- a/.github/workflows/build_for_pypi.yml +++ b/.github/workflows/build_for_pypi.yml @@ -35,4 +35,7 @@ jobs: python setup.py bdist_wheel --plat-name=win_amd64 - name: Publish package to PyPi if: inputs.publish == true - uses: pypa/gh-action-pypi-publish@v1.11.0 # Currently an issue with attestations on `release/v1` + uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true + attestation: true From a89f1080e194a8c93c1e7e9b904fa13080d287ad Mon Sep 17 00:00:00 2001 From: Codecov Releaser Date: Wed, 13 Nov 2024 16:16:54 -0500 Subject: [PATCH 23/30] Prepare release 9.0.3 (#559) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d93811c8..645a1606 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name="codecov-cli", - version="9.0.2", + version="9.0.3", packages=find_packages(exclude=["contrib", "docs", "tests*"]), description="Codecov Command Line Interface", long_description=long_description, From 84e0ee67dca43d82f5d6e2ecf9a5ae6446c9361a Mon Sep 17 00:00:00 2001 From: Tom Hu <88201630+thomasrockhu-codecov@users.noreply.github.com> Date: Wed, 13 Nov 2024 17:08:42 -0500 Subject: [PATCH 24/30] fix: turn off attestations (#560) --- .github/workflows/build_for_pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_for_pypi.yml b/.github/workflows/build_for_pypi.yml index ffc0ee8f..1315f24a 100644 --- a/.github/workflows/build_for_pypi.yml +++ b/.github/workflows/build_for_pypi.yml @@ -37,5 +37,5 @@ jobs: if: inputs.publish == true uses: pypa/gh-action-pypi-publish@release/v1 with: + attestations: false verbose: true - attestation: true From e680a672bb8e56d0f21d8ea36a22eec061c996af Mon Sep 17 00:00:00 2001 From: Codecov Releaser Date: Wed, 13 Nov 2024 17:15:37 -0500 Subject: [PATCH 25/30] Prepare release 9.0.4 (#561) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 645a1606..92442f6c 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name="codecov-cli", - version="9.0.3", + version="9.0.4", packages=find_packages(exclude=["contrib", "docs", "tests*"]), description="Codecov Command Line Interface", long_description=long_description, From c51e790f868af44d32757abfb51a9ed4a31df575 Mon Sep 17 00:00:00 2001 From: Codecov Releaser Date: Thu, 14 Nov 2024 13:57:59 -0500 Subject: [PATCH 26/30] Prepare release 0.9.4 (#562) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 92442f6c..fd96cd1b 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name="codecov-cli", - version="9.0.4", + version="0.9.4", packages=find_packages(exclude=["contrib", "docs", "tests*"]), description="Codecov Command Line Interface", long_description=long_description, From 0ebc5136c225b548c120f93b22bb9967afce6b9b Mon Sep 17 00:00:00 2001 From: Tom Hu <88201630+thomasrockhu-codecov@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:37:25 -0500 Subject: [PATCH 27/30] chore(deps): bump httpx to 0.27.x (#552) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fd96cd1b..4ffee44c 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ author_email="support@codecov.io", install_requires=[ "click==8.*", - "httpx==0.23.*", + "httpx==0.27.*", "ijson==3.*", "pyyaml==6.*", "responses==0.21.*", From 1f04560c0cbc203670d39abe64032f5322457721 Mon Sep 17 00:00:00 2001 From: Tony Le Date: Mon, 25 Nov 2024 14:51:55 -0500 Subject: [PATCH 28/30] Adds upload-coverage command (#551) * Adds single endpoint coverage command "upload-coverage" --- codecov_cli/commands/upload_coverage.py | 175 ++++++++++++++++++ codecov_cli/main.py | 2 + codecov_cli/services/upload/__init__.py | 4 + codecov_cli/services/upload/upload_sender.py | 16 +- .../services/upload_coverage/__init__.py | 90 +++++++++ tests/commands/test_invoke_upload_coverage.py | 140 ++++++++++++++ tests/commands/test_invoke_upload_process.py | 2 + tests/helpers/test_upload_sender.py | 41 ++++ tests/services/upload/test_upload_service.py | 6 + tests/test_codecov_cli.py | 1 + 10 files changed, 476 insertions(+), 1 deletion(-) create mode 100644 codecov_cli/commands/upload_coverage.py create mode 100644 codecov_cli/services/upload_coverage/__init__.py create mode 100644 tests/commands/test_invoke_upload_coverage.py diff --git a/codecov_cli/commands/upload_coverage.py b/codecov_cli/commands/upload_coverage.py new file mode 100644 index 00000000..7f22c998 --- /dev/null +++ b/codecov_cli/commands/upload_coverage.py @@ -0,0 +1,175 @@ +import logging +import pathlib +import typing + +import click + +from codecov_cli.commands.commit import create_commit +from codecov_cli.commands.report import create_report +from codecov_cli.commands.upload import do_upload, global_upload_options +from codecov_cli.helpers.args import get_cli_args +from codecov_cli.helpers.options import global_options +from codecov_cli.services.upload_coverage import upload_coverage_logic +from codecov_cli.types import CommandContext + +logger = logging.getLogger("codecovcli") + + +# These options are the combined options of commit, report and upload commands +@click.command() +@global_options +@global_upload_options +@click.option( + "--parent-sha", + help="SHA (with 40 chars) of what should be the parent of this commit", +) +@click.pass_context +def upload_coverage( + ctx: CommandContext, + branch: typing.Optional[str], + build_code: typing.Optional[str], + build_url: typing.Optional[str], + commit_sha: str, + disable_file_fixes: bool, + disable_search: bool, + dry_run: bool, + env_vars: typing.Dict[str, str], + fail_on_error: bool, + files_search_exclude_folders: typing.List[pathlib.Path], + files_search_explicitly_listed_files: typing.List[pathlib.Path], + files_search_root_folder: pathlib.Path, + flags: typing.List[str], + gcov_args: typing.Optional[str], + gcov_executable: typing.Optional[str], + gcov_ignore: typing.Optional[str], + gcov_include: typing.Optional[str], + git_service: typing.Optional[str], + handle_no_reports_found: bool, + job_code: typing.Optional[str], + name: typing.Optional[str], + network_filter: typing.Optional[str], + network_prefix: typing.Optional[str], + network_root_folder: pathlib.Path, + parent_sha: typing.Optional[str], + plugin_names: typing.List[str], + pull_request_number: typing.Optional[str], + report_code: str, + report_type: str, + slug: typing.Optional[str], + swift_project: typing.Optional[str], + token: typing.Optional[str], + use_legacy_uploader: bool, +): + args = get_cli_args(ctx) + logger.debug( + "Starting upload coverage", + extra=dict( + extra_log_attributes=args, + ), + ) + + if not use_legacy_uploader and report_type == "coverage": + versioning_system = ctx.obj["versioning_system"] + codecov_yaml = ctx.obj["codecov_yaml"] or {} + cli_config = codecov_yaml.get("cli", {}) + ci_adapter = ctx.obj.get("ci_adapter") + enterprise_url = ctx.obj.get("enterprise_url") + args = get_cli_args(ctx) + ctx.invoke( + upload_coverage_logic, + cli_config, + versioning_system, + ci_adapter, + branch=branch, + build_code=build_code, + build_url=build_url, + commit_sha=commit_sha, + disable_file_fixes=disable_file_fixes, + disable_search=disable_search, + dry_run=dry_run, + enterprise_url=enterprise_url, + env_vars=env_vars, + fail_on_error=fail_on_error, + files_search_exclude_folders=files_search_exclude_folders, + files_search_explicitly_listed_files=files_search_explicitly_listed_files, + files_search_root_folder=files_search_root_folder, + flags=flags, + gcov_args=gcov_args, + gcov_executable=gcov_executable, + gcov_ignore=gcov_ignore, + gcov_include=gcov_include, + git_service=git_service, + handle_no_reports_found=handle_no_reports_found, + job_code=job_code, + name=name, + network_filter=network_filter, + network_prefix=network_prefix, + network_root_folder=network_root_folder, + parent_sha=parent_sha, + plugin_names=plugin_names, + pull_request_number=pull_request_number, + report_code=report_code, + slug=slug, + swift_project=swift_project, + token=token, + upload_file_type=report_type, + use_legacy_uploader=use_legacy_uploader, + args=args, + ) + else: + ctx.invoke( + create_commit, + commit_sha=commit_sha, + parent_sha=parent_sha, + pull_request_number=pull_request_number, + branch=branch, + slug=slug, + token=token, + git_service=git_service, + fail_on_error=True, + ) + if report_type == "coverage": + ctx.invoke( + create_report, + token=token, + code=report_code, + fail_on_error=True, + commit_sha=commit_sha, + slug=slug, + git_service=git_service, + ) + ctx.invoke( + do_upload, + branch=branch, + build_code=build_code, + build_url=build_url, + commit_sha=commit_sha, + disable_file_fixes=disable_file_fixes, + disable_search=disable_search, + dry_run=dry_run, + env_vars=env_vars, + fail_on_error=fail_on_error, + files_search_exclude_folders=files_search_exclude_folders, + files_search_explicitly_listed_files=files_search_explicitly_listed_files, + files_search_root_folder=files_search_root_folder, + flags=flags, + gcov_args=gcov_args, + gcov_executable=gcov_executable, + gcov_ignore=gcov_ignore, + gcov_include=gcov_include, + git_service=git_service, + handle_no_reports_found=handle_no_reports_found, + job_code=job_code, + name=name, + network_filter=network_filter, + network_prefix=network_prefix, + network_root_folder=network_root_folder, + plugin_names=plugin_names, + pull_request_number=pull_request_number, + report_code=report_code, + report_type=report_type, + slug=slug, + swift_project=swift_project, + token=token, + use_legacy_uploader=use_legacy_uploader, + ) diff --git a/codecov_cli/main.py b/codecov_cli/main.py index 9505aaa6..0640fad8 100644 --- a/codecov_cli/main.py +++ b/codecov_cli/main.py @@ -16,6 +16,7 @@ from codecov_cli.commands.send_notifications import send_notifications from codecov_cli.commands.staticanalysis import static_analysis from codecov_cli.commands.upload import do_upload +from codecov_cli.commands.upload_coverage import upload_coverage from codecov_cli.commands.upload_process import upload_process from codecov_cli.helpers.ci_adapters import get_ci_adapter, get_ci_providers_list from codecov_cli.helpers.config import load_cli_config @@ -74,6 +75,7 @@ def cli( cli.add_command(label_analysis) cli.add_command(static_analysis) cli.add_command(empty_upload) +cli.add_command(upload_coverage) cli.add_command(upload_process) cli.add_command(send_notifications) cli.add_command(process_test_results) diff --git a/codecov_cli/services/upload/__init__.py b/codecov_cli/services/upload/__init__.py index 7b3c884f..003f84bc 100644 --- a/codecov_cli/services/upload/__init__.py +++ b/codecov_cli/services/upload/__init__.py @@ -24,6 +24,7 @@ def do_upload_logic( cli_config: typing.Dict, versioning_system: VersioningSystemInterface, ci_adapter: CIAdapterBase, + upload_coverage: bool = False, *, args: dict = None, branch: typing.Optional[str], @@ -51,6 +52,7 @@ def do_upload_logic( network_filter: typing.Optional[str], network_prefix: typing.Optional[str], network_root_folder: Path, + parent_sha: typing.Optional[str] = None, plugin_names: typing.List[str], pull_request_number: typing.Optional[str], report_code: str, @@ -148,6 +150,8 @@ def do_upload_logic( ci_service, git_service, enterprise_url, + parent_sha, + upload_coverage, args, ) else: diff --git a/codecov_cli/services/upload/upload_sender.py b/codecov_cli/services/upload/upload_sender.py index 84dc8189..6619401b 100644 --- a/codecov_cli/services/upload/upload_sender.py +++ b/codecov_cli/services/upload/upload_sender.py @@ -42,6 +42,8 @@ def send_upload_data( ci_service: typing.Optional[str] = None, git_service: typing.Optional[str] = None, enterprise_url: typing.Optional[str] = None, + parent_sha: typing.Optional[str] = None, + upload_coverage: bool = False, args: dict = None, ) -> RequestResult: data = { @@ -54,6 +56,12 @@ def send_upload_data( "name": name, "version": codecov_cli_version, } + if upload_coverage: + data["branch"] = branch + data["code"] = report_code + data["commitid"] = commit_sha + data["parent_commit_id"] = parent_sha + data["pullid"] = pull_request_number headers = get_token_header(token) encoded_slug = encode_slug(slug) upload_url = enterprise_url or CODECOV_INGEST_URL @@ -66,6 +74,7 @@ def send_upload_data( encoded_slug, commit_sha, report_code, + upload_coverage, ) # Data that goes to storage reports_payload = self._generate_payload( @@ -176,9 +185,14 @@ def get_url_and_possibly_update_data( encoded_slug, commit_sha, report_code, + upload_coverage=False, ): if report_type == "coverage": - url = f"{upload_url}/upload/{git_service}/{encoded_slug}/commits/{commit_sha}/reports/{report_code}/uploads" + base_url = f"{upload_url}/upload/{git_service}/{encoded_slug}" + if upload_coverage: + url = f"{base_url}/upload-coverage" + else: + url = f"{base_url}/commits/{commit_sha}/reports/{report_code}/uploads" elif report_type == "test_results": data["slug"] = encoded_slug data["branch"] = branch diff --git a/codecov_cli/services/upload_coverage/__init__.py b/codecov_cli/services/upload_coverage/__init__.py new file mode 100644 index 00000000..9f53a554 --- /dev/null +++ b/codecov_cli/services/upload_coverage/__init__.py @@ -0,0 +1,90 @@ +import pathlib +import typing + +from codecov_cli.helpers.ci_adapters.base import CIAdapterBase +from codecov_cli.helpers.versioning_systems import VersioningSystemInterface +from codecov_cli.services.upload import do_upload_logic + + +def upload_coverage_logic( + cli_config: typing.Dict, + versioning_system: VersioningSystemInterface, + ci_adapter: CIAdapterBase, + *, + branch: typing.Optional[str], + build_code: typing.Optional[str], + build_url: typing.Optional[str], + commit_sha: str, + disable_file_fixes: bool, + disable_search: bool, + dry_run: bool, + enterprise_url: typing.Optional[str], + env_vars: typing.Dict[str, str], + fail_on_error: bool, + files_search_exclude_folders: typing.List[pathlib.Path], + files_search_explicitly_listed_files: typing.List[pathlib.Path], + files_search_root_folder: pathlib.Path, + flags: typing.List[str], + gcov_args: typing.Optional[str], + gcov_executable: typing.Optional[str], + gcov_ignore: typing.Optional[str], + gcov_include: typing.Optional[str], + git_service: typing.Optional[str], + handle_no_reports_found: bool, + job_code: typing.Optional[str], + name: typing.Optional[str], + network_filter: typing.Optional[str], + network_prefix: typing.Optional[str], + network_root_folder: pathlib.Path, + parent_sha: typing.Optional[str], + plugin_names: typing.List[str], + pull_request_number: typing.Optional[str], + report_code: str, + slug: typing.Optional[str], + swift_project: typing.Optional[str], + token: typing.Optional[str], + use_legacy_uploader: bool, + upload_file_type: str = "coverage", + args: dict = None, +): + return do_upload_logic( + cli_config=cli_config, + versioning_system=versioning_system, + ci_adapter=ci_adapter, + upload_coverage=True, + args=args, + branch=branch, + build_code=build_code, + build_url=build_url, + commit_sha=commit_sha, + disable_file_fixes=disable_file_fixes, + disable_search=disable_search, + dry_run=dry_run, + enterprise_url=enterprise_url, + env_vars=env_vars, + fail_on_error=fail_on_error, + files_search_exclude_folders=files_search_exclude_folders, + files_search_explicitly_listed_files=files_search_explicitly_listed_files, + files_search_root_folder=files_search_root_folder, + flags=flags, + gcov_args=gcov_args, + gcov_executable=gcov_executable, + gcov_ignore=gcov_ignore, + gcov_include=gcov_include, + git_service=git_service, + handle_no_reports_found=handle_no_reports_found, + job_code=job_code, + name=name, + network_filter=network_filter, + network_prefix=network_prefix, + network_root_folder=network_root_folder, + parent_sha=parent_sha, + plugin_names=plugin_names, + pull_request_number=pull_request_number, + report_code=report_code, + slug=slug, + swift_project=swift_project, + token=token, + use_legacy_uploader=use_legacy_uploader, + upload_file_type=upload_file_type, + ) diff --git a/tests/commands/test_invoke_upload_coverage.py b/tests/commands/test_invoke_upload_coverage.py new file mode 100644 index 00000000..3558ed06 --- /dev/null +++ b/tests/commands/test_invoke_upload_coverage.py @@ -0,0 +1,140 @@ +from unittest.mock import patch + +from click.testing import CliRunner + +from codecov_cli.fallbacks import FallbackFieldEnum +from codecov_cli.main import cli +from codecov_cli.types import RequestError, RequestResult +from tests.factory import FakeProvider, FakeVersioningSystem + + +def test_upload_coverage_missing_commit_sha(mocker): + fake_ci_provider = FakeProvider({FallbackFieldEnum.commit_sha: None}) + fake_versioning_system = FakeVersioningSystem({FallbackFieldEnum.commit_sha: None}) + mocker.patch( + "codecov_cli.main.get_versioning_system", return_value=fake_versioning_system + ) + mocker.patch("codecov_cli.main.get_ci_adapter", return_value=fake_ci_provider) + runner = CliRunner() + with runner.isolated_filesystem(): + result = runner.invoke(cli, ["upload-coverage"], obj={}) + assert result.exit_code != 0 + + +def test_upload_coverage_raise_Z_option(mocker, use_verbose_option): + error = RequestError( + code=401, params={"some": "params"}, description="Unauthorized" + ) + command_result = RequestResult( + error=error, warnings=[], status_code=401, text="Unauthorized" + ) + + runner = CliRunner() + with runner.isolated_filesystem(): + with patch( + "codecov_cli.services.commit.send_commit_data" + ) as mocked_create_commit: + mocked_create_commit.return_value = command_result + result = runner.invoke( + cli, + [ + "upload-coverage", + "--fail-on-error", + "-C", + "command-sha", + "--slug", + "owner/repo", + "--report-type", + "test_results", + ], + obj={}, + ) + + assert result.exit_code != 0 + assert "Commit creating failed: Unauthorized" in result.output + assert str(result) == "" + + +def test_upload_coverage_options(mocker): + runner = CliRunner() + fake_ci_provider = FakeProvider({FallbackFieldEnum.commit_sha: None}) + mocker.patch("codecov_cli.main.get_ci_adapter", return_value=fake_ci_provider) + with runner.isolated_filesystem(): + runner = CliRunner() + result = runner.invoke(cli, ["upload-coverage", "-h"], obj={}) + assert result.exit_code == 0 + print(result.output) + + assert result.output.split("\n")[1:] == [ + "Usage: cli upload-coverage [OPTIONS]", + "", + "Options:", + " -C, --sha, --commit-sha TEXT Commit SHA (with 40 chars) [required]", + " -Z, --fail-on-error Exit with non-zero code in case of error", + " --git-service [github|gitlab|bitbucket|github_enterprise|gitlab_enterprise|bitbucket_server]", + " -t, --token TEXT Codecov upload token", + " -r, --slug TEXT owner/repo slug used instead of the private", + " repo token in Self-hosted", + " --code, --report-code TEXT The code of the report. If unsure, leave", + " default", + " --network-root-folder PATH Root folder from which to consider paths on", + " the network section [default: (Current", + " working directory)]", + " -s, --dir, --coverage-files-search-root-folder, --files-search-root-folder PATH", + " Folder where to search for coverage files", + " [default: (Current Working Directory)]", + " --exclude, --coverage-files-search-exclude-folder, --files-search-exclude-folder PATH", + " Folders to exclude from search", + " -f, --file, --coverage-files-search-direct-file, --files-search-direct-file PATH", + " Explicit files to upload. These will be added", + " to the coverage files found for upload. If you", + " wish to only upload the specified files,", + " please consider using --disable-search to", + " disable uploading other files.", + " --disable-search Disable search for coverage files. This is", + " helpful when specifying what files you want to", + " upload with the --file option.", + " --disable-file-fixes Disable file fixes to ignore common lines from", + " coverage (e.g. blank lines or empty brackets)", + " -b, --build, --build-code TEXT Specify the build number manually", + " --build-url TEXT The URL of the build where this is running", + " --job-code TEXT", + " -n, --name TEXT Custom defined name of the upload. Visible in", + " Codecov UI", + " -B, --branch TEXT Branch to which this commit belongs to", + " -P, --pr, --pull-request-number TEXT", + " Specify the pull request number mannually.", + " Used to override pre-existing CI environment", + " variables", + " -e, --env, --env-var TEXT Specify environment variables to be included", + " with this build.", + " -F, --flag TEXT Flag the upload to group coverage metrics.", + " Multiple flags allowed.", + " --plugin TEXT", + " -d, --dry-run Don't upload files to Codecov", + " --legacy, --use-legacy-uploader", + " Use the legacy upload endpoint", + " --handle-no-reports-found Raise no excpetions when no coverage reports", + " found.", + " --report-type [coverage|test_results]", + " The type of the file to upload, coverage by", + " default. Possible values are: testing,", + " coverage.", + " --network-filter TEXT Specify a filter on the files listed in the", + " network section of the Codecov report. This", + " will only add files whose path begin with the", + " specified filter. Useful for upload-specific", + " path fixing", + " --network-prefix TEXT Specify a prefix on files listed in the", + " network section of the Codecov report. Useful", + " to help resolve path fixing", + " --gcov-args TEXT Extra arguments to pass to gcov", + " --gcov-ignore TEXT Paths to ignore during gcov gathering", + " --gcov-include TEXT Paths to include during gcov gathering", + " --gcov-executable TEXT gcov executable to run. Defaults to 'gcov'", + " --swift-project TEXT Specify the swift project", + " --parent-sha TEXT SHA (with 40 chars) of what should be the", + " parent of this commit", + " -h, --help Show this message and exit.", + "", + ] diff --git a/tests/commands/test_invoke_upload_process.py b/tests/commands/test_invoke_upload_process.py index 59f70851..47f7e124 100644 --- a/tests/commands/test_invoke_upload_process.py +++ b/tests/commands/test_invoke_upload_process.py @@ -44,6 +44,8 @@ def test_upload_process_raise_Z_option(mocker, use_verbose_option): "command-sha", "--slug", "owner/repo", + "--report-type", + "test_results", ], obj={}, ) diff --git a/tests/helpers/test_upload_sender.py b/tests/helpers/test_upload_sender.py index 642e2a35..8fe91819 100644 --- a/tests/helpers/test_upload_sender.py +++ b/tests/helpers/test_upload_sender.py @@ -80,6 +80,22 @@ def mocked_legacy_upload_endpoint(mocked_responses): yield resp +@pytest.fixture +def mocked_upload_coverage_endpoint(mocked_responses): + encoded_slug = encode_slug(named_upload_data["slug"]) + resp = responses.Response( + responses.POST, + f"https://ingest.codecov.io/upload/github/{encoded_slug}/upload-coverage", + status=200, + json={ + "raw_upload_location": "https://puturl.com", + "url": "https://app.codecov.io/commit-url", + }, + ) + mocked_responses.add(resp) + yield resp + + @pytest.fixture def mocked_test_results_endpoint(mocked_responses): resp = responses.Response( @@ -193,6 +209,31 @@ def test_upload_sender_post_called_with_right_parameters( post_req_made.headers.items() >= headers.items() ) # test dict is a subset of the other + def test_upload_sender_post_called_with_right_parameters_and_upload_coverage( + self, mocked_responses, mocked_upload_coverage_endpoint, mocked_storage_server + ): + headers = {"Authorization": f"token {random_token}"} + + sending_result = UploadSender().send_upload_data( + upload_collection, random_sha, random_token, upload_coverage=True, **named_upload_data + ) + assert sending_result.error is None + assert sending_result.warnings == [] + + assert len(mocked_responses.calls) == 2 + + post_req_made = mocked_responses.calls[0].request + encoded_slug = encode_slug(named_upload_data["slug"]) + response = json.loads(mocked_responses.calls[0].response.text) + assert response.get("url") == "https://app.codecov.io/commit-url" + assert ( + post_req_made.url + == f"https://ingest.codecov.io/upload/github/{encoded_slug}/upload-coverage" + ) + assert ( + post_req_made.headers.items() >= headers.items() + ) # test dict is a subset of the other + def test_upload_sender_post_called_with_right_parameters_test_results( self, mocked_responses, mocked_test_results_endpoint, mocked_storage_server ): diff --git a/tests/services/upload/test_upload_service.py b/tests/services/upload/test_upload_service.py index 9a38a5f9..8de23367 100644 --- a/tests/services/upload/test_upload_service.py +++ b/tests/services/upload/test_upload_service.py @@ -125,6 +125,8 @@ def test_do_upload_logic_happy_path_legacy_uploader(mocker): "git_service", None, None, + False, + None, ) @@ -235,6 +237,8 @@ def test_do_upload_logic_happy_path(mocker): "git_service", None, None, + False, + None, ) @@ -684,5 +688,7 @@ def test_do_upload_logic_happy_path_test_results(mocker): "service", "git_service", None, + None, + False, {"args": "fake_args"}, ) diff --git a/tests/test_codecov_cli.py b/tests/test_codecov_cli.py index 80136f06..6d3a81c3 100644 --- a/tests/test_codecov_cli.py +++ b/tests/test_codecov_cli.py @@ -14,5 +14,6 @@ def test_existing_commands(): "process-test-results", "send-notifications", "static-analysis", + "upload-coverage", "upload-process", ] From c20a2033c56dd63f5ce1f4c19a593ebb9f5a75f6 Mon Sep 17 00:00:00 2001 From: joseph-sentry <136376984+joseph-sentry@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:31:01 -0500 Subject: [PATCH 29/30] fix: import error (#567) * fix: import get_cli_args in get_report_results * chore: address linting errors * build: use ruff for linting and run linting this uses ruff for linting, it adds a configuration for ignoring certain rules (F401: unused import) and excluding certain directories (default + languages/ + samples/) * fix: remove as _ from except statements * build: update ci to lint with make lint --- .github/workflows/ci.yml | 12 +-- Makefile | 24 ++++-- codecov_cli/commands/get_report_results.py | 2 + codecov_cli/commands/labelanalysis.py | 8 +- codecov_cli/commands/process_test_results.py | 6 +- codecov_cli/helpers/args.py | 4 +- codecov_cli/helpers/request.py | 5 +- codecov_cli/plugins/pycoverage.py | 1 - codecov_cli/runners/dan_runner.py | 2 +- codecov_cli/runners/pytest_standard_runner.py | 1 - .../services/staticanalysis/__init__.py | 2 +- .../analyzers/python/__init__.py | 1 - .../services/upload/upload_collector.py | 2 +- ruff.toml | 79 +++++++++++++++++++ tests/ci_adapters/test_circleci.py | 2 +- tests/ci_adapters/test_gitlabci.py | 1 - tests/ci_adapters/test_herokuci.py | 10 +-- tests/ci_adapters/test_jenkins.py | 18 +---- tests/ci_adapters/test_local.py | 2 +- tests/commands/test_invoke_labelanalysis.py | 6 +- tests/commands/test_process_test_results.py | 28 +++---- tests/data/reports_examples.py | 1 - tests/helpers/git_services/test_github.py | 2 +- tests/helpers/test_config.py | 4 +- tests/helpers/test_encoder.py | 4 +- tests/helpers/test_git.py | 4 +- tests/helpers/test_request.py | 8 +- tests/helpers/test_upload_sender.py | 2 +- tests/helpers/test_versioning_systems.py | 2 +- .../test_compress_pycoverage_contexts.py | 6 +- tests/services/commit/test_commit_service.py | 2 +- .../static_analysis/test_analyse_file.py | 2 +- .../upload/test_coverage_file_finder.py | 1 - tests/services/upload/test_upload_service.py | 2 +- 34 files changed, 161 insertions(+), 95 deletions(-) create mode 100644 ruff.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e96fda3..434fd283 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,16 +16,10 @@ jobs: - uses: actions/checkout@v4 with: submodules: true - - name: Install dependencies + - name: Check linting with ruff run: | - python -m pip install --upgrade pip - pip install black==22.3.0 isort==5.10.1 - - name: Check linting with black - run: | - black --check codecov_cli - - name: Check imports order with isort - run: | - isort --check --profile=black codecov_cli -p staticcodecov_languages + make lint + codecov-startup: runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index aed79ccb..96f12b10 100644 --- a/Makefile +++ b/Makefile @@ -2,12 +2,26 @@ name ?= codecovcli # Semantic versioning format https://semver.org/ tag_regex := ^v([0-9]{1,}\.){2}[0-9]{1,}([-_]\w+)?$ +lint.install: + echo "Installing ruff..." + pip install -Iv ruff + +# The preferred method (for now) w.r.t. fixable rules is to manually update the makefile +# with --fix and re-run 'make lint.' Since ruff is constantly adding rules this is a slight +# amount of "needed" friction imo. +lint.run: + ruff check --ignore F401 --exclude languages --exclude samples + ruff format --exclude languages --exclude samples + +lint.check: + echo "Linting..." + ruff check --ignore F401 --exclude languages --exclude samples + echo "Formatting..." + ruff format --check --exclude languages --exclude samples + lint: - pip install black==22.3.0 isort==5.10.1 - black codecov_cli - isort --profile=black codecov_cli -p staticcodecov_languages - black tests - isort --profile black tests + make lint.install + make lint.run tag.release: ifeq ($(shell echo ${version} | egrep "${tag_regex}"),) diff --git a/codecov_cli/commands/get_report_results.py b/codecov_cli/commands/get_report_results.py index 017025d1..4e02a1f9 100644 --- a/codecov_cli/commands/get_report_results.py +++ b/codecov_cli/commands/get_report_results.py @@ -3,12 +3,14 @@ import click from codecov_cli.fallbacks import CodecovOption, FallbackFieldEnum +from codecov_cli.helpers.args import get_cli_args from codecov_cli.helpers.encoder import encode_slug from codecov_cli.helpers.git import GitService from codecov_cli.helpers.options import global_options from codecov_cli.services.report import send_reports_result_get_request from codecov_cli.types import CommandContext + logger = logging.getLogger("codecovcli") diff --git a/codecov_cli/commands/labelanalysis.py b/codecov_cli/commands/labelanalysis.py index cb664994..d384083d 100644 --- a/codecov_cli/commands/labelanalysis.py +++ b/codecov_cli/commands/labelanalysis.py @@ -390,12 +390,12 @@ def _dry_run_list_output( logger.warning(f"label-analysis didn't run correctly. Error: {fallback_reason}") to_run_line = " ".join( - sorted(map(lambda l: f"'{l}'", runner_options)) - + sorted(map(lambda l: f"'{l}'", labels_to_run)) + sorted(map(lambda option: f"'{option}'", runner_options)) + + sorted(map(lambda label: f"'{label}'", labels_to_run)) ) to_skip_line = " ".join( - sorted(map(lambda l: f"'{l}'", runner_options)) - + sorted(map(lambda l: f"'{l}'", labels_to_skip)) + sorted(map(lambda option: f"'{option}'", runner_options)) + + sorted(map(lambda label: f"'{label}'", labels_to_skip)) ) # ⚠️ DON'T use logger # logger goes to stderr, we want it in stdout diff --git a/codecov_cli/commands/process_test_results.py b/codecov_cli/commands/process_test_results.py index 6d3d89f2..b95cc30c 100644 --- a/codecov_cli/commands/process_test_results.py +++ b/codecov_cli/commands/process_test_results.py @@ -103,9 +103,9 @@ def process_test_results( dir, exclude_folders, files, disable_search, report_type="test_results" ) - upload_collection_results: List[ - UploadCollectionResultFile - ] = file_finder.find_files() + upload_collection_results: List[UploadCollectionResultFile] = ( + file_finder.find_files() + ) if len(upload_collection_results) == 0: raise click.ClickException( "No JUnit XML files were found. Make sure to specify them using the --file option." diff --git a/codecov_cli/helpers/args.py b/codecov_cli/helpers/args.py index 74c82f7b..0d797692 100644 --- a/codecov_cli/helpers/args.py +++ b/codecov_cli/helpers/args.py @@ -20,12 +20,12 @@ def get_cli_args(ctx: click.Context): filtered_args = {} for k in args.keys(): try: - if type(args[k]) == PosixPath: + if isinstance(args[k], PosixPath): filtered_args[k] = str(args[k]) else: json.dumps(args[k]) filtered_args[k] = args[k] - except Exception as e: + except Exception: continue return filtered_args diff --git a/codecov_cli/helpers/request.py b/codecov_cli/helpers/request.py index e5c04f8f..27bd3be0 100644 --- a/codecov_cli/helpers/request.py +++ b/codecov_cli/helpers/request.py @@ -52,8 +52,7 @@ def backoff_time(curr_retry): return 2 ** (curr_retry - 1) -class RetryException(Exception): - ... +class RetryException(Exception): ... def retry_request(func): @@ -73,7 +72,7 @@ def wrapper(*args, **kwargs): requests.exceptions.ConnectionError, requests.exceptions.Timeout, RetryException, - ) as exp: + ): logger.warning( "Request failed. Retrying", extra=dict(extra_log_attributes=dict(retry=retry)), diff --git a/codecov_cli/plugins/pycoverage.py b/codecov_cli/plugins/pycoverage.py index 6aba1575..99bbf96b 100644 --- a/codecov_cli/plugins/pycoverage.py +++ b/codecov_cli/plugins/pycoverage.py @@ -54,7 +54,6 @@ def __init__(self, config: dict): self.config = PycoverageConfig(config) def run_preparation(self, collector) -> PreparationPluginReturn: - if shutil.which("coverage") is None: logger.warning("coverage.py is not installed or can't be found.") return diff --git a/codecov_cli/runners/dan_runner.py b/codecov_cli/runners/dan_runner.py index 556c4931..1fee9af5 100644 --- a/codecov_cli/runners/dan_runner.py +++ b/codecov_cli/runners/dan_runner.py @@ -54,7 +54,7 @@ def process_labelanalysis_result(self, result: LabelAnalysisRequestResult): "DAN runner missing 'process_labelanalysis_result_command' configuration value" ) command_list = [] - if type(command) == list: + if isinstance(command, list): command_list.extend(command) else: command_list.append(command) diff --git a/codecov_cli/runners/pytest_standard_runner.py b/codecov_cli/runners/pytest_standard_runner.py index 0ab327cf..fd040429 100644 --- a/codecov_cli/runners/pytest_standard_runner.py +++ b/codecov_cli/runners/pytest_standard_runner.py @@ -57,7 +57,6 @@ def get_available_params(cls) -> List[str]: class PytestStandardRunner(LabelAnalysisRunnerInterface): - dry_run_runner_options = ["--cov-context=test"] params: PytestStandardRunnerConfigParams diff --git a/codecov_cli/services/staticanalysis/__init__.py b/codecov_cli/services/staticanalysis/__init__.py index aedd82c1..3cde4313 100644 --- a/codecov_cli/services/staticanalysis/__init__.py +++ b/codecov_cli/services/staticanalysis/__init__.py @@ -111,7 +111,7 @@ async def run_analysis_entrypoint( failed_uploads = [] with click.progressbar( length=len(files_that_need_upload), - label=f"Upload info to storage", + label="Upload info to storage", ) as bar: # It's better to have less files competing over CPU time when uploading # Especially if we might have large files diff --git a/codecov_cli/services/staticanalysis/analyzers/python/__init__.py b/codecov_cli/services/staticanalysis/analyzers/python/__init__.py index e535698b..d5e6db0c 100644 --- a/codecov_cli/services/staticanalysis/analyzers/python/__init__.py +++ b/codecov_cli/services/staticanalysis/analyzers/python/__init__.py @@ -44,7 +44,6 @@ class PythonAnalyzer(BaseAnalyzer): - condition_statements = [ "if_statement", "while_statement", diff --git a/codecov_cli/services/upload/upload_collector.py b/codecov_cli/services/upload/upload_collector.py index 53dcd860..5d0626af 100644 --- a/codecov_cli/services/upload/upload_collector.py +++ b/codecov_cli/services/upload/upload_collector.py @@ -141,7 +141,7 @@ def _get_file_fixes( reason=err.reason, ), ) - except IsADirectoryError as err: + except IsADirectoryError: logger.info(f"Skipping {filename}, found a directory not a file") return UploadCollectionResultFileFixer( diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000..42d4e461 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,79 @@ +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + "languages", + "samples" +] + +# Same as Black. +line-length = 88 +indent-width = 4 + +# Assume Python 3.9 +target-version = "py39" + +[lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = ["E4", "E7", "E9", "F"] +ignore = ["F401"] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" \ No newline at end of file diff --git a/tests/ci_adapters/test_circleci.py b/tests/ci_adapters/test_circleci.py index 6dc7e964..02073448 100644 --- a/tests/ci_adapters/test_circleci.py +++ b/tests/ci_adapters/test_circleci.py @@ -143,7 +143,7 @@ def test_branch(self, env_dict, expected, mocker): assert actual == expected def test_raises_value_error_if_invalid_field(self): - with pytest.raises(ValueError) as ex: + with pytest.raises(ValueError): CircleCICIAdapter().get_fallback_value("some random key x 123") def test_service(self): diff --git a/tests/ci_adapters/test_gitlabci.py b/tests/ci_adapters/test_gitlabci.py index 61e3e3ac..66cd7915 100644 --- a/tests/ci_adapters/test_gitlabci.py +++ b/tests/ci_adapters/test_gitlabci.py @@ -133,7 +133,6 @@ def test_pull_request_number(self, env_dict, expected, mocker): ], ) def test_slug(self, env_dict, expected, mocker): - mocker.patch.dict( os.environ, env_dict, diff --git a/tests/ci_adapters/test_herokuci.py b/tests/ci_adapters/test_herokuci.py index 092ad266..5dc75e30 100644 --- a/tests/ci_adapters/test_herokuci.py +++ b/tests/ci_adapters/test_herokuci.py @@ -73,7 +73,7 @@ def test_branch(self, env_dict, expected, mocker): assert actual == expected def test_raises_value_error_if_invalid_field(self): - with pytest.raises(ValueError) as ex: + with pytest.raises(ValueError): HerokuCIAdapter().get_fallback_value("some_random_key") def test_service(self): @@ -82,7 +82,7 @@ def test_service(self): ) def test_other_values_fallback_to_none(self): - assert HerokuCIAdapter()._get_slug() == None - assert HerokuCIAdapter()._get_build_url() == None - assert HerokuCIAdapter()._get_job_code() == None - assert HerokuCIAdapter()._get_pull_request_number() == None + assert HerokuCIAdapter()._get_slug() is None + assert HerokuCIAdapter()._get_build_url() is None + assert HerokuCIAdapter()._get_job_code() is None + assert HerokuCIAdapter()._get_pull_request_number() is None diff --git a/tests/ci_adapters/test_jenkins.py b/tests/ci_adapters/test_jenkins.py index 69f91454..525f7d3e 100644 --- a/tests/ci_adapters/test_jenkins.py +++ b/tests/ci_adapters/test_jenkins.py @@ -40,18 +40,6 @@ def test_build_url(self, env_dict, expected, mocker): actual = JenkinsAdapter().get_fallback_value(FallbackFieldEnum.build_url) assert actual == expected - @pytest.mark.parametrize( - "env_dict,expected", - [ - ({}, None), - ({JenkinsCIEnvEnum.BUILD_URL: "url"}, "url"), - ], - ) - def test_build_url(self, env_dict, expected, mocker): - mocker.patch.dict(os.environ, env_dict) - actual = JenkinsAdapter().get_fallback_value(FallbackFieldEnum.build_url) - assert actual == expected - @pytest.mark.parametrize( "env_dict,expected", [ @@ -99,6 +87,6 @@ def test_service(self): ) def test_none_values(self): - JenkinsAdapter().get_fallback_value(FallbackFieldEnum.slug) == None - JenkinsAdapter().get_fallback_value(FallbackFieldEnum.commit_sha) == None - JenkinsAdapter().get_fallback_value(FallbackFieldEnum.job_code) == None + JenkinsAdapter().get_fallback_value(FallbackFieldEnum.slug) is None + JenkinsAdapter().get_fallback_value(FallbackFieldEnum.commit_sha) is None + JenkinsAdapter().get_fallback_value(FallbackFieldEnum.job_code) is None diff --git a/tests/ci_adapters/test_local.py b/tests/ci_adapters/test_local.py index d92f1057..36c8022f 100644 --- a/tests/ci_adapters/test_local.py +++ b/tests/ci_adapters/test_local.py @@ -27,7 +27,7 @@ def test_detect_git_not_installed(self, mocker): "codecov_cli.helpers.ci_adapters.local.subprocess.run", return_value=mocker.MagicMock(returncode=1), ) - assert LocalAdapter().detect() == False + assert not LocalAdapter().detect() mocked_subprocess.assert_called_once() @pytest.mark.parametrize( diff --git a/tests/commands/test_invoke_labelanalysis.py b/tests/commands/test_invoke_labelanalysis.py index 230e5b12..729f2965 100644 --- a/tests/commands/test_invoke_labelanalysis.py +++ b/tests/commands/test_invoke_labelanalysis.py @@ -228,7 +228,7 @@ def test_invoke_label_analysis( ): mock_get_runner = get_labelanalysis_deps["mock_get_runner"] fake_runner = get_labelanalysis_deps["fake_runner"] - collected_labels = get_labelanalysis_deps["collected_labels"] + _ = get_labelanalysis_deps["collected_labels"] label_analysis_result = { "present_report_labels": ["test_present"], @@ -349,7 +349,7 @@ def test_invoke_label_analysis_dry_run( def test_invoke_label_analysis_dry_run_pytest_format( self, get_labelanalysis_deps, mocker ): - mock_get_runner = get_labelanalysis_deps["mock_get_runner"] + _ = get_labelanalysis_deps["mock_get_runner"] fake_runner = get_labelanalysis_deps["fake_runner"] label_analysis_result = { @@ -733,7 +733,7 @@ def test_first_labelanalysis_request_fails_but_second_works( ): mock_get_runner = get_labelanalysis_deps["mock_get_runner"] fake_runner = get_labelanalysis_deps["fake_runner"] - collected_labels = get_labelanalysis_deps["collected_labels"] + _ = get_labelanalysis_deps["collected_labels"] label_analysis_result = { "present_report_labels": ["test_present"], diff --git a/tests/commands/test_process_test_results.py b/tests/commands/test_process_test_results.py index 9ef89adc..ea9faaba 100644 --- a/tests/commands/test_process_test_results.py +++ b/tests/commands/test_process_test_results.py @@ -11,8 +11,7 @@ def test_process_test_results( mocker, tmpdir, ): - - tmp_file = tmpdir.mkdir("folder").join("summary.txt") + _ = tmpdir.mkdir("folder").join("summary.txt") mocker.patch.dict( os.environ, @@ -21,7 +20,7 @@ def test_process_test_results( "GITHUB_REF": "pull/fake/pull", }, ) - mocked_post = mocker.patch( + _ = mocker.patch( "codecov_cli.commands.process_test_results.send_post_request", return_value=RequestResult( status_code=200, error=None, warnings=[], text="yay it worked" @@ -48,8 +47,7 @@ def test_process_test_results_create_github_message( mocker, tmpdir, ): - - tmp_file = tmpdir.mkdir("folder").join("summary.txt") + _ = tmpdir.mkdir("folder").join("summary.txt") mocker.patch.dict( os.environ, @@ -95,8 +93,7 @@ def test_process_test_results_update_github_message( mocker, tmpdir, ): - - tmp_file = tmpdir.mkdir("folder").join("summary.txt") + _ = tmpdir.mkdir("folder").join("summary.txt") mocker.patch.dict( os.environ, @@ -166,8 +163,7 @@ def test_process_test_results_errors_getting_comments( mocker, tmpdir, ): - - tmp_file = tmpdir.mkdir("folder").join("summary.txt") + _ = tmpdir.mkdir("folder").join("summary.txt") mocker.patch.dict( os.environ, @@ -187,7 +183,7 @@ def test_process_test_results_errors_getting_comments( ), ) - mocked_post = mocker.patch( + _ = mocker.patch( "codecov_cli.commands.process_test_results.send_post_request", return_value=RequestResult( status_code=200, error=None, warnings=[], text="yay it worked" @@ -211,7 +207,7 @@ def test_process_test_results_errors_getting_comments( def test_process_test_results_non_existent_file(mocker, tmpdir): - tmp_file = tmpdir.mkdir("folder").join("summary.txt") + _ = tmpdir.mkdir("folder").join("summary.txt") mocker.patch.dict( os.environ, @@ -220,7 +216,7 @@ def test_process_test_results_non_existent_file(mocker, tmpdir): "GITHUB_REF": "pull/fake/pull", }, ) - mocked_post = mocker.patch( + _ = mocker.patch( "codecov_cli.commands.process_test_results.send_post_request", return_value=RequestResult( status_code=200, error=None, warnings=[], text="yay it worked" @@ -248,7 +244,7 @@ def test_process_test_results_non_existent_file(mocker, tmpdir): def test_process_test_results_missing_repo(mocker, tmpdir): - tmp_file = tmpdir.mkdir("folder").join("summary.txt") + _ = tmpdir.mkdir("folder").join("summary.txt") mocker.patch.dict( os.environ, @@ -258,7 +254,7 @@ def test_process_test_results_missing_repo(mocker, tmpdir): ) if "GITHUB_REPOSITORY" in os.environ: del os.environ["GITHUB_REPOSITORY"] - mocked_post = mocker.patch( + _ = mocker.patch( "codecov_cli.commands.process_test_results.send_post_request", return_value=RequestResult( status_code=200, error=None, warnings=[], text="yay it worked" @@ -288,7 +284,7 @@ def test_process_test_results_missing_repo(mocker, tmpdir): def test_process_test_results_missing_ref(mocker, tmpdir): - tmp_file = tmpdir.mkdir("folder").join("summary.txt") + _ = tmpdir.mkdir("folder").join("summary.txt") mocker.patch.dict( os.environ, @@ -299,7 +295,7 @@ def test_process_test_results_missing_ref(mocker, tmpdir): if "GITHUB_REF" in os.environ: del os.environ["GITHUB_REF"] - mocked_post = mocker.patch( + _ = mocker.patch( "codecov_cli.commands.process_test_results.send_post_request", return_value=RequestResult( status_code=200, error=None, warnings=[], text="yay it worked" diff --git a/tests/data/reports_examples.py b/tests/data/reports_examples.py index 0450a1dd..9c638e43 100644 --- a/tests/data/reports_examples.py +++ b/tests/data/reports_examples.py @@ -2,7 +2,6 @@ # Avoid parsing and removing indentation from multiline strings by defining them in the top level of this file - coverage_file_section_simple = b"""# path=flagtwo.coverage.xml diff --git a/tests/helpers/git_services/test_github.py b/tests/helpers/git_services/test_github.py index 05662766..b77ad832 100644 --- a/tests/helpers/git_services/test_github.py +++ b/tests/helpers/git_services/test_github.py @@ -71,4 +71,4 @@ def mock_request(*args, headers={}, **kwargs): ) slug = "codecov/codecov-cli" response = Github().get_pull_request(slug, 1) - assert response == None + assert response is None diff --git a/tests/helpers/test_config.py b/tests/helpers/test_config.py index 4ec4f513..c3d8d329 100644 --- a/tests/helpers/test_config.py +++ b/tests/helpers/test_config.py @@ -15,13 +15,13 @@ def test_load_config(mocker): def test_load_config_doesnt_exist(mocker): path = pathlib.Path("doesnt/exist") result = load_cli_config(path) - assert result == None + assert result is None def test_load_config_not_file(mocker): path = pathlib.Path("samples/") result = load_cli_config(path) - assert result == None + assert result is None def test_find_codecov_yaml(mocker): diff --git a/tests/helpers/test_encoder.py b/tests/helpers/test_encoder.py index 8e422614..cc185487 100644 --- a/tests/helpers/test_encoder.py +++ b/tests/helpers/test_encoder.py @@ -21,7 +21,7 @@ ], ) def test_encode_invalid_slug(slug): - with pytest.raises(ValueError) as ex: + with pytest.raises(ValueError): encode_slug(slug) @@ -77,7 +77,7 @@ def test_valid_slug(): ) def test_invalid_encoded_slug(slug): assert slug_encoded_incorrectly(slug) - with pytest.raises(ValueError) as ex: + with pytest.raises(ValueError): decode_slug(slug) diff --git a/tests/helpers/test_git.py b/tests/helpers/test_git.py index cca8a60b..77213c19 100644 --- a/tests/helpers/test_git.py +++ b/tests/helpers/test_git.py @@ -131,5 +131,5 @@ def test_parse_git_service_invalid_service(url): def test_get_git_service_class(): assert isinstance(git.get_git_service("github"), Github) - assert git.get_git_service("gitlab") == None - assert git.get_git_service("bitbucket") == None + assert git.get_git_service("gitlab") is None + assert git.get_git_service("bitbucket") is None diff --git a/tests/helpers/test_request.py b/tests/helpers/test_request.py index 92dd6e6d..7069068b 100644 --- a/tests/helpers/test_request.py +++ b/tests/helpers/test_request.py @@ -38,7 +38,7 @@ def test_log_error_no_raise(mocker): error=error, warnings=[], status_code=401, text="Unauthorized" ) log_warnings_and_errors_if_any(result, "Process", fail_on_error=False) - mock_log_error.assert_called_with(f"Process failed: Unauthorized") + mock_log_error.assert_called_with("Process failed: Unauthorized") def test_log_error_raise(mocker): @@ -51,7 +51,7 @@ def test_log_error_raise(mocker): ) with pytest.raises(SystemExit): log_warnings_and_errors_if_any(result, "Process", fail_on_error=True) - mock_log_error.assert_called_with(f"Process failed: Unauthorized") + mock_log_error.assert_called_with("Process failed: Unauthorized") def test_log_result_without_token(mocker): @@ -137,7 +137,7 @@ def test_request_retry(mocker, valid_response): def test_request_retry_too_many_errors(mocker): - mock_sleep = mocker.patch("codecov_cli.helpers.request.sleep") + _ = mocker.patch("codecov_cli.helpers.request.sleep") mocker.patch.object( requests, "post", @@ -150,7 +150,7 @@ def test_request_retry_too_many_errors(mocker): ], ) with pytest.raises(Exception) as exp: - resp = send_post_request("my_url") + _ = send_post_request("my_url") assert str(exp.value) == "Request failed after too many retries" diff --git a/tests/helpers/test_upload_sender.py b/tests/helpers/test_upload_sender.py index 8fe91819..1e164ef5 100644 --- a/tests/helpers/test_upload_sender.py +++ b/tests/helpers/test_upload_sender.py @@ -100,7 +100,7 @@ def mocked_upload_coverage_endpoint(mocked_responses): def mocked_test_results_endpoint(mocked_responses): resp = responses.Response( responses.POST, - f"https://ingest.codecov.io/upload/test_results/v1", + "https://ingest.codecov.io/upload/test_results/v1", status=200, json={ "raw_upload_location": "https://puturl.com", diff --git a/tests/helpers/test_versioning_systems.py b/tests/helpers/test_versioning_systems.py index cc74410f..52d13793 100644 --- a/tests/helpers/test_versioning_systems.py +++ b/tests/helpers/test_versioning_systems.py @@ -128,5 +128,5 @@ def test_list_relevant_files_fails_if_no_root_is_found(self, mocker): ) vs = GitVersioningSystem() - with pytest.raises(ValueError) as ex: + with pytest.raises(ValueError): vs.list_relevant_files() diff --git a/tests/plugins/test_compress_pycoverage_contexts.py b/tests/plugins/test_compress_pycoverage_contexts.py index 1dc8fc86..fe89a99f 100644 --- a/tests/plugins/test_compress_pycoverage_contexts.py +++ b/tests/plugins/test_compress_pycoverage_contexts.py @@ -172,7 +172,7 @@ class TestCompressPycoverageContexts(object): def test_default_options(self): plugin = CompressPycoverageContexts() assert plugin.config.file_to_compress == pathlib.Path("coverage.json") - assert plugin.config.delete_uncompressed == True + assert plugin.config.delete_uncompressed assert plugin.file_to_compress == pathlib.Path("coverage.json") assert plugin.file_to_write == pathlib.Path("coverage.codecov.json") @@ -183,7 +183,7 @@ def test_change_options(self): } plugin = CompressPycoverageContexts(config) assert plugin.config.file_to_compress == pathlib.Path("label.coverage.json") - assert plugin.config.delete_uncompressed == False + assert not plugin.config.delete_uncompressed assert plugin.file_to_compress == pathlib.Path("label.coverage.json") assert plugin.file_to_write == pathlib.Path("label.coverage.codecov.json") @@ -192,7 +192,7 @@ def test_run_preparation_fail_fast_no_file(self): res = plugin.run_preparation(None) assert res == PreparationPluginReturn( success=False, - messages=[f"File to compress coverage.json not found."], + messages=["File to compress coverage.json not found."], ) def test_run_preparation_fail_fast_path_not_file(self, tmp_path): diff --git a/tests/services/commit/test_commit_service.py b/tests/services/commit/test_commit_service.py index 2fd5157d..90af6ddf 100644 --- a/tests/services/commit/test_commit_service.py +++ b/tests/services/commit/test_commit_service.py @@ -156,7 +156,7 @@ def test_commit_sender_with_forked_repo(mocker): ) mocker.patch("os.environ", dict(TOKENLESS="user_forked_repo/codecov-cli:branch")) - res = send_commit_data( + _ = send_commit_data( "commit_sha", "parent_sha", "1", diff --git a/tests/services/static_analysis/test_analyse_file.py b/tests/services/static_analysis/test_analyse_file.py index 7a31e864..43269b16 100644 --- a/tests/services/static_analysis/test_analyse_file.py +++ b/tests/services/static_analysis/test_analyse_file.py @@ -53,6 +53,6 @@ def test_analyse_file_no_analyzer(mock_get_analyzer, mock_open): mock_open.return_value.__enter__.return_value.read.return_value = fake_contents config = {} res = analyze_file(config, file_name) - assert res == None + assert res is None mock_open.assert_called_with("filepath", "rb") mock_get_analyzer.assert_called_with(file_name, fake_contents) diff --git a/tests/services/upload/test_coverage_file_finder.py b/tests/services/upload/test_coverage_file_finder.py index 29261fa1..201701c8 100644 --- a/tests/services/upload/test_coverage_file_finder.py +++ b/tests/services/upload/test_coverage_file_finder.py @@ -357,7 +357,6 @@ def test_find_coverage_files_with_user_specified_files_not_found( def test_find_coverage_files_with_user_specified_files_in_default_ignored_folder( self, coverage_file_finder_fixture ): - ( project_root, coverage_file_finder, diff --git a/tests/services/upload/test_upload_service.py b/tests/services/upload/test_upload_service.py index 8de23367..2cf216d1 100644 --- a/tests/services/upload/test_upload_service.py +++ b/tests/services/upload/test_upload_service.py @@ -533,7 +533,7 @@ def side_effect(*args, **kwargs): ci_adapter.get_fallback_value.return_value = "service" with pytest.raises(click.ClickException) as exp: - res = do_upload_logic( + _ = do_upload_logic( cli_config, versioning_system, ci_adapter, From 87d5916c61ce4eca32679e25c713259eefac221e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 2 Dec 2024 20:22:13 +0100 Subject: [PATCH 30/30] Take Java JUnit conventions into account when detecting test files to upload (#550) --- codecov_cli/services/upload/file_finder.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codecov_cli/services/upload/file_finder.py b/codecov_cli/services/upload/file_finder.py index 9c0a89cc..232dde41 100644 --- a/codecov_cli/services/upload/file_finder.py +++ b/codecov_cli/services/upload/file_finder.py @@ -38,6 +38,8 @@ test_results_files_patterns = [ "*junit*.xml", "*test*.xml", + # the actual JUnit (Java) prefixes the tests with "TEST-" + "*TEST-*.xml" ] coverage_files_excluded_patterns = [