Skip to content
This repository was archived by the owner on Jul 16, 2025. It is now read-only.

Commit 3333bcb

Browse files
feat: add gcov capabilities (#536)
* first pass * fix: update tests
1 parent 6efe50c commit 3333bcb

File tree

10 files changed

+187
-82
lines changed

10 files changed

+187
-82
lines changed

codecov_cli/commands/upload.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,22 @@ def _turn_env_vars_into_dict(ctx, params, value):
178178
"--network-prefix",
179179
help="Specify a prefix on files listed in the network section of the Codecov report. Useful to help resolve path fixing",
180180
),
181+
click.option(
182+
"--gcov-args",
183+
help="Extra arguments to pass to gcov",
184+
),
185+
click.option(
186+
"--gcov-ignore",
187+
help="Paths to ignore during gcov gathering",
188+
),
189+
click.option(
190+
"--gcov-include",
191+
help="Paths to include during gcov gathering",
192+
),
193+
click.option(
194+
"--gcov-executable",
195+
help="gcov executable to run. Defaults to 'gcov'",
196+
),
181197
]
182198

183199

@@ -207,6 +223,10 @@ def do_upload(
207223
files_search_explicitly_listed_files: typing.List[pathlib.Path],
208224
files_search_root_folder: pathlib.Path,
209225
flags: typing.List[str],
226+
gcov_args: typing.Optional[str],
227+
gcov_executable: typing.Optional[str],
228+
gcov_ignore: typing.Optional[str],
229+
gcov_include: typing.Optional[str],
210230
git_service: typing.Optional[str],
211231
handle_no_reports_found: bool,
212232
job_code: typing.Optional[str],
@@ -251,6 +271,10 @@ def do_upload(
251271
files_search_explicitly_listed_files=list(files_search_explicitly_listed_files),
252272
files_search_root_folder=files_search_root_folder,
253273
flags=flags,
274+
gcov_args=gcov_args,
275+
gcov_executable=gcov_executable,
276+
gcov_ignore=gcov_ignore,
277+
gcov_include=gcov_include,
254278
git_service=git_service,
255279
handle_no_reports_found=handle_no_reports_found,
256280
job_code=job_code,

codecov_cli/commands/upload_process.py

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,34 +25,38 @@
2525
@click.pass_context
2626
def upload_process(
2727
ctx: CommandContext,
28-
commit_sha: str,
29-
report_code: str,
28+
branch: typing.Optional[str],
3029
build_code: typing.Optional[str],
3130
build_url: typing.Optional[str],
32-
job_code: typing.Optional[str],
31+
commit_sha: str,
32+
disable_file_fixes: bool,
33+
disable_search: bool,
34+
dry_run: bool,
3335
env_vars: typing.Dict[str, str],
36+
fail_on_error: bool,
37+
files_search_exclude_folders: typing.List[pathlib.Path],
38+
files_search_explicitly_listed_files: typing.List[pathlib.Path],
39+
files_search_root_folder: pathlib.Path,
3440
flags: typing.List[str],
41+
gcov_args: typing.Optional[str],
42+
gcov_executable: typing.Optional[str],
43+
gcov_ignore: typing.Optional[str],
44+
gcov_include: typing.Optional[str],
45+
git_service: typing.Optional[str],
46+
handle_no_reports_found: bool,
47+
job_code: typing.Optional[str],
3548
name: typing.Optional[str],
3649
network_filter: typing.Optional[str],
3750
network_prefix: typing.Optional[str],
3851
network_root_folder: pathlib.Path,
39-
files_search_root_folder: pathlib.Path,
40-
files_search_exclude_folders: typing.List[pathlib.Path],
41-
files_search_explicitly_listed_files: typing.List[pathlib.Path],
42-
disable_search: bool,
43-
disable_file_fixes: bool,
44-
token: typing.Optional[str],
52+
parent_sha: typing.Optional[str],
4553
plugin_names: typing.List[str],
46-
branch: typing.Optional[str],
47-
slug: typing.Optional[str],
4854
pull_request_number: typing.Optional[str],
49-
use_legacy_uploader: bool,
50-
fail_on_error: bool,
51-
dry_run: bool,
52-
git_service: typing.Optional[str],
53-
parent_sha: typing.Optional[str],
54-
handle_no_reports_found: bool,
55+
report_code: str,
5556
report_type: str,
57+
slug: typing.Optional[str],
58+
token: typing.Optional[str],
59+
use_legacy_uploader: bool,
5660
):
5761
args = get_cli_args(ctx)
5862
logger.debug(
@@ -85,31 +89,35 @@ def upload_process(
8589
)
8690
ctx.invoke(
8791
do_upload,
88-
commit_sha=commit_sha,
89-
report_code=report_code,
92+
branch=branch,
9093
build_code=build_code,
9194
build_url=build_url,
92-
job_code=job_code,
95+
commit_sha=commit_sha,
96+
disable_file_fixes=disable_file_fixes,
97+
disable_search=disable_search,
98+
dry_run=dry_run,
9399
env_vars=env_vars,
100+
fail_on_error=fail_on_error,
101+
files_search_exclude_folders=files_search_exclude_folders,
102+
files_search_explicitly_listed_files=files_search_explicitly_listed_files,
103+
files_search_root_folder=files_search_root_folder,
94104
flags=flags,
105+
gcov_args=gcov_args,
106+
gcov_executable=gcov_executable,
107+
gcov_ignore=gcov_ignore,
108+
gcov_include=gcov_include,
109+
git_service=git_service,
110+
handle_no_reports_found=handle_no_reports_found,
111+
job_code=job_code,
95112
name=name,
96113
network_filter=network_filter,
97114
network_prefix=network_prefix,
98115
network_root_folder=network_root_folder,
99-
files_search_root_folder=files_search_root_folder,
100-
files_search_exclude_folders=files_search_exclude_folders,
101-
files_search_explicitly_listed_files=files_search_explicitly_listed_files,
102-
disable_search=disable_search,
103-
token=token,
104116
plugin_names=plugin_names,
105-
branch=branch,
106-
slug=slug,
107117
pull_request_number=pull_request_number,
108-
use_legacy_uploader=use_legacy_uploader,
109-
fail_on_error=fail_on_error,
110-
dry_run=dry_run,
111-
git_service=git_service,
112-
handle_no_reports_found=handle_no_reports_found,
113-
disable_file_fixes=disable_file_fixes,
118+
report_code=report_code,
114119
report_type=report_type,
120+
slug=slug,
121+
token=token,
122+
use_legacy_uploader=use_legacy_uploader,
115123
)

codecov_cli/plugins/__init__.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@ def run_preparation(self, collector):
1717
pass
1818

1919

20-
def select_preparation_plugins(cli_config: typing.Dict, plugin_names: typing.List[str]):
21-
plugins = [_get_plugin(cli_config, p) for p in plugin_names]
20+
def select_preparation_plugins(
21+
cli_config: typing.Dict, plugin_names: typing.List[str], plugin_config: typing.Dict
22+
):
23+
plugins = [_get_plugin(cli_config, p, plugin_config) for p in plugin_names]
2224
logger.debug(
2325
"Selected preparation plugins",
2426
extra=dict(
25-
extra_log_attributes=dict(selected_plugins=list(map(type, plugins)))
27+
extra_log_attributes=dict(
28+
selected_plugins=list(map(type, plugins)),
29+
cli_config=cli_config,
30+
)
2631
),
2732
)
2833
return plugins
@@ -59,11 +64,18 @@ def _load_plugin_from_yaml(plugin_dict: typing.Dict):
5964
return NoopPlugin()
6065

6166

62-
def _get_plugin(cli_config, plugin_name):
67+
def _get_plugin(cli_config, plugin_name, plugin_config):
6368
if plugin_name == "noop":
6469
return NoopPlugin()
6570
if plugin_name == "gcov":
66-
return GcovPlugin()
71+
return GcovPlugin(
72+
plugin_config.get("project_root", None),
73+
plugin_config.get("folders_to_ignore", None),
74+
plugin_config.get("gcov_executable", "gcov"),
75+
plugin_config.get("gcov_include", None),
76+
plugin_config.get("gcov_ignore", None),
77+
plugin_config.get("gcov_args", None),
78+
)
6779
if plugin_name == "pycoverage":
6880
config = cli_config.get("plugins", {}).get("pycoverage", {})
6981
return Pycoverage(config)

codecov_cli/plugins/gcov.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,26 @@ class GcovPlugin(object):
1515
def __init__(
1616
self,
1717
project_root: typing.Optional[pathlib.Path] = None,
18+
folders_to_ignore: typing.Optional[typing.List[str]] = None,
19+
executable: typing.Optional[str] = "gcov",
1820
patterns_to_include: typing.Optional[typing.List[str]] = None,
1921
patterns_to_ignore: typing.Optional[typing.List[str]] = None,
20-
folders_to_ignore: typing.Optional[typing.List[str]] = None,
2122
extra_arguments: typing.Optional[typing.List[str]] = None,
2223
):
23-
self.project_root = project_root or pathlib.Path(os.getcwd())
24-
self.patterns_to_include = patterns_to_include or []
25-
self.patterns_to_ignore = patterns_to_ignore or []
26-
self.folders_to_ignore = folders_to_ignore or []
24+
self.executable = executable or "gcov"
2725
self.extra_arguments = extra_arguments or []
26+
self.folders_to_ignore = folders_to_ignore or []
27+
self.patterns_to_ignore = patterns_to_ignore or []
28+
self.patterns_to_include = patterns_to_include or []
29+
self.project_root = project_root or pathlib.Path(os.getcwd())
2830

2931
def run_preparation(self, collector) -> PreparationPluginReturn:
3032
logger.debug(
31-
"Running gcov plugin...",
33+
f"Running {self.executable} plugin...",
3234
)
3335

34-
if shutil.which("gcov") is None:
35-
logger.warning("gcov is not installed or can't be found.")
36+
if shutil.which(self.executable) is None:
37+
logger.warning(f"{self.executable} is not installed or can't be found.")
3638
return
3739

3840
filename_include_regex = globs_to_regex(["*.gcno", *self.patterns_to_include])
@@ -49,15 +51,15 @@ def run_preparation(self, collector) -> PreparationPluginReturn:
4951
]
5052

5153
if not matched_paths:
52-
logger.warning("No gcov data found.")
54+
logger.warning(f"No {self.executable} data found.")
5355
return
5456

55-
logger.warning("Running gcov on the following list of files:")
57+
logger.warning(f"Running {self.executable} on the following list of files:")
5658
for path in matched_paths:
5759
logger.warning(path)
5860

5961
s = subprocess.run(
60-
["gcov", "-pb", *self.extra_arguments, *matched_paths],
62+
[self.executable, "-pb", *self.extra_arguments, *matched_paths],
6163
cwd=self.project_root,
6264
capture_output=True,
6365
)

codecov_cli/services/upload/__init__.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ def do_upload_logic(
4040
files_search_explicitly_listed_files: typing.List[Path],
4141
files_search_root_folder: Path,
4242
flags: typing.List[str],
43+
gcov_args: typing.Optional[str],
44+
gcov_executable: typing.Optional[str],
45+
gcov_ignore: typing.Optional[str],
46+
gcov_include: typing.Optional[str],
4347
git_service: typing.Optional[str],
4448
handle_no_reports_found: bool = False,
4549
job_code: typing.Optional[str],
@@ -55,8 +59,18 @@ def do_upload_logic(
5559
upload_file_type: str = "coverage",
5660
use_legacy_uploader: bool = False,
5761
):
62+
plugin_config = {
63+
"folders_to_ignore": files_search_exclude_folders,
64+
"gcov_args": gcov_args,
65+
"gcov_executable": gcov_executable,
66+
"gcov_ignore": gcov_ignore,
67+
"gcov_include": gcov_include,
68+
"project_root": files_search_root_folder,
69+
}
5870
if upload_file_type == "coverage":
59-
preparation_plugins = select_preparation_plugins(cli_config, plugin_names)
71+
preparation_plugins = select_preparation_plugins(
72+
cli_config, plugin_names, plugin_config
73+
)
6074
elif upload_file_type == "test_results":
6175
preparation_plugins = []
6276
file_selector = select_file_finder(
@@ -73,7 +87,11 @@ def do_upload_logic(
7387
network_root_folder=network_root_folder,
7488
)
7589
collector = UploadCollector(
76-
preparation_plugins, network_finder, file_selector, disable_file_fixes
90+
preparation_plugins,
91+
network_finder,
92+
file_selector,
93+
disable_file_fixes,
94+
plugin_config,
7795
)
7896
try:
7997
upload_data = collector.generate_upload_data(upload_file_type)

codecov_cli/services/upload/upload_collector.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ def __init__(
2929
preparation_plugins: typing.List[PreparationPluginInterface],
3030
network_finder: NetworkFinder,
3131
file_finder: FileFinder,
32+
plugin_config: dict,
3233
disable_file_fixes: bool = False,
3334
):
3435
self.preparation_plugins = preparation_plugins
3536
self.network_finder = network_finder
3637
self.file_finder = file_finder
3738
self.disable_file_fixes = disable_file_fixes
39+
self.plugin_config = plugin_config
3840

3941
def _produce_file_fixes(
4042
self, files: typing.List[str]

tests/commands/test_invoke_upload_process.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ def test_upload_process_options(mocker):
126126
" --network-prefix TEXT Specify a prefix on files listed in the",
127127
" network section of the Codecov report. Useful",
128128
" to help resolve path fixing",
129+
" --gcov-args TEXT Extra arguments to pass to gcov",
130+
" --gcov-ignore TEXT Paths to ignore during gcov gathering",
131+
" --gcov-include TEXT Paths to include during gcov gathering",
132+
" --gcov-executable TEXT gcov executable to run. Defaults to 'gcov'",
129133
" --parent-sha TEXT SHA (with 40 chars) of what should be the",
130134
" parent of this commit",
131135
" -h, --help Show this message and exit.",

tests/plugins/test_instantiation.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,40 +106,46 @@ def __init__(self):
106106

107107

108108
def test_get_plugin_gcov():
109-
res = _get_plugin({}, "gcov")
109+
res = _get_plugin({}, "gcov", {})
110+
assert isinstance(res, GcovPlugin)
111+
112+
res = _get_plugin({}, "gcov", {
113+
'gcov_executable': 'lcov',
114+
})
110115
assert isinstance(res, GcovPlugin)
111116

112117

113118
def test_get_plugin_xcode():
114-
res = _get_plugin({}, "xcode")
119+
res = _get_plugin({}, "xcode", {})
115120
assert isinstance(res, XcodePlugin)
116121

117122

118123
def test_get_plugin_noop():
119-
res = _get_plugin({}, "noop")
124+
res = _get_plugin({}, "noop", {})
120125
assert isinstance(res, NoopPlugin)
121126

122127

123128
def test_get_plugin_pycoverage():
124-
res = _get_plugin({}, "pycoverage")
129+
res = _get_plugin({}, "pycoverage", {})
125130
assert isinstance(res, Pycoverage)
126131
assert res.config == PycoverageConfig()
127132
assert res.config.report_type == "xml"
128133

129134
pycoverage_config = {"project_root": "project/root", "report_type": "json"}
130-
res = _get_plugin({"plugins": {"pycoverage": pycoverage_config}}, "pycoverage")
135+
res = _get_plugin({"plugins": {"pycoverage": pycoverage_config}}, "pycoverage", {})
131136
assert isinstance(res, Pycoverage)
132137
assert res.config == PycoverageConfig(pycoverage_config)
133138
assert res.config.report_type == "json"
134139

135140

136141
def test_get_plugin_compress_pycoverage():
137-
res = _get_plugin({}, "compress-pycoverage")
142+
res = _get_plugin({}, "compress-pycoverage", {})
138143
assert isinstance(res, CompressPycoverageContexts)
139144

140145
res = _get_plugin(
141146
{"plugins": {"compress-pycoverage": {"file_to_compress": "something.json"}}},
142147
"compress-pycoverage",
148+
{},
143149
)
144150
assert isinstance(res, CompressPycoverageContexts)
145151
assert str(res.file_to_compress) == "something.json"
@@ -180,6 +186,7 @@ def __init__(self, banana=None):
180186
}
181187
},
182188
["gcov", "something", "otherthing", "second", "lalalala"],
189+
{}
183190
)
184191
assert len(res) == 5
185192
assert isinstance(res[0], GcovPlugin)

0 commit comments

Comments
 (0)