lib: reduce .block_on().unwrap() calls in tests with extension trait#9050
lib: reduce .block_on().unwrap() calls in tests with extension trait#9050martinvonz wants to merge 1 commit intomainfrom
Conversation
There was a problem hiding this comment.
Maybe we should wait a bit to see if there are other opinions before merging, but this seems good to me.
I thought a bit about other options:
- We could use something like eyre or anyhow, change tests to return
eyre::Result, and use.block_on()?. This should actually give OK stack traces since eyre is supposed to use#[track_caller](like this PR), but is probably not worth the dependency/fuss. I imagine the stack traces will definitely not get any better than they are now from using eyre (though perhaps others know a way to do that). - https://github.com/nik-rev/evil is a fun new option, but it's nightly-only
#[pollster::test]exists, and would replace.block_on()with.await, but it wouldn't help with unwraps. Proc-macros also can make rust-analyzer unhappy.
|
If we're going to switch to tokio, |
|
Can that do better than AFAIK |
|
I think some test runners let you return a |
1b337d6 to
bffcccd
Compare
|
Btw, if we keep this PR, we should also discuss what we call the method. Let me know if you prefer a different name, such as |
Tokio's test runner supports that, but we don't get the error location in the error message in case of failure, unless we use IMO, tokio's runner (or |
Did they just forget to mention it on https://docs.rs/tokio/latest/tokio/attr.test.html then? Do you have an example of what looks like? |
|
I am not entirely sure, but I think all test runners are meant to support returning Result, so maybe the tokio docs didn't think it's worth mentioning (though it certainly would be helpful if they did). Here's an example of the alternative approaches: https://gist.github.com/ilyagr/ad182a098781d403a923612fc57690e5 Update: I just noticed AI put imports inside the test functions. Sorry, it seems too minor to fix up now that I posted stack traces that refer to line numbers. To run it quickly, you can do something like: cargo init --name demo /tmp/demo && \
cargo add --manifest-path /tmp/demo/Cargo.toml eyre pollster --features pollster/macro && \
cargo add --manifest-path /tmp/demo/Cargo.toml tokio --features full,macros,rt-multi-thread,test-util && \
curl -sLf https://gist.githubusercontent.com/ilyagr/ad182a098781d403a923612fc57690e5/raw/7eaf1e077e6778599cf92c64d63f797cdf453575/block_unwrap_alternatives.rs -o /tmp/demo/src/main.rsThen Result for me: This also illustrates how each test needs to change. IMO, the advantage of Apparently, using |
|
And here's a comparison between To see this, add the following to the demo from the gist in my last comment: trait TestUnwrap {
type Output;
#[track_caller]
fn test_unwrap(self) -> Self::Output;
}
impl<T, E: std::fmt::Debug> TestUnwrap for Result<T, E> {
type Output = T;
#[track_caller]
fn test_unwrap(self) -> T { self.unwrap() }
}
impl<T> TestUnwrap for Option<T> {
type Output = T;
#[track_caller]
fn test_unwrap(self) -> T { self.unwrap() }
}
trait FutureTestExt: std::future::Future + Sized {
#[track_caller]
fn block_unwrap(self) -> <Self::Output as TestUnwrap>::Output
where Self::Output: TestUnwrap,
{
use pollster::FutureExt as _;
self.block_on().test_unwrap()
}
}
impl<F: std::future::Future + Sized> FutureTestExt for F {}
#[test]
fn test_pr9050_style() {
let commit = read_commit(0).block_unwrap();
assert_eq!(commit.id, 2);
let parent = read_parent(&commit).block_unwrap().unwrap();
assert_eq!(parent.id, 1);
} |
|
Oh, another option (if people are interested) might be to write our own version of I believe we'd just need to make the It'd still have the minor limitation that My mild preference is to use this PR as-is, but implementing the above or any of the options (including doing nothing) are fine. |
|
Here's an AI-generated 30-line implementation of https://gist.github.com/ilyagr/9c51d69e209d2aed640d0e3298ad07f0 We'd need to add Tests using this (to be concatenated with both gists): use pollster::FutureExt as _;
#[test]
fn test_magic_result() -> MagicResult {
let commit = read_commit(0).block_on()?;
assert_eq!(commit.id, 2);
let parent = read_parent(&commit).block_on()?.unwrap();
assert_eq!(parent.id, 1);
Ok(())
}
#[pollster::test] // Or #[tokio::test]
async fn test_magic_result() -> MagicResult {
let commit = read_commit(0).await?;
assert_eq!(commit.id, 2);
let parent = read_parent(&commit).await?.unwrap();
assert_eq!(parent.id, 1);
Ok(())
} |
|
That looks good to me. I don't feel strongly about which solution we go with. Does anyone else? |
|
Do I understand correctly that the consensus is to drop this PR? |
|
I'm still at
I'm having an AI try to implement the TestResult. It has some limitations, but I'm not sure whether they are worth worrying about. The limitations I'm aware of so far:
Update: Though |
…Result from parent commit This commit should be comparable with #9050 As of this writing, this replaced all but out of 1053 instances of `.block().unwrap()`. The remaining ones (as counted by AI): ``` ┌──────────────────┬───────┬────────────────────────────────────────────────────┐ │ Category │ Count │ Could convert? │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ testutils │ 20 │ No — return concrete types, would cascade to │ │ helpers │ │ hundreds of callers across all test files │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Closures │ 53 │ No — closures return () or concrete values │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Test helpers │ 22 │ Technically yes, but adds ~70 ? at call sites to │ │ │ │ save 22 unwraps │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Non-convertible │ 4 │ No — CommandError (2), Pin<Box<dyn Future>> (1), │ │ │ │ closure in test_matrix (1) │ └──────────────────┴───────┴────────────────────────────────────────────────────┘ ```
|
OK, I have a competing #9107. I'll see if I can make it output short backtraces, that will be ugly and might not work at all. If not for that, I think I now prefer it to this approach. |
…Result from parent commit This commit should be comparable with #9050 As of this writing, this replaced all but out of 1053 instances of `.block().unwrap()`. The remaining ones (as counted by AI): ``` ┌──────────────────┬───────┬────────────────────────────────────────────────────┐ │ Category │ Count │ Could convert? │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ testutils │ 20 │ No — return concrete types, would cascade to │ │ helpers │ │ hundreds of callers across all test files │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Closures │ 53 │ No — closures return () or concrete values │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Test helpers │ 22 │ Technically yes, but adds ~70 ? at call sites to │ │ │ │ save 22 unwraps │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Non-convertible │ 4 │ No — CommandError (2), Pin<Box<dyn Future>> (1), │ │ │ │ closure in test_matrix (1) │ └──────────────────┴───────┴────────────────────────────────────────────────────┘ ```
…Result from parent commit This commit should be comparable with #9050 As of this writing, this replaced all but out of 1053 instances of `.block().unwrap()`. The remaining ones (as counted by AI): ``` ┌──────────────────┬───────┬────────────────────────────────────────────────────┐ │ Category │ Count │ Could convert? │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ testutils │ 20 │ No — return concrete types, would cascade to │ │ helpers │ │ hundreds of callers across all test files │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Closures │ 53 │ No — closures return () or concrete values │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Test helpers │ 22 │ Technically yes, but adds ~70 ? at call sites to │ │ │ │ save 22 unwraps │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Non-convertible │ 4 │ No — CommandError (2), Pin<Box<dyn Future>> (1), │ │ │ │ closure in test_matrix (1) │ └──────────────────┴───────┴────────────────────────────────────────────────────┘ ```
…Result from parent commit This commit should be comparable with #9050 As of this writing, this replaced all but out of 1053 instances of `.block().unwrap()`. The remaining ones (as counted by AI): ``` ┌──────────────────┬───────┬────────────────────────────────────────────────────┐ │ Category │ Count │ Could convert? │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ testutils │ 20 │ No — return concrete types, would cascade to │ │ helpers │ │ hundreds of callers across all test files │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Closures │ 53 │ No — closures return () or concrete values │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Test helpers │ 22 │ Technically yes, but adds ~70 ? at call sites to │ │ │ │ save 22 unwraps │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Non-convertible │ 4 │ No — CommandError (2), Pin<Box<dyn Future>> (1), │ │ │ │ closure in test_matrix (1) │ └──────────────────┴───────┴────────────────────────────────────────────────────┘ ```
…Result from parent commit This commit should be comparable with #9050 As of this writing, this replaced all but out of 1053 instances of `.block().unwrap()`. The remaining ones (as counted by AI): ``` ┌──────────────────┬───────┬────────────────────────────────────────────────────┐ │ Category │ Count │ Could convert? │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ testutils │ 20 │ No — return concrete types, would cascade to │ │ helpers │ │ hundreds of callers across all test files │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Closures │ 53 │ No — closures return () or concrete values │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Test helpers │ 22 │ Technically yes, but adds ~70 ? at call sites to │ │ │ │ save 22 unwraps │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Non-convertible │ 4 │ No — CommandError (2), Pin<Box<dyn Future>> (1), │ │ │ │ closure in test_matrix (1) │ └──────────────────┴───────┴────────────────────────────────────────────────────┘ ```
I don't follow the detailed comparison, but if anyhow/eyre produces readable output, that seems better than introducing our own test helpers. At some point, our internal might depend on tokio runtime. |
…Result from parent commit This commit should be comparable with jj-vcs#9050 As of this writing, this replaced all but out of 1053 instances of `.block().unwrap()`. The remaining ones (as counted by AI): ``` ┌──────────────────┬───────┬────────────────────────────────────────────────────┐ │ Category │ Count │ Could convert? │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ testutils │ 20 │ No — return concrete types, would cascade to │ │ helpers │ │ hundreds of callers across all test files │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Closures │ 53 │ No — closures return () or concrete values │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Test helpers │ 22 │ Technically yes, but adds ~70 ? at call sites to │ │ │ │ save 22 unwraps │ ├──────────────────┼───────┼────────────────────────────────────────────────────┤ │ Non-convertible │ 4 │ No — CommandError (2), Pin<Box<dyn Future>> (1), │ │ │ │ closure in test_matrix (1) │ └──────────────────┴───────┴────────────────────────────────────────────────────┘ ```
This was all written by Gemini CLI. I'm happy to drop it if people don't like it.
Checklist
If applicable:
CHANGELOG.mdREADME.md,docs/,demos/)cli/src/config-schema.json)how it works, how it's organized), including any code drafted by AI
eye towards deleting anything that is irrelevant, clarifying anything that
is confusing, and adding details that are relevant. This includes, for
example, commit descriptions, PR descriptions, and code comments.