Skip to content

Improve precision of code coverage and add report to CI #954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

real-or-random
Copy link
Contributor

No description provided.

@real-or-random
Copy link
Contributor Author

The coverage reports are linked in the Cirrus web interface, e.g., from https://cirrus-ci.com/task/4981579249352704.

Here's a direct link for reference: https://api.cirrus-ci.com/v1/artifact/task/4981579249352704/coverage_report/index.html

Copy link
Contributor

@jonasnick jonasnick left a comment

Choose a reason for hiding this comment

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

Very nice, concept ACK.

this shows some unreachable verify_check branches as uncovered.

Good catch. This seems to be a consequence of the commit that silences the unused variable warnings. Not sure if there's a way to achieve both, but if not it would indeed be better to avoid false reporting.

@real-or-random
Copy link
Contributor Author

this shows some unreachable verify_check branches as uncovered.

Good catch. This seems to be a consequence of the commit that silences the unused variable warnings. Not sure if there's a way to achieve both, but if not it would indeed be better to avoid false reporting.

Yes, this is just a bug in this commit, I'll update.

@real-or-random
Copy link
Contributor Author

real-or-random commented Jun 22, 2021

Okay, updated. Now this suppresses the evaluation of the conditions in VERIFY_CHECK, which avoids the VERIFY_CHECK(... && ...) branches (the shortcutting operator introduces a branch) but while still keeping the code in the source to avoid "unused variable" warnings.

We can't do the same for CHECK because we have a lot of CHECKs with side-effects. That is the false positive rate for the branch coverage in tests.c can still be improved, e.g., by avoiding && and || in CHECKs in a further PR.

Nevertheless, this exposes a minor bug in the tests, where the "then" branch here is never taken for count == 64:
https://github.com/bitcoin-core/secp256k1/blob/master/src/tests.c#L3366
see https://api.cirrus-ci.com/v1/artifact/task/6531663917219840/coverage_report/index.src_tests.c.html
Even though this bug is obvious also from the line (not branch) coverage report alone.

/* Do nothing in coverage mode but try to stay syntactically correct.
This suppresses a lot of implicit branches introduced by shortcutting
operators at the cost of not being side-effect safe in coverage mode.
We rely on the compiler to eliminate the if (0) statement entirely. */
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is what GCC does. I can't really test with clang because the coverage report is empty, even though it should work with clang too when passing --gcov-executable="llvm-cov gcov" to gcovr. No idea what's wrong or if this is a bug somewhere in the tools.

@real-or-random
Copy link
Contributor Author

real-or-random commented Jun 22, 2021

In case anyone is wondering (I was): gcovr correctly collects data from multiple binaries and merges the data, i.e., it's okay to run gen_context, tests and tests_exhaustive in a row. It just means we can't see from the report which binary hit which line.

(But I pushed again to avoid exposing the raw gcov files in CI. I think those are just properly merged and anyway not very useful).

Conceptually gcovr also supports merging results from multiple compilations with different options (e.g., WIDEMUL) using the --add-tracefile option but this doesn't fit nicely in our CI matrix (we'd need a special job that builds multiple configuration) and it's not tremendously useful: we just need to look at two reports instead of one.

#if defined(COVERAGE)
/* Don't abort in coverage mode.
This avoids branches which are not expected to be taken.
We still use cond as usual to avoid unused variable warnings. */
Copy link
Contributor Author

@real-or-random real-or-random Jun 23, 2021

Choose a reason for hiding this comment

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

s/unused variable/unused return values/

(or actually both, but the "unused variables" can be suppressed by casting to void, which would the thing most people would expect.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps also note that this works because an if statement with an empty consequent is not treated as a branch.

Comment on lines +28 to +32
#if defined(COVERAGE)
/* Do nothing in COVERAGE mode. This will make the compiler optimize away the actual branch,
and we get useful branch coverage within our test files. */
#define TEST_FAILURE(msg)
#elif defined(DETERMINISTIC)
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to be unnecessary now that the definition of CHECK is changed.

#if defined(COVERAGE)
/* Don't abort in coverage mode.
This avoids branches which are not expected to be taken.
We still use cond as usual to avoid unused variable warnings. */
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps also note that this works because an if statement with an empty consequent is not treated as a branch.

@real-or-random
Copy link
Contributor Author

I still need to update this but let me note that #959 is a nice example of why we want to test branch coverage even in the tests.

Although for this specific case and this broken line, the report here doesn't show a branch at all... I suspect the branch is optimized away with an optimization so "trivial" that is not even disabled at -O0 (like for example the if (0) stuff in this PR, which apparently doesn't count as branch either).

@real-or-random
Copy link
Contributor Author

Further note: Having a separate report for the cttime tests is helpful for checking that all public branches are covered: Valgrind will complain on an executed branch on secret data but not if that secret branch is never reached due to an earlier branch on public data.

@real-or-random
Copy link
Contributor Author

There's a bug on current master which would have been caught by this PR, see https://gnusha.org/secp256k1/2021-09-22.log for details.

20abd52#diff-c2d5f1f7616875ab71cd41b053cfb428696988ff89642b931a0963d50f34f7e8R3438-R3439

@real-or-random real-or-random mentioned this pull request Mar 24, 2022
@real-or-random
Copy link
Contributor Author

I should rework this for GitHub Actions. https://github.blog/2022-05-09-supercharging-github-actions-with-job-summaries/ will be useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants