Skip to content
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

Missing coverage for logical lines split on multiple lines #270

Closed
rubik opened this issue Oct 11, 2019 · 15 comments
Closed

Missing coverage for logical lines split on multiple lines #270

rubik opened this issue Oct 11, 2019 · 15 comments

Comments

@rubik
Copy link

rubik commented Oct 11, 2019

System: Linux laptop 5.3.5-arch1-1-ARCH #1 SMP PREEMPT Mon Oct 7 19:03:08 UTC 2019 x86_64 GNU/Linux
Rust version: rustc 1.38.0 (625451e37 2019-09-23)
cargo-tarpaulin version: 0.9.0

It seems that Tarpaulin has the most trouble when logical lines are split across multiple lines.

I have a few examples of this behavior. I maintain rubik/orizuru, and I'm referring to the code at commit https://github.com/rubik/orizuru/tree/6a569c574137549af0d2c9148be694e2254c175c.

It's easier to see Tarpaulin's output on Coveralls, but I'll attach the raw output at the bottom of this post. On Coveralls, we can see that lines 32, 122, 126, 127, 130, 132, 133, 150, 165 of src/consumer.rs are marked as uncovered but they are obviously covered. In src/gc.rs, the same thing is true for line 36, while on the other hand line 37 is wrongly marked as covered, when it should be line 38.

Here is the raw output from cargo tarpaulin -v --ignore-tests:

$ cargo tarpaulin --ignore-tests -v
[INFO tarpaulin] Running Tarpaulin
[INFO tarpaulin] Building project
   Compiling orizuru v0.0.1 (/home/miki/exp/orizuru)
    Finished dev [unoptimized + debuginfo] target(s) in 4.62s
[DEBUG tarpaulin] Processing Target(test: producer)
[INFO tarpaulin] Launching test
[INFO tarpaulin] running /home/miki/exp/orizuru/target/debug/deps/producer-2ef6002cc111b285

running 1 test
test producer_can_enqueue ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

[DEBUG tarpaulin] Processing Target(test: consumer)
[INFO tarpaulin] Launching test
[INFO tarpaulin] running /home/miki/exp/orizuru/target/debug/deps/consumer-fe2596306e0a75d1

running 9 tests
test no_heartbeat ... ok
test register ... ok
test one_heartbeat ... ok
test acked_are_released ... ok
test decodes_job ... ok
test can_be_stopped ... ok
test unacked_to_unack_queue ... ok
test rejected_to_unack_queue ... ok
test multiple_heartbeat ... ok

test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

[DEBUG tarpaulin] Processing Target(test: gc)
[INFO tarpaulin] Launching test
[INFO tarpaulin] running /home/miki/exp/orizuru/target/debug/deps/gc-421ed796d3795885

running 5 tests
test collect_one_runs_with_no_jobs ... ok
test collect_noop_with_no_consumers ... ok
test collect_runs_with_a_consumer_and_no_jobs ... ok
test collect_one_runs_with_some_jobs ... ok
test collect_runs_with_a_consumer_and_some_jobs ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

[DEBUG tarpaulin] Processing Target(test: test_utils)
[INFO tarpaulin] Launching test
[INFO tarpaulin] running /home/miki/exp/orizuru/target/debug/deps/test_utils-7a56f348bdb14280

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

[DEBUG tarpaulin] Processing Target(lib)
[INFO tarpaulin] Launching test
[INFO tarpaulin] running /home/miki/exp/orizuru/target/debug/deps/orizuru-7134647b58b7d504

running 4 tests
test message::tests::cant_decode_if_not_string ... ok
test message::tests::cant_decode_if_not_msgpack ... ok
test message::tests::payload_field_is_accessible ... ok
test message::tests::message_field_is_accessible ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

[INFO tarpaulin] Coverage Results:
|| Uncovered Lines:
|| src/consumer.rs: 32, 119, 122, 126-127, 130, 132-133, 150, 157-159, 165, 167-168
|| src/gc.rs: 36, 38-39
|| src/message.rs: 126-127
|| Tested/Total Lines:
|| src/consumer.rs: 62/77
|| src/gc.rs: 25/28
|| src/message.rs: 28/30
|| src/producer.rs: 8/8
|| 
86.01% coverage, 123/143 lines covered
@jamestwebber
Copy link

