Skip to content

Commit

Permalink
feat: add gcov capabilities (#536)
Browse files Browse the repository at this point in the history
* first pass

* fix: update tests
  • Loading branch information
thomasrockhu-codecov authored Oct 17, 2024
1 parent 6efe50c commit 3333bcb
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 82 deletions.
24 changes: 24 additions & 0 deletions codecov_cli/commands/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'",
),
]


Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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,
Expand Down
74 changes: 41 additions & 33 deletions codecov_cli/commands/upload_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
)
22 changes: 17 additions & 5 deletions codecov_cli/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
24 changes: 13 additions & 11 deletions codecov_cli/plugins/gcov.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -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,
)
Expand Down
22 changes: 20 additions & 2 deletions codecov_cli/services/upload/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -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(
Expand All @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions codecov_cli/services/upload/upload_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 4 additions & 0 deletions tests/commands/test_invoke_upload_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
19 changes: 13 additions & 6 deletions tests/plugins/test_instantiation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -180,6 +186,7 @@ def __init__(self, banana=None):
}
},
["gcov", "something", "otherthing", "second", "lalalala"],
{}
)
assert len(res) == 5
assert isinstance(res[0], GcovPlugin)
Expand Down
Loading

0 comments on commit 3333bcb

Please sign in to comment.