codecov-cli 11.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. codecov_cli/__init__.py +3 -0
  2. codecov_cli/commands/__init__.py +0 -0
  3. codecov_cli/commands/base_picking.py +75 -0
  4. codecov_cli/commands/commit.py +72 -0
  5. codecov_cli/commands/create_report_result.py +41 -0
  6. codecov_cli/commands/empty_upload.py +80 -0
  7. codecov_cli/commands/get_report_results.py +50 -0
  8. codecov_cli/commands/labelanalysis.py +269 -0
  9. codecov_cli/commands/process_test_results.py +273 -0
  10. codecov_cli/commands/report.py +65 -0
  11. codecov_cli/commands/send_notifications.py +46 -0
  12. codecov_cli/commands/staticanalysis.py +62 -0
  13. codecov_cli/commands/upload.py +316 -0
  14. codecov_cli/commands/upload_coverage.py +186 -0
  15. codecov_cli/commands/upload_process.py +133 -0
  16. codecov_cli/fallbacks.py +41 -0
  17. codecov_cli/helpers/__init__.py +0 -0
  18. codecov_cli/helpers/args.py +31 -0
  19. codecov_cli/helpers/ci_adapters/__init__.py +63 -0
  20. codecov_cli/helpers/ci_adapters/appveyor_ci.py +54 -0
  21. codecov_cli/helpers/ci_adapters/azure_pipelines.py +44 -0
  22. codecov_cli/helpers/ci_adapters/base.py +102 -0
  23. codecov_cli/helpers/ci_adapters/bitbucket_ci.py +42 -0
  24. codecov_cli/helpers/ci_adapters/bitrise_ci.py +37 -0
  25. codecov_cli/helpers/ci_adapters/buildkite.py +45 -0
  26. codecov_cli/helpers/ci_adapters/circleci.py +47 -0
  27. codecov_cli/helpers/ci_adapters/cirrus_ci.py +36 -0
  28. codecov_cli/helpers/ci_adapters/cloudbuild.py +70 -0
  29. codecov_cli/helpers/ci_adapters/codebuild.py +49 -0
  30. codecov_cli/helpers/ci_adapters/droneci.py +36 -0
  31. codecov_cli/helpers/ci_adapters/github_actions.py +90 -0
  32. codecov_cli/helpers/ci_adapters/gitlab_ci.py +56 -0
  33. codecov_cli/helpers/ci_adapters/heroku.py +36 -0
  34. codecov_cli/helpers/ci_adapters/jenkins.py +38 -0
  35. codecov_cli/helpers/ci_adapters/local.py +39 -0
  36. codecov_cli/helpers/ci_adapters/teamcity.py +37 -0
  37. codecov_cli/helpers/ci_adapters/travis_ci.py +44 -0
  38. codecov_cli/helpers/ci_adapters/woodpeckerci.py +36 -0
  39. codecov_cli/helpers/config.py +66 -0
  40. codecov_cli/helpers/encoder.py +49 -0
  41. codecov_cli/helpers/folder_searcher.py +114 -0
  42. codecov_cli/helpers/git.py +97 -0
  43. codecov_cli/helpers/git_services/__init__.py +14 -0
  44. codecov_cli/helpers/git_services/github.py +40 -0
  45. codecov_cli/helpers/glob.py +146 -0
  46. codecov_cli/helpers/logging_utils.py +77 -0
  47. codecov_cli/helpers/options.py +51 -0
  48. codecov_cli/helpers/request.py +198 -0
  49. codecov_cli/helpers/upload_type.py +15 -0
  50. codecov_cli/helpers/validators.py +13 -0
  51. codecov_cli/helpers/versioning_systems.py +201 -0
  52. codecov_cli/main.py +99 -0
  53. codecov_cli/opentelemetry.py +26 -0
  54. codecov_cli/plugins/__init__.py +92 -0
  55. codecov_cli/plugins/compress_pycoverage_contexts.py +141 -0
  56. codecov_cli/plugins/gcov.py +69 -0
  57. codecov_cli/plugins/pycoverage.py +134 -0
  58. codecov_cli/plugins/types.py +8 -0
  59. codecov_cli/plugins/xcode.py +117 -0
  60. codecov_cli/runners/__init__.py +80 -0
  61. codecov_cli/runners/dan_runner.py +64 -0
  62. codecov_cli/runners/pytest_standard_runner.py +184 -0
  63. codecov_cli/runners/types.py +33 -0
  64. codecov_cli/services/__init__.py +0 -0
  65. codecov_cli/services/commit/__init__.py +86 -0
  66. codecov_cli/services/commit/base_picking.py +24 -0
  67. codecov_cli/services/empty_upload/__init__.py +42 -0
  68. codecov_cli/services/report/__init__.py +169 -0
  69. codecov_cli/services/upload/__init__.py +169 -0
  70. codecov_cli/services/upload/file_finder.py +320 -0
  71. codecov_cli/services/upload/legacy_upload_sender.py +132 -0
  72. codecov_cli/services/upload/network_finder.py +49 -0
  73. codecov_cli/services/upload/upload_collector.py +198 -0
  74. codecov_cli/services/upload/upload_sender.py +232 -0
  75. codecov_cli/services/upload_completion/__init__.py +38 -0
  76. codecov_cli/services/upload_coverage/__init__.py +93 -0
  77. codecov_cli/types.py +88 -0
  78. codecov_cli-11.0.0.dist-info/METADATA +298 -0
  79. codecov_cli-11.0.0.dist-info/RECORD +83 -0
  80. codecov_cli-11.0.0.dist-info/WHEEL +5 -0
  81. codecov_cli-11.0.0.dist-info/entry_points.txt +3 -0
  82. codecov_cli-11.0.0.dist-info/licenses/LICENSE +201 -0
  83. codecov_cli-11.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,320 @@
1
+ import logging
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Iterable, List, Optional, Pattern
5
+
6
+ import sentry_sdk
7
+
8
+ from codecov_cli.helpers.folder_searcher import globs_to_regex, search_files
9
+ from codecov_cli.helpers.upload_type import ReportType
10
+ from codecov_cli.types import UploadCollectionResultFile
11
+
12
+ logger = logging.getLogger("codecovcli")
13
+
14
+
15
+ coverage_files_patterns = [
16
+ "*.clover",
17
+ "*.codecov.*",
18
+ "*.gcov",
19
+ "*.lcov",
20
+ "*.lst",
21
+ "*coverage*.*",
22
+ "*Jacoco*.xml",
23
+ "clover.xml",
24
+ "cobertura.xml",
25
+ "codecov-result.json",
26
+ "codecov.*",
27
+ "cover.out",
28
+ "coverage-final.json",
29
+ "excoveralls.json",
30
+ "gcov.info",
31
+ "jacoco*.xml",
32
+ "lcov.dat",
33
+ "pylcov.dat",
34
+ "lcov.info",
35
+ "luacov.report.out",
36
+ "naxsi.info",
37
+ "nosetests.xml",
38
+ "report.xml",
39
+ "test_cov.xml",
40
+ ]
41
+
42
+ test_results_files_patterns = [
43
+ "*junit*.xml",
44
+ "*test*.xml",
45
+ # the actual JUnit (Java) prefixes the tests with "TEST-"
46
+ "*TEST-*.xml",
47
+ ]
48
+
49
+ coverage_files_excluded_patterns = [
50
+ "*.*js",
51
+ "*.SHA256SUM",
52
+ "*.am",
53
+ "*.bash",
54
+ "*.bat",
55
+ "*.bw",
56
+ "*.cfg",
57
+ "*.class",
58
+ "*.cmake",
59
+ "*.conf",
60
+ "*.coverage",
61
+ "*.cp",
62
+ "*.cpp",
63
+ "*.crt",
64
+ "*.csg",
65
+ "*.css",
66
+ "*.csv",
67
+ "*.dart",
68
+ "*.data",
69
+ "*.db",
70
+ "*.dox",
71
+ "*.ec",
72
+ "*.egg",
73
+ "*.egg-info",
74
+ "*.el",
75
+ "*.env",
76
+ "*.erb",
77
+ "*.err",
78
+ "*.exe",
79
+ "*.feature",
80
+ "*.ftl",
81
+ "*.gif",
82
+ "*.go",
83
+ "*.gradle",
84
+ "*.gz",
85
+ "*.h",
86
+ "*.html",
87
+ "*.in",
88
+ "*.jade",
89
+ "*.jar*",
90
+ "*.jpeg",
91
+ "*.jpg",
92
+ "*.js",
93
+ "*.less",
94
+ "*.library",
95
+ "*.log",
96
+ "*.m4",
97
+ "*.mak*",
98
+ "*.map",
99
+ "*.md",
100
+ "*.module",
101
+ "*.mp4",
102
+ "*.o",
103
+ "*.p12",
104
+ "*.pem",
105
+ "*.png",
106
+ "*.pom*",
107
+ "*.profdata",
108
+ "*.proto",
109
+ "*.prototxt",
110
+ "*.ps1",
111
+ "*.pth",
112
+ "*.py",
113
+ "*.pyc",
114
+ "*.pyo",
115
+ "*.rake",
116
+ "*.rb",
117
+ "*.rsp",
118
+ "*.rst",
119
+ "*.ru",
120
+ "*.sbt",
121
+ "*.scss",
122
+ "*.serialized",
123
+ "*.sh",
124
+ "*.sha256sum",
125
+ "*.snapshot",
126
+ "*.sql",
127
+ "*.svg",
128
+ "*.tar.tz",
129
+ "*.template",
130
+ "*.ts",
131
+ "*.whl",
132
+ "*.xcconfig",
133
+ "*.xcoverage.*",
134
+ "*.yaml",
135
+ "*.yml",
136
+ "*.zip",
137
+ "*/classycle/report.xml",
138
+ "*codecov.yml",
139
+ "*~",
140
+ ".*coveragerc",
141
+ ".coverage*",
142
+ ".ds_store",
143
+ ".git*",
144
+ ".nvmrc",
145
+ "codecov.SHA256SUM",
146
+ "codecov.SHA256SUM.sig",
147
+ "codecov.yaml",
148
+ "coverage-summary.json",
149
+ "createdFiles.lst",
150
+ "fullLocaleNames.lst",
151
+ "include.lst",
152
+ "inputFiles.lst",
153
+ "phpunit-code-coverage.xml",
154
+ "phpunit-coverage.xml",
155
+ "remapInstanbul.coverage*.json",
156
+ "scoverage.measurements.*",
157
+ "test-result-*-codecoverage.json",
158
+ "test_*_coverage.txt",
159
+ "testrunner-coverage*",
160
+ ]
161
+
162
+ test_results_files_excluded_patterns = (
163
+ coverage_files_patterns + coverage_files_excluded_patterns
164
+ )
165
+
166
+
167
+ default_folders_to_ignore = [
168
+ "vendor",
169
+ "bower_components",
170
+ ".circleci",
171
+ "conftest_*.c.gcov",
172
+ ".egg-info*",
173
+ ".env",
174
+ ".envs",
175
+ ".git",
176
+ ".go",
177
+ ".hg",
178
+ ".map",
179
+ ".marker",
180
+ ".tox",
181
+ ".venv",
182
+ ".venvs",
183
+ ".virtualenv",
184
+ ".virtualenvs",
185
+ ".yarn",
186
+ ".yarn-cache",
187
+ "__pycache__",
188
+ "env",
189
+ "envs",
190
+ "htmlcov",
191
+ "js/generated/coverage",
192
+ "node_modules",
193
+ "venv",
194
+ "venvs",
195
+ "virtualenv",
196
+ "virtualenvs",
197
+ "jspm_packages",
198
+ ".nyc_output",
199
+ ]
200
+
201
+
202
+ class FileFinder(object):
203
+ def __init__(
204
+ self,
205
+ search_root: Optional[Path] = None,
206
+ folders_to_ignore: Optional[List[Path]] = None,
207
+ explicitly_listed_files: Optional[List[Path]] = None,
208
+ disable_search: bool = False,
209
+ report_type: ReportType = ReportType.COVERAGE,
210
+ ):
211
+ self.search_root = search_root or Path(os.getcwd())
212
+ self.folders_to_ignore = (
213
+ [f.as_posix() for f in folders_to_ignore] if folders_to_ignore else []
214
+ )
215
+ self.explicitly_listed_files = explicitly_listed_files or []
216
+ self.disable_search = disable_search
217
+ self.report_type: ReportType = report_type
218
+
219
+ def find_files(self) -> List[UploadCollectionResultFile]:
220
+ with sentry_sdk.start_span(name="find_files"):
221
+ if self.report_type == ReportType.COVERAGE:
222
+ files_excluded_patterns = coverage_files_excluded_patterns
223
+ files_patterns = coverage_files_patterns
224
+ elif self.report_type == ReportType.TEST_RESULTS:
225
+ files_excluded_patterns = test_results_files_excluded_patterns
226
+ files_patterns = test_results_files_patterns
227
+ regex_patterns_to_exclude = globs_to_regex(files_excluded_patterns)
228
+ assert regex_patterns_to_exclude # this is never `None`
229
+ files_paths: Iterable[Path] = []
230
+ user_files_paths = []
231
+ if self.explicitly_listed_files:
232
+ user_files_paths = self.get_user_specified_files(
233
+ regex_patterns_to_exclude
234
+ )
235
+ if not self.disable_search:
236
+ regex_patterns_to_include = globs_to_regex(files_patterns)
237
+ assert regex_patterns_to_include # this is never `None`
238
+ files_paths = search_files(
239
+ self.search_root,
240
+ default_folders_to_ignore + self.folders_to_ignore,
241
+ filename_include_regex=regex_patterns_to_include,
242
+ filename_exclude_regex=regex_patterns_to_exclude,
243
+ )
244
+ result_files = [UploadCollectionResultFile(path) for path in files_paths]
245
+ user_result_files = [
246
+ UploadCollectionResultFile(path)
247
+ for path in user_files_paths
248
+ if user_files_paths
249
+ ]
250
+
251
+ user_result_files = []
252
+ for path in user_files_paths:
253
+ if os.path.isfile(path):
254
+ user_result_files.append(UploadCollectionResultFile(path))
255
+ else:
256
+ logger.warning(
257
+ f'File "{path}" could not be found or does not exist. Please enter in the full path or from the search root "{self.search_root}"',
258
+ )
259
+
260
+ return list(set(result_files + user_result_files))
261
+
262
+ def get_user_specified_files(self, regex_patterns_to_exclude: Pattern):
263
+ user_filenames_to_include = []
264
+ files_excluded_but_user_includes = []
265
+ for file in self.explicitly_listed_files:
266
+ user_filenames_to_include.append(file.name)
267
+ if regex_patterns_to_exclude.match(file.name):
268
+ files_excluded_but_user_includes.append(file.as_posix())
269
+ if files_excluded_but_user_includes:
270
+ logger.warning(
271
+ "Some files being explicitly added are found in the list of excluded files for upload. We are still going to search for the explicitly added files.",
272
+ extra=dict(
273
+ extra_log_attributes=dict(files=files_excluded_but_user_includes)
274
+ ),
275
+ )
276
+ regex_patterns_to_include = globs_to_regex(user_filenames_to_include)
277
+ multipart_include_regex = globs_to_regex(
278
+ [path.resolve().as_posix() for path in self.explicitly_listed_files]
279
+ )
280
+ user_files_paths = list(
281
+ search_files(
282
+ self.search_root,
283
+ self.folders_to_ignore,
284
+ filename_include_regex=regex_patterns_to_include,
285
+ multipart_include_regex=multipart_include_regex,
286
+ )
287
+ )
288
+ not_found_files = []
289
+ user_files_paths_resolved = [path.resolve() for path in user_files_paths]
290
+ for filepath in self.explicitly_listed_files:
291
+ if filepath.resolve() not in user_files_paths_resolved:
292
+ ## The file given might be linked or in a parent dir, check to see if it exists
293
+ if filepath.exists():
294
+ user_files_paths.append(filepath)
295
+ else:
296
+ not_found_files.append(filepath)
297
+
298
+ if not_found_files:
299
+ logger.warning(
300
+ "Some files were not found",
301
+ extra=dict(extra_log_attributes=dict(not_found_files=not_found_files)),
302
+ )
303
+
304
+ return user_files_paths
305
+
306
+
307
+ def select_file_finder(
308
+ root_folder_to_search,
309
+ folders_to_ignore,
310
+ explicitly_listed_files,
311
+ disable_search,
312
+ report_type: ReportType = ReportType.COVERAGE,
313
+ ):
314
+ return FileFinder(
315
+ root_folder_to_search,
316
+ folders_to_ignore,
317
+ explicitly_listed_files,
318
+ disable_search,
319
+ report_type,
320
+ )
@@ -0,0 +1,132 @@
1
+ import logging
2
+ import typing
3
+ from dataclasses import dataclass
4
+
5
+ import sentry_sdk
6
+
7
+ from codecov_cli import __version__ as codecov_cli_version
8
+ from codecov_cli.helpers.config import LEGACY_CODECOV_API_URL
9
+ from codecov_cli.helpers.request import send_post_request, send_put_request
10
+ from codecov_cli.types import UploadCollectionResult, UploadCollectionResultFile
11
+
12
+ logger = logging.getLogger("codecovcli")
13
+
14
+
15
+ @dataclass
16
+ class UploadSendingResultWarning(object):
17
+ __slots__ = ("message",)
18
+ message: str
19
+
20
+
21
+ @dataclass
22
+ class UploadSendingError(object):
23
+ __slots__ = ("code", "params", "description")
24
+ code: str
25
+ params: typing.Dict
26
+ description: str
27
+
28
+
29
+ @dataclass
30
+ class UploadSendingResult(object):
31
+ __slots__ = ("error", "warnings")
32
+ error: typing.Optional[UploadSendingError]
33
+ warnings: typing.List[UploadSendingResultWarning]
34
+
35
+
36
+ class LegacyUploadSender(object):
37
+ def send_upload_data(
38
+ self,
39
+ upload_data: UploadCollectionResult,
40
+ commit_sha: str,
41
+ token: str,
42
+ env_vars: typing.Dict[str, str],
43
+ name: typing.Optional[str] = None,
44
+ branch: typing.Optional[str] = None,
45
+ slug: typing.Optional[str] = None,
46
+ pull_request_number: typing.Optional[str] = None,
47
+ build_code: typing.Optional[str] = None,
48
+ build_url: typing.Optional[str] = None,
49
+ job_code: typing.Optional[str] = None,
50
+ flags: typing.List[str] = None,
51
+ ci_service: typing.Optional[str] = None,
52
+ enterprise_url: typing.Optional[str] = None,
53
+ args: dict = None,
54
+ **kwargs,
55
+ ) -> UploadSendingResult:
56
+ with sentry_sdk.start_span(name="upload_legacy"):
57
+ params = {
58
+ "package": f"codecov-cli/{codecov_cli_version}",
59
+ "commit": commit_sha,
60
+ "build": build_code,
61
+ "build_url": build_url,
62
+ "branch": branch,
63
+ "name": name,
64
+ "slug": slug,
65
+ "service": ci_service,
66
+ "flags": flags,
67
+ "pr": pull_request_number,
68
+ "job": job_code,
69
+ }
70
+
71
+ if token:
72
+ headers = {"X-Upload-Token": token}
73
+ else:
74
+ logger.warning("Token is empty.")
75
+ headers = {"X-Upload-Token": ""}
76
+
77
+ data = {
78
+ "cli_args": args,
79
+ }
80
+
81
+ upload_url = enterprise_url or LEGACY_CODECOV_API_URL
82
+ resp = send_post_request(
83
+ f"{upload_url}/upload/v4", data=data, headers=headers, params=params
84
+ )
85
+ if resp.status_code >= 400:
86
+ return resp
87
+ result_url, put_url = resp.text.split("\n")
88
+
89
+ reports_payload = self._generate_payload(upload_data, env_vars)
90
+ resp = send_put_request(put_url, data=reports_payload)
91
+ return resp
92
+
93
+ def _generate_payload(
94
+ self, upload_data: UploadCollectionResult, env_vars: typing.Dict[str, str]
95
+ ) -> bytes:
96
+ env_vars_section = self._generate_env_vars_section(env_vars)
97
+ network_section = self._generate_network_section(upload_data)
98
+ coverage_files_section = self._generate_coverage_files_section(upload_data)
99
+
100
+ return b"".join([env_vars_section, network_section, coverage_files_section])
101
+
102
+ def _generate_env_vars_section(self, env_vars) -> bytes:
103
+ filtered_env_vars = {
104
+ key: value for key, value in env_vars.items() if value is not None
105
+ }
106
+
107
+ if not filtered_env_vars:
108
+ return b""
109
+
110
+ env_vars_section = "".join(
111
+ f"{env_var}={value}\n" for env_var, value in filtered_env_vars.items()
112
+ )
113
+ return env_vars_section.encode() + b"<<<<<< ENV\n"
114
+
115
+ def _generate_network_section(self, upload_data: UploadCollectionResult) -> bytes:
116
+ network_files = upload_data.network
117
+
118
+ if not network_files:
119
+ return b""
120
+
121
+ network_files_section = "".join(file + "\n" for file in network_files)
122
+ return network_files_section.encode() + b"<<<<<< network\n"
123
+
124
+ def _generate_coverage_files_section(self, upload_data: UploadCollectionResult):
125
+ return b"".join(self._format_coverage_file(file) for file in upload_data.files)
126
+
127
+ def _format_coverage_file(self, file: UploadCollectionResultFile) -> bytes:
128
+ header = b"# path=" + file.get_filename().encode() + b"\n"
129
+ file_content = file.get_content() + b"\n"
130
+ file_end = b"<<<<<< EOF\n"
131
+
132
+ return header + file_content + file_end
@@ -0,0 +1,49 @@
1
+ import pathlib
2
+ import typing
3
+
4
+ from codecov_cli.helpers.versioning_systems import VersioningSystemInterface
5
+
6
+
7
+ class NetworkFinder(object):
8
+ def __init__(
9
+ self,
10
+ versioning_system: VersioningSystemInterface,
11
+ recurse_submodules: bool,
12
+ network_filter: typing.Optional[str],
13
+ network_prefix: typing.Optional[str],
14
+ network_root_folder: pathlib.Path,
15
+ ):
16
+ self.versioning_system = versioning_system
17
+ self.recurse_submodules = recurse_submodules
18
+ self.network_filter = network_filter
19
+ self.network_prefix = network_prefix
20
+ self.network_root_folder = network_root_folder
21
+
22
+ def find_files(self, ignore_filters=False) -> typing.List[str]:
23
+ files = self.versioning_system.list_relevant_files(
24
+ self.network_root_folder, self.recurse_submodules
25
+ )
26
+
27
+ if files and not ignore_filters:
28
+ if self.network_filter:
29
+ files = [file for file in files if file.startswith(self.network_filter)]
30
+ if self.network_prefix:
31
+ files = [self.network_prefix + file for file in files]
32
+
33
+ return files
34
+
35
+
36
+ def select_network_finder(
37
+ versioning_system: VersioningSystemInterface,
38
+ recurse_submodules: bool,
39
+ network_filter: typing.Optional[str],
40
+ network_prefix: typing.Optional[str],
41
+ network_root_folder: pathlib.Path,
42
+ ):
43
+ return NetworkFinder(
44
+ versioning_system,
45
+ recurse_submodules,
46
+ network_filter,
47
+ network_prefix,
48
+ network_root_folder,
49
+ )
@@ -0,0 +1,198 @@
1
+ import logging
2
+ import pathlib
3
+ import re
4
+ import typing
5
+ import uuid
6
+ from collections import namedtuple
7
+ from fnmatch import fnmatch
8
+
9
+ import click
10
+ import sentry_sdk
11
+
12
+ from codecov_cli.helpers.upload_type import ReportType
13
+ from codecov_cli.services.upload.file_finder import FileFinder
14
+ from codecov_cli.services.upload.network_finder import NetworkFinder
15
+ from codecov_cli.types import (
16
+ PreparationPluginInterface,
17
+ UploadCollectionResult,
18
+ UploadCollectionResultFileFixer,
19
+ )
20
+
21
+ logger = logging.getLogger("codecovcli")
22
+
23
+ fix_patterns_to_apply = namedtuple(
24
+ "fix_patterns_to_apply", ["without_reason", "with_reason", "eof"]
25
+ )
26
+
27
+
28
+ class UploadCollector(object):
29
+ def __init__(
30
+ self,
31
+ preparation_plugins: typing.List[PreparationPluginInterface],
32
+ network_finder: NetworkFinder,
33
+ file_finder: FileFinder,
34
+ plugin_config: dict,
35
+ disable_file_fixes: bool = False,
36
+ ):
37
+ self.preparation_plugins = preparation_plugins
38
+ self.network_finder = network_finder
39
+ self.file_finder = file_finder
40
+ self.disable_file_fixes = disable_file_fixes
41
+ self.plugin_config = plugin_config
42
+
43
+ def _produce_file_fixes(
44
+ self, files: typing.List[str]
45
+ ) -> typing.List[UploadCollectionResultFileFixer]:
46
+ if not files or self.disable_file_fixes:
47
+ return []
48
+ # patterns that we don't need to specify a reason for
49
+ empty_line_regex = re.compile(r"^\s*$")
50
+ comment_regex = re.compile(r"^\s*\/\/.*$")
51
+ bracket_regex = re.compile(r"^\s*[\{\}]\s*(\/\/.*)?$")
52
+ list_regex = re.compile(r"^\s*[\]\[]\s*(\/\/.*)?$")
53
+ parenthesis_regex = re.compile(r"^\s*[\(\)]\s*(\/\/.*)?$")
54
+ go_function_regex = re.compile(r"^\s*func\s*[\{]\s*(\/\/.*)?$")
55
+ php_end_bracket_regex = re.compile(r"^\s*\);\s*(\/\/.*)?$")
56
+
57
+ # patterns to specify a reason for
58
+ comment_block_regex = re.compile(r"^\s*(\/\*|\*\/)\s*$")
59
+ lcov_excel_regex = re.compile(r"\/\/ LCOV_EXCL")
60
+
61
+ kt_patterns_to_apply = fix_patterns_to_apply(
62
+ [bracket_regex, parenthesis_regex], [comment_block_regex], True
63
+ )
64
+ go_patterns_to_apply = fix_patterns_to_apply(
65
+ [empty_line_regex, comment_regex, bracket_regex, go_function_regex],
66
+ [comment_block_regex],
67
+ False,
68
+ )
69
+ dart_patterns_to_apply = fix_patterns_to_apply(
70
+ [bracket_regex],
71
+ [],
72
+ False,
73
+ )
74
+ php_patterns_to_apply = fix_patterns_to_apply(
75
+ [bracket_regex, list_regex, php_end_bracket_regex],
76
+ [],
77
+ False,
78
+ )
79
+ cpp_swift_vala_patterns_to_apply = fix_patterns_to_apply(
80
+ [empty_line_regex, bracket_regex],
81
+ [lcov_excel_regex],
82
+ False,
83
+ )
84
+
85
+ file_regex_patterns = {
86
+ "*.kt": kt_patterns_to_apply,
87
+ "*.go": go_patterns_to_apply,
88
+ "*.dart": dart_patterns_to_apply,
89
+ "*.php": php_patterns_to_apply,
90
+ "*.c": cpp_swift_vala_patterns_to_apply,
91
+ "*.cpp": cpp_swift_vala_patterns_to_apply,
92
+ "*.cxx": cpp_swift_vala_patterns_to_apply,
93
+ "*.h": cpp_swift_vala_patterns_to_apply,
94
+ "*.hpp": cpp_swift_vala_patterns_to_apply,
95
+ "*.m": cpp_swift_vala_patterns_to_apply,
96
+ "*.swift": cpp_swift_vala_patterns_to_apply,
97
+ "*.vala": cpp_swift_vala_patterns_to_apply,
98
+ }
99
+
100
+ result = []
101
+ for filename in files:
102
+ for glob, fix_patterns in file_regex_patterns.items():
103
+ if fnmatch(filename, glob):
104
+ result.append(self._get_file_fixes(filename, fix_patterns))
105
+ break
106
+
107
+ return result
108
+
109
+ def _get_file_fixes(
110
+ self, filename: str, fix_patterns_to_apply: fix_patterns_to_apply
111
+ ) -> UploadCollectionResultFileFixer:
112
+ path = pathlib.Path(filename)
113
+ fixed_lines_without_reason = set()
114
+ fixed_lines_with_reason = set()
115
+ eof = None
116
+
117
+ try:
118
+ with open(filename, "r", encoding="utf-8") as f:
119
+ # If lineno is unset that means that the
120
+ # file is empty thus the eof should be 0
121
+ # so lineno will be set to -1 here
122
+ lineno = -1
123
+ # overwrite lineno in this for loop
124
+ # if f is empty, lineno stays at -1
125
+ for lineno, line_content in enumerate(f):
126
+ if any(
127
+ pattern.match(line_content)
128
+ for pattern in fix_patterns_to_apply.with_reason
129
+ ):
130
+ fixed_lines_with_reason.add((lineno + 1, line_content))
131
+ elif any(
132
+ pattern.match(line_content)
133
+ for pattern in fix_patterns_to_apply.without_reason
134
+ ):
135
+ fixed_lines_without_reason.add(lineno + 1)
136
+ if fix_patterns_to_apply.eof:
137
+ eof = lineno + 1
138
+ except UnicodeDecodeError as err:
139
+ logger.warning(
140
+ f"There was an issue decoding: {filename}, file fixes were not applied to this file.",
141
+ extra=dict(
142
+ encoding=err.encoding,
143
+ reason=err.reason,
144
+ ),
145
+ )
146
+ except IsADirectoryError:
147
+ logger.info(f"Skipping {filename}, found a directory not a file")
148
+
149
+ return UploadCollectionResultFileFixer(
150
+ path, fixed_lines_without_reason, fixed_lines_with_reason, eof
151
+ )
152
+
153
+ def generate_upload_data(
154
+ self, report_type: ReportType = ReportType.COVERAGE
155
+ ) -> UploadCollectionResult:
156
+ with sentry_sdk.start_span(name="upload_collector"):
157
+ for prep in self.preparation_plugins:
158
+ logger.debug(f"Running preparation plugin: {type(prep)}")
159
+ prep.run_preparation(self)
160
+ logger.debug("Collecting relevant files")
161
+ with sentry_sdk.start_span(name="file_collector"):
162
+ network = self.network_finder.find_files()
163
+ unfiltered_network = self.network_finder.find_files(True)
164
+ report_files = self.file_finder.find_files()
165
+ logger.info(
166
+ f"Found {len(report_files)} {report_type.value} files to report"
167
+ )
168
+ logger.debug(
169
+ f"Found {len(network)} network files to report, ({len(unfiltered_network)} without filtering)"
170
+ )
171
+ if not report_files:
172
+ if report_type == ReportType.TEST_RESULTS:
173
+ error_message = "No JUnit XML reports found. Please review our documentation (https://docs.codecov.com/docs/test-result-ingestion-beta) to generate and upload the file."
174
+ logger.error(error_message)
175
+ return UploadCollectionResult(
176
+ network=network,
177
+ files=[],
178
+ file_fixes=[],
179
+ )
180
+ else:
181
+ error_message = "No coverage reports found. Please make sure you're generating reports successfully."
182
+ raise click.ClickException(
183
+ click.style(
184
+ error_message,
185
+ fg="red",
186
+ )
187
+ )
188
+ for file in report_files:
189
+ logger.info(f"> {file}")
190
+ return UploadCollectionResult(
191
+ network=network,
192
+ files=report_files,
193
+ file_fixes=(
194
+ self._produce_file_fixes(unfiltered_network)
195
+ if report_type == ReportType.COVERAGE
196
+ else []
197
+ ),
198
+ )