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.
- codecov_cli/__init__.py +3 -0
- codecov_cli/commands/__init__.py +0 -0
- codecov_cli/commands/base_picking.py +75 -0
- codecov_cli/commands/commit.py +72 -0
- codecov_cli/commands/create_report_result.py +41 -0
- codecov_cli/commands/empty_upload.py +80 -0
- codecov_cli/commands/get_report_results.py +50 -0
- codecov_cli/commands/labelanalysis.py +269 -0
- codecov_cli/commands/process_test_results.py +273 -0
- codecov_cli/commands/report.py +65 -0
- codecov_cli/commands/send_notifications.py +46 -0
- codecov_cli/commands/staticanalysis.py +62 -0
- codecov_cli/commands/upload.py +316 -0
- codecov_cli/commands/upload_coverage.py +186 -0
- codecov_cli/commands/upload_process.py +133 -0
- codecov_cli/fallbacks.py +41 -0
- codecov_cli/helpers/__init__.py +0 -0
- codecov_cli/helpers/args.py +31 -0
- codecov_cli/helpers/ci_adapters/__init__.py +63 -0
- codecov_cli/helpers/ci_adapters/appveyor_ci.py +54 -0
- codecov_cli/helpers/ci_adapters/azure_pipelines.py +44 -0
- codecov_cli/helpers/ci_adapters/base.py +102 -0
- codecov_cli/helpers/ci_adapters/bitbucket_ci.py +42 -0
- codecov_cli/helpers/ci_adapters/bitrise_ci.py +37 -0
- codecov_cli/helpers/ci_adapters/buildkite.py +45 -0
- codecov_cli/helpers/ci_adapters/circleci.py +47 -0
- codecov_cli/helpers/ci_adapters/cirrus_ci.py +36 -0
- codecov_cli/helpers/ci_adapters/cloudbuild.py +70 -0
- codecov_cli/helpers/ci_adapters/codebuild.py +49 -0
- codecov_cli/helpers/ci_adapters/droneci.py +36 -0
- codecov_cli/helpers/ci_adapters/github_actions.py +90 -0
- codecov_cli/helpers/ci_adapters/gitlab_ci.py +56 -0
- codecov_cli/helpers/ci_adapters/heroku.py +36 -0
- codecov_cli/helpers/ci_adapters/jenkins.py +38 -0
- codecov_cli/helpers/ci_adapters/local.py +39 -0
- codecov_cli/helpers/ci_adapters/teamcity.py +37 -0
- codecov_cli/helpers/ci_adapters/travis_ci.py +44 -0
- codecov_cli/helpers/ci_adapters/woodpeckerci.py +36 -0
- codecov_cli/helpers/config.py +66 -0
- codecov_cli/helpers/encoder.py +49 -0
- codecov_cli/helpers/folder_searcher.py +114 -0
- codecov_cli/helpers/git.py +97 -0
- codecov_cli/helpers/git_services/__init__.py +14 -0
- codecov_cli/helpers/git_services/github.py +40 -0
- codecov_cli/helpers/glob.py +146 -0
- codecov_cli/helpers/logging_utils.py +77 -0
- codecov_cli/helpers/options.py +51 -0
- codecov_cli/helpers/request.py +198 -0
- codecov_cli/helpers/upload_type.py +15 -0
- codecov_cli/helpers/validators.py +13 -0
- codecov_cli/helpers/versioning_systems.py +201 -0
- codecov_cli/main.py +99 -0
- codecov_cli/opentelemetry.py +26 -0
- codecov_cli/plugins/__init__.py +92 -0
- codecov_cli/plugins/compress_pycoverage_contexts.py +141 -0
- codecov_cli/plugins/gcov.py +69 -0
- codecov_cli/plugins/pycoverage.py +134 -0
- codecov_cli/plugins/types.py +8 -0
- codecov_cli/plugins/xcode.py +117 -0
- codecov_cli/runners/__init__.py +80 -0
- codecov_cli/runners/dan_runner.py +64 -0
- codecov_cli/runners/pytest_standard_runner.py +184 -0
- codecov_cli/runners/types.py +33 -0
- codecov_cli/services/__init__.py +0 -0
- codecov_cli/services/commit/__init__.py +86 -0
- codecov_cli/services/commit/base_picking.py +24 -0
- codecov_cli/services/empty_upload/__init__.py +42 -0
- codecov_cli/services/report/__init__.py +169 -0
- codecov_cli/services/upload/__init__.py +169 -0
- codecov_cli/services/upload/file_finder.py +320 -0
- codecov_cli/services/upload/legacy_upload_sender.py +132 -0
- codecov_cli/services/upload/network_finder.py +49 -0
- codecov_cli/services/upload/upload_collector.py +198 -0
- codecov_cli/services/upload/upload_sender.py +232 -0
- codecov_cli/services/upload_completion/__init__.py +38 -0
- codecov_cli/services/upload_coverage/__init__.py +93 -0
- codecov_cli/types.py +88 -0
- codecov_cli-11.0.0.dist-info/METADATA +298 -0
- codecov_cli-11.0.0.dist-info/RECORD +83 -0
- codecov_cli-11.0.0.dist-info/WHEEL +5 -0
- codecov_cli-11.0.0.dist-info/entry_points.txt +3 -0
- codecov_cli-11.0.0.dist-info/licenses/LICENSE +201 -0
- 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
|
+
)
|