You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Playing around, it seems that the await is only treated as covered if the future yields underneath it, the following forces a yield:
#[allow(dead_code)]
#[derive(Debug, Default)]
struct Yield {
init: bool,
}
impl std::future::Future for Yield {
type Output = ();
fn poll(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
if !self.init {
self.init = true;
cx.waker().wake_by_ref();
return std::task::Poll::Pending;
} else {
return std::task::Poll::Ready(());
}
}
}
pub async fn do_things() {
println!("Hello world");
Yield::default().await; // Causes a yield then completes on next `poll`
}
#[tokio::main]
pub async fn main() {
do_things()
.await; // This is the line/span we care about coverage for
}
With the Yield::default().await line in place then the await line in main is marked as hit, if it's commented out then the await is unhit.
My guess is that only the "bail out to the calling frame" logic has a BCB counter in it (or maybe there simply is no code in the other branch to count) so unless the future yields the counter is not incremented, and the span is deemed unhit.
Does anyone have a suggestion on how to re-write the example so it could be used as a helper-type or function to actually get code coverage on await statements.
I am trying to do something like this:
async_func().await; would become Yield::new(async_func()).await
But, my rust futures abilities aren't so strong.
I'll keep at it, and post something if I figure it out.
I created a helper type, so now I do read(...).fix_cov().await;
This will fix the coverage where the await is invoked.
The helper type doesn't get 100% coverage, and it may be possible to
write a helper that gets 100% coverage, but it is not required for my needs at the moment,
Since I sometimes clean my repos, I will add the workaround right here!
use std::future::Future;#[allow(dead_code)]#[derive(Debug,Default)]structYield{init:bool,}impl std::future::FutureforYield{typeOutput = ();fnpoll(mutself: std::pin::Pin<&mutSelf>,cx:&mut std::task::Context<'_>,) -> std::task::Poll<Self::Output>{if !self.init{self.init = true;
cx.waker().wake_by_ref();return std::task::Poll::Pending;}else{return std::task::Poll::Ready(());}}}traitFixCoverage{asyncfnfix_cov(self) -> <SelfasFuture>::OutputwhereSelf:Sized,Self:Future,{// this will NOT show as covered// but for my usage I just keep it outside of my coverage checked codelet r = self.await;Yield::default().await;
r
}}impl<F,T>FixCoverageforFwhereF:Future<Output = T>{}pubasyncfndo_things(){println!("Hello world");}#[tokio::main]pubasyncfnmain(){do_things().await;// will NOT show as covereddo_things().fix_cov().await;// will show as covered}
Note that Yeild is available in tokio as tokio::task::yeild_now() (https://docs.rs/tokio/latest/tokio/task/fn.yield_now.html). An implementation slightly closer to what you originally pitched (where you wrap a future in an adaptor) might look like:
use std::pin::Pin;
use std::future::Future;
use std::task::{Context, Poll};
use futures::future::FutureExt;
pin_project_lite::pin_project! {
struct AlwaysYeildAtLeastOnce<F> {
yeilded: bool,
#[pin]
inner: F,
}
}
impl<F> AlwaysYeildAtLeastOnce<F> {
fn new(f: F) -> Self {
Self {
yeilded: false,
inner: f,
}
}
}
impl<F> Future for AlwaysYeildAtLeastOnce<F> where F: Future {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let zelf = self.project();
if *zelf.yeilded {
zelf.inner.poll(cx)
} else {
*zelf.yeilded = true;
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
async fn dont_yeild() {
println!("Not yeilding");
}
#[tokio::main]
async fn main() {
AlwaysYeildAtLeastOnce::new(dont_yeild()).await;
assert!(AlwaysYeildAtLeastOnce::new(dont_yeild()).now_or_never().is_none())
}
Activity
bossmc commentedon Sep 28, 2022
Playing around, it seems that the
awaitis only treated as covered if the future yields underneath it, the following forces a yield:With the
Yield::default().awaitline in place then theawaitline inmainis marked as hit, if it's commented out then theawaitis unhit.My guess is that only the "bail out to the calling frame" logic has a BCB counter in it (or maybe there simply is no code in the other branch to count) so unless the future yields the counter is not incremented, and the span is deemed unhit.
cameronelliott commentedon Feb 1, 2024
Does anyone have a suggestion on how to re-write the example so it could be used as a helper-type or function to actually get code coverage on
awaitstatements.I am trying to do something like this:
async_func().await; would become Yield::new(async_func()).await
But, my rust futures abilities aren't so strong.
I'll keep at it, and post something if I figure it out.
cameronelliott commentedon Feb 2, 2024
I did write a workaround that works for my needs.
https://github.com/cameronelliott/await-coverage-workaround.git
I created a helper type, so now I do
read(...).fix_cov().await;This will fix the coverage where the await is invoked.
The helper type doesn't get 100% coverage, and it may be possible to
write a helper that gets 100% coverage, but it is not required for my needs at the moment,
cameronelliott commentedon Feb 2, 2024
Since I sometimes clean my repos, I will add the workaround right here!
bossmc commentedon Feb 2, 2024
Note that
Yeildis available intokioastokio::task::yeild_now()(https://docs.rs/tokio/latest/tokio/task/fn.yield_now.html). An implementation slightly closer to what you originally pitched (where you wrap a future in an adaptor) might look like:Rollup merge of rust-lang#130013 - jonathan-conder:await_coverage, r=…
Unrolled build for rust-lang#130013