I've noticed the same issue–I'm trying to keep my code readable and nicely formatted but tarpaulin does not like it.

On the other hand, it doesn't make a ton of sense to count a statement like that as multiple lines for the purposes of coverage. I wonder if a simple-ish (maybe not so simple) solution would be to min-ify the code before measuring coverage? e.g.:

  1. collapse all multi-line statements into one-liners, keeping track of the line number transformation
  2. run tarpaulin and get coverage info for minified code
  3. use the inverse line-number mapping for display purposes

I would maybe use the minified version for the purpose of computing coverage percentage, but opinions might vary there.

@rubik
Copy link
Author

rubik commented Oct 30, 2019

@jamestwebber In static analysis there is a concept of "logical line of code", as opposed to the physical source line of code. The latter is simply what is present in the file, whereas the logical one measures the number of executable statements.

You can see some examples on Wikipedia; they are in C but they easily translate to Rust:
https://en.wikipedia.org/wiki/Source_lines_of_code#Measurement_methods

I suppose Tarpaulin could adopt a similar approach. It's very frequently used by static analysis tools.

@haukened
Copy link

haukened commented Dec 28, 2019

So no movement on this? The majority of my new lib has multi line statements because:

let local_path = String::from_utf8(
    Command::new("which")
        .arg("keybase")
        .output()
        .expect("which is not installed")
        .stdout,
    )
    .expect("Output not in UTF-8");

Is just so much more readable than the one-line marathon. (Especially when you’re working in vim)

Edited for real world example

@xd009642
Copy link
Owner

No movement of this, I'm trying to figure out an issue on selecting the right lines for breakpoints that solves this. Though currently I've only managed to do trial and error switching between multiple potential breakpoints and finding there is a correct solution and an incorrect solution. Need to figure out how to do this analytically and change tarpaulin to match that behaviour

@KamilaBorowska
Copy link

This happens for me. The project source code is at https://gitlab.com/pastebinrun/pastebinrun.

image

Somehow format! is reachable, but concat! is not. CONTENT_SECURITY_POLICY is a constant.

@xd009642
Copy link
Owner

Hmm I'd have to see how concat is implemented, if it emits no runtime instructions it should probably be filtered out of results..

@xd009642
Copy link
Owner

Also is concat needed, I think if you remove it and the comma at the end of each arg it's a valid multiline string in rust (though I might be getting confused with C++

@KamilaBorowska
Copy link

KamilaBorowska commented Jan 21, 2020

It's not needed, but it's going to be a ridiculously long string without it. All what concat! does is concatenate string into a literal, for instance concat!("a", "b") gives "ab". This is a built-in Rust macro. You cannot concatenate strings by typing "a" "b" in Rust either.

I cannot have newlines in content-security-policy either (this is a HTTP header, cannot really have newlines in those).

@orhun
Copy link

orhun commented Jan 23, 2020

I think I have the same issue at this report:
https://codecov.io/gh/orhun/kmon/src/master/src/kernel/log.rs#L25

@rye
Copy link
Contributor

rye commented Jan 23, 2020

FYI, concat! is a compiler built-in, and concatenates literals, yielding a &'static str.

@Razican
Copy link

Razican commented Jan 28, 2020

Some other examples:

assert_eq! in its own line:
.split() or .collect() in its own lines:
match self and multiple line match left side:

@jstasiak
Copy link

jstasiak commented Jan 28, 2020

Similar, with a plain multi-line assignment:

Screenshot 2020-01-28 at 12 42 03

The full report with the source code: https://coveralls.io/builds/28315588/source?filename=src/sounddat.rs#L58

@DavidBM
Copy link

DavidBM commented Feb 7, 2020

I have the same problem. Seems that the lines that can be optimized away by the compiler (because they have static values) are not counted. See the image:

Screenshot from 2020-02-07 13-32-54

@xd009642 xd009642 mentioned this issue Feb 23, 2020
15 tasks
@xd009642
Copy link
Owner

I've popped the issue on the new mega issue for all missing coverage etc so I'll close this one in favour of that one #351

@Luro02
Copy link

Luro02 commented Feb 23, 2020

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

No branches or pull requests