Change:
I tried to generate test code coverage with this source file using and the LLVM tools. Source File: I expected to see this happen: Instead, this happened: . cargo-kcov, -Zprofile+grcov, and tarpaulin agree on 76%. In the output below lines , , were not executable but should have been executable+uncovered. Line should be executable+covered. <details<summarycoverage output</summary<p</p</details:
label: +A-code-coverage cc
Thanks for the bug report ! There was a recent bug fix (PR ) that addresses most of the missing coverage in your example (except for the last one). I just tested with and I see the expected coverage now for the first three ranges you reported seeing no coverage. These three are now showing coverage (with 0 executions) as expected: I was surprised to see no coverage for line 169, and I do see the did print, so that does seem to be a bug. So as of today, this issue is still a bug that I will need to look into further:
cc: Note, here is a snippet showing the missing coverage and code relevant to its codepath. I haven't thoroughly investigated this. I should be able to reproduce this in a simplified test (without the asynctrait and executor dependencies) that I can add to our unit tests. I don't have a theory yet. It could either be the closure's MIR is not included in coverage (seems unlikely to me), or is silently dropping coverage because it doesn't like something. (This is what was happening for the other missing coverage reported in this issue, that has since been resolved.) The test (shown at the bottom) calls and awaits the implementation of below: I do see the test output, including the from above, and from the awaited call to , below: The async closure seems to be missing coverage, as shown here. Note the wrapper function is covered and called (showing the coverage of on line 168), but there should also be coverage for the awaited implicit closure (lines 168-170, I believe). Since the program output proves this is called, the coverage should be showing execution. And the test that invokes it is covered:
Thank you. I see improvement in the latest nightly. I minified the test case to the async issues for anyone looking at this (but it still uses executor and asynctrait.) Since this function is missing coverage too the issue isn't specific to async_trait thought.
So it turns out that the internal (auto-generated) closure actually does get coverage, but for some reason the code region for the macro, by itself, is not or not counted. I've created a draft PR with a new test and current results, demonstrating this issue. Once I figure out how to fix it, I'll submit the PR with the updated results.
Update: fixes the issue.

