Skip to content

Implement lcov coverage collection for libvirt#4326

Merged
chunfuwen merged 1 commit intoavocado-framework:masterfrom
Yingshun:lcov_libvirt
Mar 9, 2026
Merged

Implement lcov coverage collection for libvirt#4326
chunfuwen merged 1 commit intoavocado-framework:masterfrom
Yingshun:lcov_libvirt

Conversation

@Yingshun
Copy link
Copy Markdown
Contributor

@Yingshun Yingshun commented Feb 27, 2026

  1. Add lcov coverage collection for both libvirt and qemu
  2. Move gcovr collection from env_process to gcov.py

Test results:

Kar Command Line: # python3 ConfigTest.py --testcase=boot --guestname=RHEL.10.1 --platform=x86_64  --customsparams='gcov_libvirt=yes\ngcov_libvirt_builddir=/builddir/build/BUILD/libvirt-11.10.0/' 

lcov tracefiles in test log dir:
-rw-r--r--. 1 root root 7.8M Mar  3 21:52 ./test-results/1-Host_RHEL.m10.u2.ovmf.qcow2.virtio_scsi.up.virtio_net.Guest.RHEL.10.1.x86_64.io-github-autotest-qemu.unattended_install.cdrom.extra_cdrom_ks.default_install.aio_threads.q35/gcov_libvirt/coverage_Host_RHEL.m10.u2.ovmf.qcow2.virtio_scsi.up.virtio_net.Guest.RHEL.10.1.x86_64.io-github-autotest-qemu.unattended_install.cdrom.extra_cdrom_ks.default_install.aio_threads.q35.info
-rw-r--r--. 1 root root 7.7M Mar  3 21:58 ./test-results/2-Host_RHEL.m10.u2.ovmf.qcow2.virtio_scsi.up.virtio_net.Guest.RHEL.10.1.x86_64.io-github-autotest-qemu.boot.q35/gcov_libvirt/coverage_Host_RHEL.m10.u2.ovmf.qcow2.virtio_scsi.up.virtio_net.Guest.RHEL.10.1.x86_64.io-github-autotest-qemu.boot.q35.info

@Yingshun Yingshun marked this pull request as draft February 27, 2026 08:37
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the virttest framework by integrating lcov for code coverage collection. It provides a dedicated function to gather coverage data for specific components and tests, improving the ability to monitor and analyze code execution paths during testing.

Highlights

  • New LCOV Coverage Collection Function: Introduced a new function, collect_lcov_coverage, in virttest/test_setup/gcov.py to handle the collection of lcov coverage data.
  • Per-Test Coverage Naming: The new function supports per-test naming for coverage tracefiles, allowing for more granular analysis of code coverage.
  • Robust Coverage Data Handling: The implementation includes logging, directory creation, command execution, and checks for the validity and size of the generated tracefiles, ensuring reliable data collection.
Changelog
  • virttest/test_setup/gcov.py
    • Added logging import and initialized a logger instance.
    • Implemented collect_lcov_coverage function to capture lcov data with per-test naming.
    • Included logic for creating output directories, executing lcov commands, and validating generated tracefiles.
Activity
  • No specific activity (comments, reviews, progress updates) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request introduces a new function collect_lcov_coverage to gather code coverage data using lcov. However, this function contains a command injection vulnerability due to unsafe construction of a shell command with unquoted parameters, which should be addressed by properly escaping the parameters using shlex.quote(). Additionally, I've proposed refactoring a nested conditional block to improve readability and using f-strings for consistency with modern Python practices.

Comment on lines +29 to +38
collect_cmd = (
"lcov --capture "
"--directory %s "
"--output-file %s "
"--test-name %s "
"--rc lcov_branch_coverage=1" % (build_dir, tracefile, test_name)
)

try:
a_process.system(collect_cmd, shell=True)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The collect_lcov_coverage function is vulnerable to command injection. The collect_cmd is constructed using string interpolation with parameters build_dir, tracefile, and test_name, and then executed with shell=True. If any of these parameters contain shell metacharacters (e.g., ;, $(...), `...`), an attacker could execute arbitrary commands on the system. Constructing shell commands with string formatting in this manner is unsafe. To remediate this, use shlex.quote() to escape all variables before they are interpolated into the command string, or avoid using shell=True and pass the command as a list of arguments to prevent shell interpretation.

    import shlex
    collect_cmd = (
        "lcov --capture "
        "--directory %s "
        "--output-file %s "
        "--test-name %s "
        "--rc lcov_branch_coverage=1" % (shlex.quote(build_dir),
                                        shlex.quote(tracefile),
                                        shlex.quote(test_name))
    )

    try:
        a_process.system(collect_cmd, shell=True)

)

try:
a_process.system(collect_cmd, shell=True)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Avoid using shell=True for security reasons. When passing a list of arguments as suggested for the command construction, the shell is not needed and the call is safer.

        a_process.system(collect_cmd)

return None

os.makedirs(output_dir, exist_ok=True)
tracefile = os.path.join(output_dir, "coverage_%s.info" % test_name)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better readability and consistency with modern Python, consider using an f-string to format the tracefile path.

    tracefile = os.path.join(output_dir, f"coverage_{test_name}.info")

