Skip to content

tests: replace many .unwrap()s with ?s using a new TestResult helper#9107

Open
ilyagr wants to merge 4 commits intomainfrom
ig/test-result-magic
Open

tests: replace many .unwrap()s with ?s using a new TestResult helper#9107
ilyagr wants to merge 4 commits intomainfrom
ig/test-result-magic

Conversation

@ilyagr
Copy link
Contributor

@ilyagr ilyagr commented Mar 14, 2026

This is an alternative to #9050, also AI-generated. I have not reviewed it super-carefully at this point. I do follow the first commit :)

Here is a demo of stack traces:

There was an additional issue that, with Rust's stdlib, this always gave the RUST_BACKTRACE=full stack trace experience instead of RUST_BACKTRACE=1. AI fixed it using the backtrace crate, I have not carefully reviewed that commit yet.

Help in reviewing is welcome :)

An alternative approach might be to use https://github.com/athre0z/color-backtrace (we can make color optional IIUC, but not its adjustments to make the bakctrace prettier; we'd probably then want to make all test panics use its backtrace), or one of the crates that uses (https://github.com/yaahc/btparse?), or fork it/copy its logic

$ cargo test -p jj-lib simple_op_store::tests::test_read_write_view
    Finished `test` profile [optimized + debuginfo] target(s) in 0.14s
     Running unittests src/lib.rs (target/debug/deps/jj_lib-b91f32213840aea6)

running 1 test
test simple_op_store::tests::test_read_write_view ... FAILED

failures:

---- simple_op_store::tests::test_read_write_view stdout ----
Error: Object deadbeef of type view not found

Location: lib/src/simple_op_store.rs:1077:25

note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    simple_op_store::tests::test_read_write_view

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1166 filtered out; finished in 0.01s

error: test failed, to rerun pass `-p jj-lib --lib`

$ # Sorry for the weird indentation, this is slightly simulated
$ RUST_BACKTRACE=1 cargo test -p jj-lib simple_op_store::tests::test_read_write_view
     ---- simple_op_store::tests::test_read_write_view stdout ----
     Error: Object deadbeef of type view not found

     Location: lib/src/simple_op_store.rs:1077:25
     stack backtrace:
        0: trace
                  at /Users/ilyagr/.cargo/registry/src/index.crates.io-1949cf8c6b
     5b557f/backtrace-0.3.76/src/backtrace/libunwind.rs:117

     trace_unsynchronized<backtrace::capture::{impl#4}::create::{closure_env#0}>
                  at /Users/ilyagr/.cargo/registry/src/index.crates.io-1949cf8c6b
     5b557f/backtrace-0.3.76/src/backtrace/mod.rs:66
           trace<backtrace::capture::{impl#4}::create::{closure_env#0}>
                  at /Users/ilyagr/.cargo/registry/src/index.crates.io-1949cf8c6b
     5b557f/backtrace-0.3.76/src/backtrace/mod.rs:53
           create
                  at /Users/ilyagr/.cargo/registry/src/index.crates.io-1949cf8c6b
     5b557f/backtrace-0.3.76/src/capture.rs:294
        1: <testutils::ErrorAndBacktrace as
     core::convert::From<jj_lib::op_store::OpStoreError>>::from
                  at /Users/ilyagr/dev/jj/lib/testutils/src/lib.rs:105
        2: <core::result::Result<(), testutils::ErrorAndBacktrace> as core::ops::
     try_trait::FromResidual<core::result::Result<core::convert::Infallible,
     jj_lib::op_store::OpStoreError>>>::from_residual
                  at /Users/ilyagr/.rustup/toolchains/nightly-aarch64-apple-darwi
     n/lib/rustlib/src/rust/library/core/src/result.rs:2189
           jj_lib::simple_op_store::tests::test_read_write_view
                  at /Users/ilyagr/dev/jj/lib/src/simple_op_store.rs:1077
        3: jj_lib::simple_op_store::tests::test_read_write_view::{closure#0}
                  at /Users/ilyagr/dev/jj/lib/src/simple_op_store.rs:1068
           <jj_lib::simple_op_store::tests::test_read_write_view::{closure#0} as
     core::ops::function::FnOnce<()>>::call_once
                  at /Users/ilyagr/.rustup/toolchains/nightly-aarch64-apple-darwi
     n/lib/rustlib/src/rust/library/core/src/ops/function.rs:250
     note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose
      backtrace.


     failures:

$ RUST_BACKTRACE=full...
     ---- simple_op_store::tests::test_read_write_view stdout ----
     Error: Object deadbeef of type view not found

     Location: lib/src/simple_op_store.rs:1077:25
     stack backtrace:
        0: trace
                  at /Users/ilyagr/.cargo/registry/src/index.crates.io-1949cf8c6b
     5b557f/backtrace-0.3.76/src/backtrace/libunwind.rs:117

     trace_unsynchronized<backtrace::capture::{impl#4}::create::{closure_env#0}>
                  at /Users/ilyagr/.cargo/registry/src/index.crates.io-1949cf8c6b
     5b557f/backtrace-0.3.76/src/backtrace/mod.rs:66
           trace<backtrace::capture::{impl#4}::create::{closure_env#0}>
                  at /Users/ilyagr/.cargo/registry/src/index.crates.io-1949cf8c6b
     5b557f/backtrace-0.3.76/src/backtrace/mod.rs:53
           create
                  at /Users/ilyagr/.cargo/registry/src/index.crates.io-1949cf8c6b
     5b557f/backtrace-0.3.76/src/capture.rs:294
        1: <testutils::ErrorAndBacktrace as
     core::convert::From<jj_lib::op_store::OpStoreError>>::from
                  at /Users/ilyagr/dev/jj/lib/testutils/src/lib.rs:105
        2: <core::result::Result<(), testutils::ErrorAndBacktrace> as core::ops::
     try_trait::FromResidual<core::result::Result<core::convert::Infallible,
     jj_lib::op_store::OpStoreError>>>::from_residual
                  at /Users/ilyagr/.rustup/toolchains/nightly-aarch64-apple-darwi
     n/lib/rustlib/src/rust/library/core/src/result.rs:2189
           jj_lib::simple_op_store::tests::test_read_write_view
                  at /Users/ilyagr/dev/jj/lib/src/simple_op_store.rs:1077
        3: jj_lib::simple_op_store::tests::test_read_write_view::{closure#0}
                  at /Users/ilyagr/dev/jj/lib/src/simple_op_store.rs:1068
           <jj_lib::simple_op_store::tests::test_read_write_view::{closure#0} as
     core::ops::function::FnOnce<()>>::call_once
                  at /Users/ilyagr/.rustup/toolchains/nightly-aarch64-apple-darwi
     n/lib/rustlib/src/rust/library/core/src/ops/function.rs:250
        4: <fn() -> core::result::Result<(), alloc::string::String> as
     core::ops::function::FnOnce<()>>::call_once
                  at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core
     /src/ops/function.rs:250
           test::__rust_begin_short_backtrace::<core::result::Result<(),
     alloc::string::String>, fn() -> core::result::Result<(),
     alloc::string::String>>
                  at
     /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/test/src/lib.rs:663
        5: test::run_test_in_process::{closure#0}
                  at
     /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/test/src/lib.rs:686
           <core::panic::unwind_safe::AssertUnwindSafe<test::run_test_in_process:
     :{closure#0}> as core::ops::function::FnOnce<()>>::call_once
                  at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core
     /src/panic/unwind_safe.rs:274
           std::panicking::catch_unwind::do_call::<core::panic::unwind_safe::Asse
     rtUnwindSafe<test::run_test_in_process::{closure#0}>,
     core::result::Result<(), alloc::string::String>>
                  at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/
     src/panicking.rs:581
           std::panicking::catch_unwind::<core::result::Result<(),
     alloc::string::String>, core::panic::unwind_safe::AssertUnwindSafe<test::run
     _test_in_process::{closure#0}>>
                  at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/
     src/panicking.rs:544
           std::panic::catch_unwind::<core::panic::unwind_safe::AssertUnwindSafe<
     test::run_test_in_process::{closure#0}>, core::result::Result<(),
     alloc::string::String>>
                  at
     /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panic.rs:359
           test::run_test_in_process
                  at
     /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/test/src/lib.rs:686
           test::run_test::{closure#0}
                  at
     /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/test/src/lib.rs:607
        6: test::run_test::{closure#1}
                  at
     /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/test/src/lib.rs:637
           std::sys::backtrace::__rust_begin_short_backtrace::<test::run_test::{c
     losure#1}, ()>
                  at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/
     src/sys/backtrace.rs:166
        7: std::thread::lifecycle::spawn_unchecked::<test::run_test::{closure#1},
      ()>::{closure#1}::{closure#0}
                  at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/
     src/thread/lifecycle.rs:91
           <core::panic::unwind_safe::AssertUnwindSafe<std::thread::lifecycle::sp
     awn_unchecked<test::run_test::{closure#1}, ()>::{closure#1}::{closure#0}> as
      core::ops::function::FnOnce<()>>::call_once
                  at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core
     /src/panic/unwind_safe.rs:274

     std::panicking::catch_unwind::do_call::<core::panic::unwind_safe::AssertUnwi
     ndSafe<std::thread::lifecycle::spawn_unchecked<test::run_test::{closure#1},
     ()>::{closure#1}::{closure#0}>, ()>
                  at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/
     src/panicking.rs:581
           std::panicking::catch_unwind::<(), core::panic::unwind_safe::AssertUnw
     indSafe<std::thread::lifecycle::spawn_unchecked<test::run_test::{closure#1},
      ()>::{closure#1}::{closure#0}>>
                  at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/
     src/panicking.rs:544
           std::panic::catch_unwind::<core::panic::unwind_safe::AssertUnwindSafe<
     std::thread::lifecycle::spawn_unchecked<test::run_test::{closure#1},
     ()>::{closure#1}::{closure#0}>, ()>
                  at
     /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panic.rs:359
           std::thread::lifecycle::spawn_unchecked::<test::run_test::{closure#1},
      ()>::{closure#1}
                  at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/
     src/thread/lifecycle.rs:89
           <std::thread::lifecycle::spawn_unchecked<test::run_test::{closure#1},
     ()>::{closure#1} as
     core::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
                  at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core
     /src/ops/function.rs:250
        8: <alloc::boxed::Box<dyn core::ops::function::FnOnce<(), Output = ()> +
     core::marker::Send> as core::ops::function::FnOnce<()>>::call_once
                  at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/allo
     c/src/boxed.rs:2240
           <std::sys::thread::unix::Thread>::new::thread_start
                  at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/
     src/sys/thread/unix.rs:118
        9: __pthread_cond_wait


     failures:
...

To reproduce this, use this diff on top of this PR:

diff --git a/lib/src/simple_op_store.rs b/lib/src/simple_op_store.rs
index 1a56d7d13d..6cebd1a485 100644
--- a/lib/src/simple_op_store.rs
+++ b/lib/src/simple_op_store.rs
@@ -1073,7 +1073,8 @@
         let store = SimpleOpStore::init(temp_dir.path(), root_data)?;
         let view = create_view();
         let view_id = store.write_view(&view).block_on()?;
-        let read_view = store.read_view(&view_id).block_on()?;
+        let bogus_id = ViewId::from_hex("deadbeef");
+        let read_view = store.read_view(&bogus_id).block_on()?;
         assert_eq!(read_view, view);
         Ok(())
     }

For comparison, if we replace that ? back to .unwrap(), we get this stack trace:

$ RUST_BACKTRACE=1 cargo test -p jj-lib simple_op_store::tests::test_read_write_view
running 1 test
test simple_op_store::tests::test_read_write_view ... FAILED

failures:

---- simple_op_store::tests::test_read_write_view stdout ----

thread 'simple_op_store::tests::test_read_write_view' (96054076) panicked at lib/src/simple_op_store.rs:1077:63:
called `Result::unwrap()` on an `Err` value: ObjectNotFound { object_type: "view", hash: "deadbeef", source: PathError { path: "/var/folders/lj/rv4h95_d0mxb9ryztzpz4qph0000gn/T/jj-test-C4vOA1/views/deadbeef", source: Os { code: 2, kind: NotFound, message: "No such file or directory" } } }
stack backtrace:
   0: __rustc::rust_begin_unwind
             at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panicking.rs:689:5
   1: core::panicking::panic_fmt
             at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core/src/panicking.rs:80:14
   2: core::result::unwrap_failed
             at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core/src/result.rs:1867:5
   3: <core::result::Result<jj_lib::op_store::View, jj_lib::op_store::OpStoreError>>::unwrap
             at /Users/ilyagr/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/result.rs:1233:23
   4: jj_lib::simple_op_store::tests::test_read_write_view
             at ./src/simple_op_store.rs:1077:63
   5: jj_lib::simple_op_store::tests::test_read_write_view::{closure#0}
             at ./src/simple_op_store.rs:1068:34
   6: <jj_lib::simple_op_store::tests::test_read_write_view::{closure#0} as core::ops::function::FnOnce<()>>::call_once
             at /Users/ilyagr/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
   7: <fn() -> core::result::Result<(), alloc::string::String> as core::ops::function::FnOnce<()>>::call_once
             at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.


failures:
    simple_op_store::tests::test_read_write_view

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1166 filtered out; finished in 0.06s

error: test failed, to rerun pass `-p jj-lib --lib`



$ RUST_BACKTRACE=full cargo test -p jj-lib simple_op_store::tests::test_read_write_view
warning: unused variable: `view_id`

running 1 test
test simple_op_store::tests::test_read_write_view ... FAILED

failures:

---- simple_op_store::tests::test_read_write_view stdout ----

thread 'simple_op_store::tests::test_read_write_view' (96076370) panicked at lib/src/simple_op_store.rs:1077:63:
called `Result::unwrap()` on an `Err` value: ObjectNotFound { object_type: "view", hash: "deadbeef", source: PathError { path: "/var/folders/lj/rv4h95_d0mxb9ryztzpz4qph0000gn/T/jj-test-PHl6WT/views/deadbeef", source: Os { code: 2, kind: NotFound, message: "No such file or directory" } } }
stack backtrace:
   0:        0x104e664a8 - std[a580ed78d70a0328]::backtrace_rs::backtrace::libunwind::trace
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/../../backtrace/src/backtrace/libunwind.rs:117:9
   1:        0x104e664a8 - std[a580ed78d70a0328]::backtrace_rs::backtrace::trace_unsynchronized::<std[a580ed78d70a0328]::sys::backtrace::_print_fmt::{closure#1}>
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/../../backtrace/src/backtrace/mod.rs:66:14
   2:        0x104e664a8 - std[a580ed78d70a0328]::sys::backtrace::_print_fmt
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/sys/backtrace.rs:74:9
   3:        0x104e664a8 - <<std[a580ed78d70a0328]::sys::backtrace::BacktraceLock>::print::DisplayBacktrace as core[97583a736526339]::fmt::Display>::fmt
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/sys/backtrace.rs:44:26
   4:        0x104e7b6c0 - <core[97583a736526339]::fmt::rt::Argument>::fmt
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core/src/fmt/rt.rs:152:76
   5:        0x104e7b6c0 - core[97583a736526339]::fmt::write
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core/src/fmt/mod.rs:1686:22
   6:        0x104e6ac08 - std[a580ed78d70a0328]::io::default_write_fmt::<alloc[3583cb2059ddce49]::vec::Vec<u8>>
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/io/mod.rs:639:11
   7:        0x104e6ac08 - <alloc[3583cb2059ddce49]::vec::Vec<u8> as std[a580ed78d70a0328]::io::Write>::write_fmt
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/io/mod.rs:1994:13
   8:        0x104e482e8 - <std[a580ed78d70a0328]::sys::backtrace::BacktraceLock>::print
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/sys/backtrace.rs:47:25
   9:        0x104e482e8 - std[a580ed78d70a0328]::panicking::default_hook::{closure#0}
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panicking.rs:292:27
  10:        0x104e5e008 - std[a580ed78d70a0328]::panicking::default_hook
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panicking.rs:316:9
  11:        0x104b15ea0 - <alloc[3583cb2059ddce49]::boxed::Box<dyn for<'a, 'b> core[97583a736526339]::ops::function::Fn<(&'a std[a580ed78d70a0328]::panic::PanicHookInfo<'b>,), Output = ()> + core[97583a736526339]::marker::Sync + core[97583a736526339]::marker::Send> as core[97583a736526339]::ops::function::Fn<(&std[a580ed78d70a0328]::panic::PanicHookInfo,)>>::call
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/alloc/src/boxed.rs:2254:9
  12:        0x104b15ea0 - test[826a02b2cb0cf4c3]::test_main_with_exit_callback::<test[826a02b2cb0cf4c3]::test_main::{closure#0}>::{closure#0}
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/test/src/lib.rs:145:21
  13:        0x104e5e3c0 - <alloc[3583cb2059ddce49]::boxed::Box<dyn for<'a, 'b> core[97583a736526339]::ops::function::Fn<(&'a std[a580ed78d70a0328]::panic::PanicHookInfo<'b>,), Output = ()> + core[97583a736526339]::marker::Sync + core[97583a736526339]::marker::Send> as core[97583a736526339]::ops::function::Fn<(&std[a580ed78d70a0328]::panic::PanicHookInfo,)>>::call
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/alloc/src/boxed.rs:2254:9
  14:        0x104e5e3c0 - std[a580ed78d70a0328]::panicking::panic_with_hook
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panicking.rs:833:13
  15:        0x104e48394 - std[a580ed78d70a0328]::panicking::panic_handler::{closure#0}
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panicking.rs:698:13
  16:        0x104e3f288 - std[a580ed78d70a0328]::sys::backtrace::__rust_end_short_backtrace::<std[a580ed78d70a0328]::panicking::panic_handler::{closure#0}, !>
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/sys/backtrace.rs:182:18
  17:        0x104e48f68 - __rustc[d65e30e194c7d9cd]::rust_begin_unwind
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panicking.rs:689:5
  18:        0x104ebbd5c - core[97583a736526339]::panicking::panic_fmt
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core/src/panicking.rs:80:14
  19:        0x104ebbb34 - core[97583a736526339]::result::unwrap_failed
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core/src/result.rs:1867:5
  20:        0x10498fb0c - <core[97583a736526339]::result::Result<jj_lib[a81c49c1ca9ff666]::op_store::View, jj_lib[a81c49c1ca9ff666]::op_store::OpStoreError>>::unwrap
                               at /Users/ilyagr/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/result.rs:1233:23
  21:        0x10498fb0c - jj_lib[a81c49c1ca9ff666]::simple_op_store::tests::test_read_write_view
                               at /Users/ilyagr/dev/jj/lib/src/simple_op_store.rs:1077:63
  22:        0x1049a712c - jj_lib[a81c49c1ca9ff666]::simple_op_store::tests::test_read_write_view::{closure#0}
                               at /Users/ilyagr/dev/jj/lib/src/simple_op_store.rs:1068:34
  23:        0x1049a712c - <jj_lib[a81c49c1ca9ff666]::simple_op_store::tests::test_read_write_view::{closure#0} as core[97583a736526339]::ops::function::FnOnce<()>>::call_once
                               at /Users/ilyagr/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
  24:        0x104b0c20c - <fn() -> core[97583a736526339]::result::Result<(), alloc[3583cb2059ddce49]::string::String> as core[97583a736526339]::ops::function::FnOnce<()>>::call_once
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core/src/ops/function.rs:250:5
  25:        0x104b0c20c - test[826a02b2cb0cf4c3]::__rust_begin_short_backtrace::<core[97583a736526339]::result::Result<(), alloc[3583cb2059ddce49]::string::String>, fn() -> core[97583a736526339]::result::Result<(), alloc[3583cb2059ddce49]::string::String>>
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/test/src/lib.rs:663:18
  26:        0x104b16418 - test[826a02b2cb0cf4c3]::run_test_in_process::{closure#0}
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/test/src/lib.rs:686:74
  27:        0x104b16418 - <core[97583a736526339]::panic::unwind_safe::AssertUnwindSafe<test[826a02b2cb0cf4c3]::run_test_in_process::{closure#0}> as core[97583a736526339]::ops::function::FnOnce<()>>::call_once
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core/src/panic/unwind_safe.rs:274:9
  28:        0x104b16418 - std[a580ed78d70a0328]::panicking::catch_unwind::do_call::<core[97583a736526339]::panic::unwind_safe::AssertUnwindSafe<test[826a02b2cb0cf4c3]::run_test_in_process::{closure#0}>, core[97583a736526339]::result::Result<(), alloc[3583cb2059ddce49]::string::String>>
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panicking.rs:581:40
  29:        0x104b16418 - std[a580ed78d70a0328]::panicking::catch_unwind::<core[97583a736526339]::result::Result<(), alloc[3583cb2059ddce49]::string::String>, core[97583a736526339]::panic::unwind_safe::AssertUnwindSafe<test[826a02b2cb0cf4c3]::run_test_in_process::{closure#0}>>
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panicking.rs:544:19
  30:        0x104b16418 - std[a580ed78d70a0328]::panic::catch_unwind::<core[97583a736526339]::panic::unwind_safe::AssertUnwindSafe<test[826a02b2cb0cf4c3]::run_test_in_process::{closure#0}>, core[97583a736526339]::result::Result<(), alloc[3583cb2059ddce49]::string::String>>
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panic.rs:359:14
  31:        0x104b16418 - test[826a02b2cb0cf4c3]::run_test_in_process
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/test/src/lib.rs:686:27
  32:        0x104b16418 - test[826a02b2cb0cf4c3]::run_test::{closure#0}
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/test/src/lib.rs:607:43
  33:        0x104b12d6c - test[826a02b2cb0cf4c3]::run_test::{closure#1}
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/test/src/lib.rs:637:41
  34:        0x104b12d6c - std[a580ed78d70a0328]::sys::backtrace::__rust_begin_short_backtrace::<test[826a02b2cb0cf4c3]::run_test::{closure#1}, ()>
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/sys/backtrace.rs:166:18
  35:        0x104b186f0 - std[a580ed78d70a0328]::thread::lifecycle::spawn_unchecked::<test[826a02b2cb0cf4c3]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/thread/lifecycle.rs:91:13
  36:        0x104b186f0 - <core[97583a736526339]::panic::unwind_safe::AssertUnwindSafe<std[a580ed78d70a0328]::thread::lifecycle::spawn_unchecked<test[826a02b2cb0cf4c3]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}> as core[97583a736526339]::ops::function::FnOnce<()>>::call_once
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core/src/panic/unwind_safe.rs:274:9
  37:        0x104b186f0 - std[a580ed78d70a0328]::panicking::catch_unwind::do_call::<core[97583a736526339]::panic::unwind_safe::AssertUnwindSafe<std[a580ed78d70a0328]::thread::lifecycle::spawn_unchecked<test[826a02b2cb0cf4c3]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}>, ()>
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panicking.rs:581:40
  38:        0x104b186f0 - std[a580ed78d70a0328]::panicking::catch_unwind::<(), core[97583a736526339]::panic::unwind_safe::AssertUnwindSafe<std[a580ed78d70a0328]::thread::lifecycle::spawn_unchecked<test[826a02b2cb0cf4c3]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}>>
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panicking.rs:544:19
  39:        0x104b186f0 - std[a580ed78d70a0328]::panic::catch_unwind::<core[97583a736526339]::panic::unwind_safe::AssertUnwindSafe<std[a580ed78d70a0328]::thread::lifecycle::spawn_unchecked<test[826a02b2cb0cf4c3]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}>, ()>
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/panic.rs:359:14
  40:        0x104b186f0 - std[a580ed78d70a0328]::thread::lifecycle::spawn_unchecked::<test[826a02b2cb0cf4c3]::run_test::{closure#1}, ()>::{closure#1}
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/thread/lifecycle.rs:89:26
  41:        0x104b186f0 - <std[a580ed78d70a0328]::thread::lifecycle::spawn_unchecked<test[826a02b2cb0cf4c3]::run_test::{closure#1}, ()>::{closure#1} as core[97583a736526339]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/core/src/ops/function.rs:250:5
  42:        0x104e65708 - <alloc[3583cb2059ddce49]::boxed::Box<dyn core[97583a736526339]::ops::function::FnOnce<(), Output = ()> + core[97583a736526339]::marker::Send> as core[97583a736526339]::ops::function::FnOnce<()>>::call_once
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/alloc/src/boxed.rs:2240:9
  43:        0x104e65708 - <std[a580ed78d70a0328]::sys::thread::unix::Thread>::new::thread_start
                               at /rustc/b41f22de2a13a0babd28771e96feef4c309f54aa/library/std/src/sys/thread/unix.rs:118:17
  44:        0x1952b7bc8 - __pthread_cond_wait


failures:
    simple_op_store::tests::test_read_write_view

Checklist

If applicable:

  • I have updated CHANGELOG.md
  • I have updated the documentation (README.md, docs/, demos/)
  • I have updated the config schema (cli/src/config-schema.json)
  • I have added/updated tests to cover my changes
  • I fully understand the code that I am submitting (what it does,
    how it works, how it's organized), including any code drafted by an LLM.
  • For any prose generated by an LLM, I have proof-read and copy-edited with
    an 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.

The intention is so that tests can use `?` instead of `unwrap`s. This
uses `#[track_caller]` and ensures that the backtrace is printed,
(if `RUST_BACKTRACE` is set) similar to the output of a panic on `.unwrap()`.

The actual conversion of tests is left to the following commits.
@ilyagr ilyagr force-pushed the ig/test-result-magic branch 3 times, most recently from fe6e5cc to 4e8715e Compare March 14, 2026 03:09
ilyagr added 3 commits March 13, 2026 20:11
`Backtrace::capture()` always gives the `RUST_BACKTRACE=full`
experience, not the `RUST_BACKTRACE=1` experience. This commit
allows both using the `backtrace` crate.

https://github.com/rust-lang/backtrace-rs

`backtrace` crate is actually part of `std` codebase, but stable Rust
does not expose its API. It can be used separately.
…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)
│

└──────────────────┴───────┴────────────────────────────────────────────────────┘
```
@ilyagr ilyagr force-pushed the ig/test-result-magic branch from 4e8715e to a98b159 Compare March 14, 2026 03:11
@ilyagr ilyagr marked this pull request as ready for review March 14, 2026 03:33
@ilyagr ilyagr requested a review from a team as a code owner March 14, 2026 03:33
@ilyagr
Copy link
Contributor Author

ilyagr commented Mar 14, 2026

I'm marking this as ready for review because I'm running a little out of steam. The backtrace mess is... a mess, though the AI-generated code seems to work. Opinions on how this compares to #9050 are welcome.

I might look into whether the bttrace crate makes the code less of a mess, but it itself relies on string matching.

@ilyagr ilyagr marked this pull request as draft March 14, 2026 03:47
@ilyagr
Copy link
Contributor Author

ilyagr commented Mar 14, 2026

Turns out there is a better way. Backtrace::capture has a long and a short output format, {:#} vs {}. Not my favorite Rust feature.

Or maybe not. That only affects whether hex addresses are shown

Some possibilities/discussions:

rust-lang/rust#105413
rust-lang/backtrace-rs#502 (comment)

@ilyagr
Copy link
Contributor Author

ilyagr commented Mar 14, 2026

If anyone is interested here are two alternatives (still AI-generated) for the backtrace parsing code:

A. Don't add any dependencies. Do everything manually ((instead of the second commit):

diff --git a/lib/testutils/src/lib.rs b/lib/testutils/src/lib.rs
index c662eb06b9..0c9c99c09d 100644
--- a/lib/testutils/src/lib.rs
+++ b/lib/testutils/src/lib.rs
@@ -112,7 +112,42 @@
         write!(f, "{}\n\nLocation: {}", self.inner, self.location)?;
         match self.backtrace.status() {
             std::backtrace::BacktraceStatus::Captured => {
-                write!(f, "\n\n{}", self.backtrace)?;
+                let full = self.backtrace.to_string();
+                let is_full = env::var_os("RUST_BACKTRACE").is_some_and(|v| v == "full");
+                if is_full {
+                    write!(f, "\n\n{full}")?;
+                } else {
+                    write!(f, "\nstack backtrace:")?;
+                    // Trim frames after `__rust_begin_short_backtrace`, like
+                    // the panic handler does for `RUST_BACKTRACE=1`.
+                    let mut count = 0;
+                    for line in full.lines() {
+                        if line.contains("__rust_begin_short_backtrace") {
+                            break;
+                        }
+                        // Renumber frames starting from 0
+                        let trimmed = line.trim_start();
+                        if trimmed.starts_with("at ") {
+                            write!(f, "\n{line}")?;
+                        } else if trimmed.starts_with(char::is_numeric) {
+                            let rest = trimmed.trim_start_matches(char::is_numeric);
+                            write!(f, "\n{count:>4}{rest}")?;
+                            count += 1;
+                        } else {
+                            write!(f, "\n{line}")?;
+                        }
+                    }
+                    if count == 0 {
+                        // Marker not found; fall back to full output
+                        write!(f, "\n{full}")?;
+                    } else {
+                        write!(
+                            f,
+                            "\nnote: Some details are omitted, run with \
+                             `RUST_BACKTRACE=full` for a verbose backtrace."
+                        )?;
+                    }
+                }
             }
             _ => {
                 write!(

B. Add backtrace_ext crate in addition to backtrace. Then (on top of the second commit),

diff --git a/lib/testutils/src/lib.rs b/lib/testutils/src/lib.rs
index 062d13d3ca..d1d7da19aa 100644
--- a/lib/testutils/src/lib.rs
+++ b/lib/testutils/src/lib.rs
@@ -121,23 +121,17 @@
         let is_full = env::var_os("RUST_BACKTRACE").is_some_and(|v| v == "full");
         let mut bt = self.backtrace.clone();
         bt.resolve();
-        // Find the range of interesting frames by looking for the short
-        // backtrace markers that the test harness inserts.
-        let frames = bt.frames();
-        let begin_marker = frames.iter().position(|frame| {
-            frame.symbols().iter().any(|s| {
-                s.name()
-                    .is_some_and(|n| format!("{n:#}").contains("__rust_begin_short_backtrace"))
-            })
-        });
-        let end_frames = if let Some(idx) = begin_marker.filter(|_| !is_full) {
-            &frames[..idx]
+        let frames: Vec<_> = if is_full {
+            bt.frames()
+                .iter()
+                .map(|frame| (frame, 0..frame.symbols().len()))
+                .collect()
         } else {
-            frames
+            backtrace_ext::short_frames_strict(&bt).collect()
         };
         write!(f, "\nstack backtrace:")?;
-        for (i, frame) in end_frames.iter().enumerate() {
-            let symbols = frame.symbols();
+        for (i, (frame, sub_range)) in frames.iter().enumerate() {
+            let symbols = &frame.symbols()[sub_range.clone()];
             if symbols.is_empty() {
                 write!(f, "\n{i:>4}: <unknown>")?;
             }
@@ -156,7 +150,7 @@
                 }
             }
         }
-        if !is_full && begin_marker.is_some() {
+        if !is_full {
             write!(
                 f,
                 "\nnote: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose \

Not much shorter, but bactrace-ext is pretty carefully tested.

@ilyagr ilyagr marked this pull request as ready for review March 14, 2026 04:51
@ilyagr ilyagr added the help wanted Extra attention is needed label Mar 14, 2026
@glehmann
Copy link
Contributor

I'm not sure to understand what this custom approach brings compared to just using eyre. Is it about avoiding a new dependency?. Or is there something this simple example doesn't show?

For reference, here is the result with just using

/// Convenient return type for test functions.
pub type TestResult = eyre::Result<()>;
image

@martinvonz
Copy link
Member

That eyre example looks good to me. It's nice that it includes the location. Do you know if it can be made to include the location in the test case of the ? operator too?

@glehmann
Copy link
Contributor

Or is there something this simple example doesn't show?

The elephant I missed in the room: using eyre alone doesn't allow printing the stacktrace with RUST_BACKTRACE.
It requires using either color-eyre or stable-eyre as an extra dependency.

Do you know if it can be made to include the location in the test case of the ? operator too?

I don't think it can, but I might be wrong—I don’t consider myself an expert in error handling in Rust!

@glehmann
Copy link
Contributor

See #9111 as an alternative to this PR using color-eyre

@ilyagr
Copy link
Contributor Author

ilyagr commented Mar 15, 2026

Do you know if it can be made to include the location in the test case of the ? operator too?

I'm not sure I understand the question, I thought that's what that location in the screenshot is.

One additional thing that eyre gets us is the nice printing of the error's cause chain. We could do it without eyre too, but we'd essentially be talking its code.

Unfortunately, eyre suffers from several problems if we care about backtraces. 😞

@glehmann
Copy link
Contributor

glehmann commented Mar 15, 2026

(Oops, wrong PR)

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

Labels

help wanted Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants