-
Notifications
You must be signed in to change notification settings - Fork 347
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
Add option for generating coverage reports #3954
Conversation
Thanks! :-) Would be good to also have a smoke test in CI to at least ensure this works. That should likely go somewhere in |
48780bf
to
5b1666a
Compare
If you additionally pipe the output to Little effort for a minimal report shown on every run |
miri-script/src/commands.rs
Outdated
let home = std::env::var("HOME")?; | ||
let ignored = format!("{home}/*|miri/target/*|target/debug/*|rust/deps/*"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What exactly is this supposed to ignore? Or, what undesirable thing happens if we don't ignore anything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I don't add these ignore regular expressions, the report also show files like:
miri/target/debug/build/chrono-tz-65fd732542149546/out/timezones.rs
root/.cargo/registry/src/index.crates.io-6f17d22bba15001f/aes-0.8.4/src/autodetect.rs
rust/deps/tracing-0.1.37/src/macros.rs
(the root path is because I run it in a container, so it is the home directory)
Additionally the coverage percentages shown in the report are smaller than the real ones, since non-miri files are included.
Ignoring these files is the simplest way I could think to fix this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. I suppose the report isn't supposed to look like this?
$ cargo-cov -- report --instr-profile=merged.profdata --object target/debug/miri -ignore-filename-regex=/home/ben/*|miri/target/*|target/debug/*|rust/deps/*
Filename Regions Missed Regions Cover Functions Missed Functions Executed Lines Missed Lines Cover Branches Missed Branches Cover
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TOTAL 0 0 - 0 0 - 0 0 - 0 0 -
I do all development work from directories under $HOME
which I think is pretty typical. So I think this regex is excluding all the profile data.
Is it possible for us to only include certain directories, instead of excluding them? We just want to include src/
I think, which seems like a much more robust filter, if it's possible to implement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah that makes sense. Working in the container, I did my dev work under /miri
, which was not under $HOME
(/root
). So I didn't think of that. Unfortunately I can't find an option to include files/directories, instead of excluding them.
We could use the llvm_profparser crate, used in tarpaulin, as i suggested in #240. It seems to provide a mapping which can produce reports only for the paths that we are interested in.
I went with using cargo-binutils for this PR, since it seemed like a more common crate, than tarpauling/llvm_profparser. Maybe adding llvm_profparser as a dependency for coverage reports is acceptable.
The quick solution would be to exclude $HOME/.{cargo, rustup}
as suggested in another comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth a feature request for cargo-binutils?
I'd rather avoid that dependency, at least for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think replacing -ignore-filename-regex={ignored}
with --sources src/
selects the files we want. At least the coverage report it emits looks reasonable to me?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there's any need to use cargo-binutils
, in this case cargo-cov
is a trivial wrapper around llvm-cov
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, --sources src/
actually works. I tried using it like --sources=src/
and it didn't work for me. Thanks for finding this option!
I use cargo-binutils because I saw this in the instrument coverage documentation:
note that the coverage mapping data generated by the Rust compiler requires LLVM version 12 or higher, and processing the raw data may require exactly the LLVM version used by the compiler. (llvm-cov --version typically shows the tool's LLVM version number, and rustc --verbose --version shows the version of LLVM used by the Rust
You can install compatible versions of these tools via the rustup component llvm-tools-preview. This component is the recommended path, though the specific tools available and their interface is not currently subject to Rust's usual stability guarantees. In this case, you may also find cargo-binutils useful as a wrapper around these tools.
I am not really familiar with llvm ecosystem so I don't know how easy it is to get compatible versions of the llvm tools. I used cargo-binutils
to be more portable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As the cargo-binutils docs say, all that package does is exec the llvm tools from the sysroot, relying on the llvm-tools-preview component being installed. We're already doing toolchain management and finding stuff in the sysroot as part of miri-script. Can you reuse that code? Or make it reusable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
During the toolchain
subcommand we install the llvm-tools
component (not the -preview
version). I copy-pasted and modified some code from cargo-binutils and it seems to work. (do we need to do anything license-wise? I don't think so but I am not sure. Both miri and cargo-binutils have an MIT license).
After doing some more search it seems that llvm-tools
should never have existed and the "proper" name for it should have been llvm-tools-preview
. rust-lang/rust#119164. However after inadvertently using the name llvm-tools
and people started depending on it it was decided not to change the name again and break people. Indeed when I add the llvm-tools-preview
component I see:
$ rustup component add llvm-tools-preview
info: downloading component 'llvm-tools'
info: installing component 'llvm-tools'
Regarding smoke tests/showing the output in the CI: I am only worried about the run time of the tests growing. I could maybe add a filter in the I am not sure how github actions work. Would creating a new action so that it can run in parallel with the other tests work? This also makes me think that the way this is implemented, will only produce coverage reports for a single target. It wont aggregate coverage information across different targets, so we won't have a complete picture of the coverage in a single report. A possible solution to this might be to support running multiple targets with a single |
Regarding the multi-target situation, let's move this discussion to the issue as it is not needed for an MVP.
What is the runtime overhead of this? We can add a new dedicated coverage target if needed, though it will duplicate all the work of downloading the right rustc and building Miri. Or does it need its own separate build anyway?
|
Yeah excluding HOME is definitely not going to work. Does excluding HOME/.cargo work?
|
Also, why does it exclude "target/debug" on top of the others? Hard-coding "debug" is not great.
Please have a comment explaining for every exclusion why it is needed.
(Sorry I can't reply in the thread right now, only via email.)
|
miri-script/src/commands.rs
Outdated
@@ -468,6 +468,9 @@ impl Command { | |||
if bless { | |||
e.sh.set_var("RUSTC_BLESS", "Gesundheit"); | |||
} | |||
if coverage { | |||
e.sh.set_var("RUSTFLAGS", "-C instrument-coverage"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will overwrite the previous RUSTFLAGS that is carefully computed in MiriEnv::new
. Instead, please append the flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, Miri will be built twice this way: once without the flag in e.build_miri_sysroot
, and then again with the flag in e.test
.
So the flag should probably be set before build_miri_sysroot
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i am now appending the flag, and have moved the code before building the sysroot.
8f36706
to
b6fcc50
Compare
I am now not excluding directories, and I use the For the smoke test, I invoke What I am not sure about is how I find the miri binary to give to
with suffix and miri dir being: let miri_dir = e.miri_dir.as_os_str();
let suffix = if cfg!(windows) { ".exe" } else { "" }; I tried reading through the code but I couldn't find a similar pattern to see if there is a better way to get the miri binary's path. I couldn't find a way to not hardcode I also added |
This will still do a full rebuild, right, because it needs an instrumented Miri? And then it will run zero tests, so it doesn't actually check that we can invoke Miri with instrumentation and gather the results. Note that I think I prefer the option where we add a new job that does the full
There's a |
miri-script/src/commands.rs
Outdated
pathbuf.push("rustlib"); | ||
pathbuf.push(rustc_version::version_meta()?.host); | ||
pathbuf.push("bin"); | ||
Ok(pathbuf) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to duplicate logic in MiriEnv::new
. Instead, please just store the libdir
that is computed there in a field. path!(libdir / ".." / "bin")
should be the directory you are looking for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah I missed that! thanks!
miri-script/src/commands.rs
Outdated
Ok(()) | ||
} | ||
|
||
fn show_coverage_report(e: &MiriEnv) -> Result<()> { | ||
let profraw_files: Vec<_> = Self::profraw_files(".")?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems a bit messy... it will just go over all files that happened to be called .profraw
, no matter whether they are from this run or not? Isn't there a cleaner way, like tell the instrumented binary to put the output into a particular folder?
We should not be creating files just randomly in the Miri source tree, and we certainly should not delete files randomly from the Miri source tree. Temporary files should go in a tempdir, and then it also shouldn't be needed to change .gitignore
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am using the tempfile
crate now. I saw that it is used in dev-dependencies
for some crates so maybe it is ok depending on it for the script? Otherwise I could use std::env::temp_dir
and manually create a subdir there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah tempfile is good. It pulls in a few more dependencies, I hope those don't have significant build time.
682a86b
to
5b32bef
Compare
miri-script/src/coverage.rs
Outdated
let rustflags = format!("{rustflags} -C instrument-coverage"); | ||
e.sh.set_var("RUSTFLAGS", rustflags); | ||
|
||
let file_template = self.path.path().join("default_%m_%p.profraw"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please explain the magic formatting symbols here. Also, why "default"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done. as for "default" i just copy pasted the default value for the env var.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should just make it miri_
then to make it a bit more self-explaining 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
miri-script/src/coverage.rs
Outdated
let rustflags = e.sh.var("RUSTFLAGS")?; | ||
let rustflags = format!("{rustflags} -C instrument-coverage"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let rustflags = e.sh.var("RUSTFLAGS")?; | |
let rustflags = format!("{rustflags} -C instrument-coverage"); | |
let mut rustflags = e.sh.var("RUSTFLAGS")?; | |
rustflags.push_str(" -C instrument-coverage"); |
miri-script/src/coverage.rs
Outdated
let mut cov_bin = e.libdir.clone(); | ||
cov_bin.push(".."); | ||
cov_bin.push("bin"); | ||
cov_bin.push("llvm-cov"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be written nicer with the path!
macro?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, thanks!
4aa0f4d
to
309459f
Compare
@rustbot ready |
It's in my queue, but $DAYJOB is keeping me quite busy these days. |
☔ The latest upstream changes (presumably #3981) made this pull request unmergeable. Please resolve the merge conflicts. |
0247e83
to
609d897
Compare
miri-script/src/commands.rs
Outdated
bless, | ||
flags, | ||
target, | ||
coverage.then_some(crate::coverage::CoverageReport::new()?), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this logic into the test
function; the arguments should corresponds to the enum variant.
@rustbot author |
☔ The latest upstream changes (presumably #3989) made this pull request unmergeable. Please resolve the merge conflicts. |
4df23a8
to
6688670
Compare
@rustbot ready |
Please don't squash commits while review is in progress. Thanks to github being kind of bad at dealing with force pushes, that makes it totally impossible to tell what changed since my last review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some nits left :)
@rustbot author |
Ah, sorry about the squashing! Didn't know that the diff would get messed up :/ @rustbot ready |
No worries. :) All looking good, so now is the time to squash. :) (with |
Add a `--coverage` option in the `test` subcommand of the miri script. This option, when set, will generate a coverage report after running the tests. `cargo-binutils` is needed as a dependency to generate the reports.
f2acadc
to
45ea681
Compare
Hoping this worked as expected (out of curiosity, do you use the "changes from last review" in the "files changed" tab? just asking so I can understand the workflow better) |
Looks good, thanks!
No, I have not found that to work reliably. After a force-push, it always says "We went looking everywhere, but couldn’t find those commits". I am looking at the commits you push and the "force-push" diffs. |
Add a
--coverage
option in thetest
subcommand of the miri script. This option, when set, will generate a coverage report after running the tests.cargo-binutils
is needed as a dependency to generate the reports.