Discussion:
use rustc_middle::ty::TyCtxt; use rustc_span::source_map::original_sp; <del> use rustc_span::{BytePos, Span, SyntaxContext}; </del> <ins> use rustc_span::{BytePos, Span}; </ins> use std::cmp::Ordering;

) -> Vec<CoverageSpan> { let mut coverage_spans = CoverageSpans { mir_body, <del> fn_sig_span, </del> <ins> fn_sig_span: fn_sig_source_span(fn_sig_span, body_span), </ins> body_span, basic_coverage_blocks, sorted_spans_iter: None,

} #[inline] <ins> fn fn_sig_source_span(fn_sig_span: Span, body_span: Span) -> Span { original_sp(fn_sig_span, body_span).with_ctxt(body_span.ctxt()) } #[inline] </ins> fn function_source_span(span: Span, body_span: Span) -> Span { <del> let span = original_sp(span, body_span).with_ctxt(SyntaxContext::root()); </del> <ins> let span = original_sp(span, body_span).with_ctxt(body_span.ctxt()); </ins> if body_span.contains(span) { span } else { body_span } }

1|       |#![allow(unused_assignments, dead_code)] 2|       | <del> 3|       |// compile-flags: --edition=2018 -C opt-level=1 # fix in rustc_mir/monomorphize/partitioning/mod.rs </del> <ins> 3|       |// compile-flags: --edition=2018 -C opt-level=1 </ins> 4|       | 5|      1|async fn c(x: u8) -> u8 { 6|      1|    if x == 8 {

<ins> 1|       |// compile-flags: --edition=2018 2|       | 3|       |use core::{ 4|       |    future::Future, 5|       |    marker::Send, 6|       |    pin::Pin, 7|       |}; 8|       | 9|      1|fn non_async_func() { 10|      1|    println!("non_async_func was covered"); 11|      1|    let b = true; 12|      1|    if b { 13|      1|        println!("non_async_func println in block"); 14|      1|    } 15|      1|} 16|       | 17|       |// FIXME(#83985): The auto-generated closure in an async function is failing to include 18|       |// the println!() and `let` assignment lines in the coverage code region(s), as it does in the 19|       |// non-async function above, unless the `println!()` is inside a covered block. 20|      1|async fn async_func() { 21|      1|    println!("async_func was covered"); 22|      1|    let b = true; 23|      1|    if b { 24|      1|        println!("async_func println in block"); 25|      1|    } ^0 26|      1|} 27|       | 28|       |// FIXME(#83985): As above, this async function only has the `println!()` macro call, which is not 29|       |// showing coverage, so the entire async closure _appears_ uncovered; but this is not exactly true. 30|       |// It's only certain kinds of lines and/or their context that results in missing coverage. 31|      1|async fn async_func_just_println() { 32|      1|    println!("async_func_just_println was covered"); 33|      1|} 34|       | 35|      1|fn main() { 36|      1|    println!("codecovsample::main"); 37|      1| 38|      1|    non_async_func(); 39|      1| 40|      1|    executor::block_on(async_func()); 41|      1|    executor::block_on(async_func_just_println()); 42|      1|} 43|       | 44|       |mod executor { 45|       |    use core::{ 46|       |        future::Future, 47|       |        pin::Pin, 48|       |        task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, 49|       |    }; 50|       | 51|      2|    pub fn block_on<F: Future>(mut future: F) -> F::Output { 52|      2|        let mut future = unsafe { Pin::new_unchecked(&mut future) }; 53|      2|        use std::hint::unreachable_unchecked; 54|      2|        static VTABLE: RawWakerVTable = RawWakerVTable::new( 55|      2|            |_| unsafe { unreachable_unchecked() }, // clone ^0 56|      2|            |_| unsafe { unreachable_unchecked() }, // wake ^0 57|      2|            |_| unsafe { unreachable_unchecked() }, // wake_by_ref ^0 58|      2|            |_| (), 59|      2|        ); 60|      2|        let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; 61|      2|        let mut context = Context::from_waker(&waker); 62|       | 63|       |        loop { 64|      2|            if let Poll::Ready(val) = future.as_mut().poll(&mut context) { 65|      2|                break val; 66|      0|            } 67|       |        } 68|      2|    } ------------------ | async2::executor::block_on::<core::future::from_generator::GenFuture<async2::async_func::{closure#0}>>: |   51|      1|    pub fn block_on<F: Future>(mut future: F) -> F::Output { |   52|      1|        let mut future = unsafe { Pin::new_unchecked(&mut future) }; |   53|      1|        use std::hint::unreachable_unchecked; |   54|      1|        static VTABLE: RawWakerVTable = RawWakerVTable::new( |   55|      1|            |_| unsafe { unreachable_unchecked() }, // clone |   56|      1|            |_| unsafe { unreachable_unchecked() }, // wake |   57|      1|            |_| unsafe { unreachable_unchecked() }, // wake_by_ref |   58|      1|            |_| (), |   59|      1|        ); |   60|      1|        let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; |   61|      1|        let mut context = Context::from_waker(&waker); |   62|       | |   63|       |        loop { |   64|      1|            if let Poll::Ready(val) = future.as_mut().poll(&mut context) { |   65|      1|                break val; |   66|      0|            } |   67|       |        } |   68|      1|    } ------------------ | async2::executor::block_on::<core::future::from_generator::GenFuture<async2::async_func_just_println::{closure#0}>>: |   51|      1|    pub fn block_on<F: Future>(mut future: F) -> F::Output { |   52|      1|        let mut future = unsafe { Pin::new_unchecked(&mut future) }; |   53|      1|        use std::hint::unreachable_unchecked; |   54|      1|        static VTABLE: RawWakerVTable = RawWakerVTable::new( |   55|      1|            |_| unsafe { unreachable_unchecked() }, // clone |   56|      1|            |_| unsafe { unreachable_unchecked() }, // wake |   57|      1|            |_| unsafe { unreachable_unchecked() }, // wake_by_ref |   58|      1|            |_| (), |   59|      1|        ); |   60|      1|        let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; |   61|      1|        let mut context = Context::from_waker(&waker); |   62|       | |   63|       |        loop { |   64|      1|            if let Poll::Ready(val) = future.as_mut().poll(&mut context) { |   65|      1|                break val; |   66|      0|            } |   67|       |        } |   68|      1|    } ------------------ 69|       |} </ins>

#![allow(unused_assignments, dead_code)] <del> // compile-flags: --edition=2018 -C opt-level=1 # fix in rustc_mir/monomorphize/partitioning/mod.rs </del> <ins> // compile-flags: --edition=2018 -C opt-level=1 </ins> async fn c(x: u8) -> u8 { if x == 8 {

<ins> // compile-flags: --edition=2018 use core::{ future::Future, marker::Send, pin::Pin, }; fn non_async_func() { println!("non_async_func was covered"); let b = true; if b { println!("non_async_func println in block"); } } // FIXME(#83985): The auto-generated closure in an async function is failing to include // the println!() and `let` assignment lines in the coverage code region(s), as it does in the // non-async function above, unless the `println!()` is inside a covered block. async fn async_func() { println!("async_func was covered"); let b = true; if b { println!("async_func println in block"); } } // FIXME(#83985): As above, this async function only has the `println!()` macro call, which is not // showing coverage, so the entire async closure _appears_ uncovered; but this is not exactly true. // It's only certain kinds of lines and/or their context that results in missing coverage. async fn async_func_just_println() { println!("async_func_just_println was covered"); } fn main() { println!("codecovsample::main"); non_async_func(); executor::block_on(async_func()); executor::block_on(async_func_just_println()); } mod executor { use core::{ future::Future, pin::Pin, task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, }; pub fn block_on<F: Future>(mut future: F) -> F::Output { let mut future = unsafe { Pin::new_unchecked(&mut future) }; use std::hint::unreachable_unchecked; static VTABLE: RawWakerVTable = RawWakerVTable::new( |_| unsafe { unreachable_unchecked() }, // clone |_| unsafe { unreachable_unchecked() }, // wake |_| unsafe { unreachable_unchecked() }, // wake_by_ref |_| (), ); let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; let mut context = Context::from_waker(&waker); loop { if let Poll::Ready(val) = future.as_mut().poll(&mut context) { break val; } } } } </ins>