So right now one of the best ways to get code coverage analysis with Rust is to use
cargo test and then use
kcov to run the binaries, as outlined in @lifthrasiir’s excellent tutorial on the topic. One of the problems with this approach, however, is how agnostic kcov is to the language being profiled.
It appears that kcov works by looking at all code that’s being run, and then it tracks what’s actually run and what’s not. All not-run code is considered not covered. This generally sounds like a good idea, except that the compiler’s default behavior ends up messing with the output of kcov.
By default the compiler will compile each function and each global into its own section of the output object file. The linker is then passed
--gc-sections which will eliminate all unused functions/globals at link time. This behavior is crucial to keep binary sizes under control in Rust, and I don’t think that we should turn this off by default. This does, however, have the implication that testable functions in a library are stripped by default in test executables if they are not used (or in this case, not tested). This consequence of this is that all untested code is not considered for coverage by kcov. Now seeing how one of the great points of kcov is to tell you what’s not being covered, this seems bad!
What would others think about not passing
--gc-sections by default when the compiler is passed the
--test flag. The consequence of this is that test binaries will be larger, but the benefit is that tools like kcov (I can imagine there might before) will be able to understand what code actually isn’t run and provide more accurate coverage reports. An alternative here would be to add a separate compiler option to not pass
--gc-sections, but this would be somewhat more complicated as Cargo would then need to perhaps provide a subcommand like
cargo coverage to pass this flag.
Curious what others think!