Comment on lines +40 to +57
if os.path.exists(tracefile):
file_size = os.path.getsize(tracefile)
if file_size > 0:
LOG.info(
"%s coverage tracefile saved: %s (%d bytes)",
component.upper(),
tracefile,
file_size,
)
return tracefile
else:
LOG.warning("%s coverage file is empty, removing: %s",
component, tracefile)
os.unlink(tracefile)
return None
else:
LOG.warning("%s coverage file was not created: %s", component, tracefile)
return None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The nested if/else block can be flattened to improve readability. You can check for the failure conditions first and return early. This is often called a "guard clause" pattern.

        if not os.path.exists(tracefile):
            LOG.warning("%s coverage file was not created: %s", component, tracefile)
            return None

        file_size = os.path.getsize(tracefile)
        if file_size > 0:
            LOG.info(
                "%s coverage tracefile saved: %s (%d bytes)",
                component.upper(),
                tracefile,
                file_size,
            )
            return tracefile

        LOG.warning("%s coverage file is empty, removing: %s",
                    component, tracefile)
        os.unlink(tracefile)
        return None

@Yingshun Yingshun requested a review from nickzhq February 28, 2026 01:59
@Yingshun Yingshun force-pushed the lcov_libvirt branch 3 times, most recently from 954016e to ccd2c5c Compare March 2, 2026 09:27
@nickzhq
Copy link
Copy Markdown
Contributor

nickzhq commented Mar 3, 2026

@YongxueHong Please help to review this one as a high priority task. Thanks!


# Libvirt code coverage configuration
# Enable/disable gcov for libvirt, default: disable
gcov_libvirt = no
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether collect libvirt or qemu code coverage depends on parameters dynamically passed in by Kar, therefore needs to confirm with Kar members whether this parameter value can be override by parameters passed in by Kar

Copy link
Copy Markdown
Contributor Author

@Yingshun Yingshun Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kar and avocado can set different variants values in command line. Please check the test results in description.

# Coverage output format: lcov or html
gcov_libvirt_format = lcov
# Libvirt build directory where .gcda files are generated
gcov_libvirt_builddir = /var/tmp/libvirt
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we use libvirt virtcov packages, the value for this needs to be /builddir/build/BUILD

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we need to get the proper path after the package installation, so I just put a fake dir here. Users need to specify the correct one during their testings.

Comment thread virttest/test_setup/gcov.py Outdated
"--directory %s "
"--output-file %s "
"--test-name %s "
"--rc lcov_branch_coverage=1" % (build_dir, tracefile, test_name)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest lcov_branch_coverage is configured. Since previous experience show branch level coverage will add up 30% more additional data ,and extend this case running time

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

Comment thread virttest/env_process.py
try:
path.find_command("lcov")
except path.CmdNotFoundError:
LOG.warning("lcov package not installed, cannot collect QEMU coverage")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For lcov , we needs to use https://github.com/LuyaoHuang/lcov --rhel version since it fix the issue that test case can not contain special letters. So here we needs to consider this point

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's right. I think it's better to put lcov installation part in ci, similar place with libvirt/qemu coverage package installation, so I just checked lcov command here.

@Yingshun Yingshun marked this pull request as ready for review March 4, 2026 02:39
@Yingshun Yingshun requested a review from chunfuwen March 4, 2026 06:02
Comment thread virttest/test_setup/gcov.py
Comment thread virttest/test_setup/gcov.py Outdated
return None


def collect_html_coverage(build_dir, output_dir, component, cmd_opts="--html"):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest moving the collect_lcov_coverage and collect_html_coverage to a new utility module(e.g: virttest/utils_coverage)
From my understanding of both functions, I think both mainly handle the data of the coverage as a utility for the upper layer, but the test_setup package is responsible for setting up the essential environment for running tests.
Please let me know your thoughts if I misunderstood. Thanks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I know, test_set is used for pre/post test, lcov collection will be executed in postprocess in env_process.py, so I think it should be in the current location. what do you think?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, most of the modules of the package test_setup are used for the pre/post process, but there are some cases where using it within their own test cases. For example:
https://github.com/autotest/tp-libvirt/blob/ffe5bdb31268dd04a38a6df59df4687629d269e7/libvirt/tests/src/migration/migration_numa/migration_numatune.py#L66-L67
https://github.com/autotest/tp-qemu/blob/22f974662556eb283722d230c99406d908c010fc/qemu/tests/hugepage_specify_node.py#L70-L72

Structurally, there is no hard coupling between test_setup and collect_html_coverage; here, we just want to need it to deal with the coverage data in the postprocess.

Theoretically, as long as I provide the argument build_dir, output_dir, component, and cmd_opts, I can call this function anywhere, right? For example, if the user generated their own coverage data, and then the user could use this function to collect, it means it can work not only in the pre/post process, but also in the test case if there is such a requirement in the future. This flexibility allows users to collect coverage data in the post-process or even mid-test if a specific requirement arises.

Plus, treating this as a standalone utility module doesn't change your current implementation, but it does make the architecture cleaner, more extensible, and easier to maintain for future development."

Those are just my personal thoughts, but I’d love to hear more perspectives. @chunfuwen @nickzhq, what do you think? If everyone else is in agreement with your point, I’m happy to move forward with it. Thanks!

Copy link
Copy Markdown
Contributor Author

@Yingshun Yingshun Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@YongxueHong I appreciate your suggestion for moving 'collect_lcov_coverage' and 'collect_gcovr_coverage' to a new utils_coverage.py file to promote modularity. However, I think their current location in virttest/test_setup/gcov.py is appropriate for the following reasons:

  1. Since the coverage flag is not used in the release, I don't believe it's necessary for a test. Please let me know me if you are aware of any test that uses it.
  2. The gcov.py file currently have all functionality related to gcov. This includes the 'ResetGCov' class, which handles resetting gcov counters as part of the test setup, and the 'collect_*_coverage functions', which specifically process gcov output files (.gcda) using lcov and gcovr. Keeping them together ensures that all gcov-related logic is self-contained and easily discoverable within a single module.
  3. Both resetting and collecting gcov data are integral parts of a complete gcov-based coverage workflow within the testing environment. Separating the collection functions would break this natural grouping and could make the overall gcov management logic harder to follow.
  4. While lcov and gcovr are general tools, their usage here is specifically for GCOV. Naming a file utils_coverage.py might imply it contains utilities for any type of coverage. We still have coverage.py in avocado.
  5. I'm trying to determine if clear guidelines exist for using test_setup/ and other utils_*.py files, specifically to check if my current approach aligns with any rules. Can you please share them to me if you have one?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@YongxueHong I appreciate your suggestion for moving 'collect_lcov_coverage' and 'collect_gcovr_coverage' to a new utils_coverage.py file to promote modularity. However, I think their current location in virttest/test_setup/gcov.py is appropriate for the following reasons:

  1. The gcov.py file currently have all functionality related to gcov. This includes the 'ResetGCov' class, which handles resetting gcov counters as part of the test setup, and the 'collect_*_coverage functions', which specifically process gcov output files (.gcda) using lcov and gcovr. Keeping them together ensures that all gcov-related logic is self-contained and easily discoverable within a single module.

Hi @Yingshun
I agree with your opinion about the description of the function collect_*_coverage functions that process the output of the gcov and lcov, but I think those functions not only serve for the test_setup process, which you mentioned the ResetGCov test_setup, but also elsewhere as long as it provided the gcov output files and the user want to process the files if needed in the future.

  1. Both resetting and collecting gcov data are integral parts of a complete gcov-based coverage workflow within the testing environment. Separating the collection functions would break this natural grouping and could make the overall gcov management logic harder to follow.

Following up on your comment: should we move the gcov data collection into the cleanup(self) method? Since setup(self) already handles resetting the gcov data, placing the collection in cleanup would create a more symmetric and intuitive gcov-based coverage workflow. This ensures that the setup and teardown operations remain balanced and logically grouped.

  1. While lcov and gcovr are general tools, their usage here is specifically for GCOV. Naming a file utils_coverage.py might imply it contains utilities for any type of coverage.

Yeah, if we name the utils_coverage.py, it means that it can work for any type of coverage, but we can add the corresponding functions to this module if there is other type supportment in the future. And this design principle does make sense.

We still have coverage.py in avocado.

There is no conflict between the aovocado and avocado-vt since they have their own namespace, refer to aovocado.utils.disk vs virttest.utils_disk.

  1. I'm trying to determine if clear guidelines exist for using test_setup/ and other utils_*.py files, specifically to check if my current approach aligns with any rules. Can you please share them to me if you have one?

Unfortunately, I assume there are no such guidelines and docs for developing the test_setup and utils_*, instead, it depends on the understanding of each maintainer.

Hi @Yingshun
While we have different perspectives on this specific implementation, I don't believe it's a critical issue for the framework since it doesn't introduce a risk that can't be managed later if needed. Your approach is a solid solution, so I’m happy to approve it and move forward to keep the project on track.
Thanks for sharing your thoughts and working through this with me!

Comment thread virttest/test_setup/gcov.py
Comment thread virttest/test_setup/gcov.py
Comment thread virttest/env_process.py
Comment thread virttest/test_setup/gcov.py Outdated
@Yingshun Yingshun force-pushed the lcov_libvirt branch 2 times, most recently from 5b12fce to 2b80c58 Compare March 5, 2026 11:38
@Yingshun Yingshun requested a review from YongxueHong March 5, 2026 11:40
@Yingshun Yingshun force-pushed the lcov_libvirt branch 3 times, most recently from 700013f to ab28cb6 Compare March 6, 2026 04:10
1. Add lcov coverage collection for both libvirt and qemu
2. Move gcovr collection from env_process to gcov.py

Signed-off-by: Yingshun Cui <yicui@redhat.com>
Copy link
Copy Markdown
Contributor

@chunfuwen chunfuwen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@chunfuwen
Copy link
Copy Markdown
Contributor

merge it since it is urgently needes.

@chunfuwen chunfuwen merged commit 37b7568 into avocado-framework:master Mar 9, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants