Skip to content

Commit 8049c6a

Browse files
committed
format: render stack traces in results
Now when a test fails, we get output like the below, which allows us to see where things actually went wrong. This is a bit fugly because it displays the stack trace twice. This is because we're using thiserror which doesn't really provide us a nice way to override Display implementations (perhaps we should just hand craft those, but then what's the point of the macro?). This also requires us to use nightly, because std::backtrace and related functionality are not in stable yet. Hopefully they will arrive soon. (I did not add in all the macro tests for feature, since we don't necessarily care about building on stable right now...) Anyway, let's say this: Fixes #22 for now. Hopefully someone can come along in 6 months when the situation looks better and make things prettier, but for now this at least gives us functional backtraces. test tests::test_put_blob_correct_hash ... ok thread 'index::tests::test_can_open_new_index' panicked at 'called `Result::unwrap()` on an `Err` value: invalid image schema: -1 0: oci::index::Index::open at ./src/index.rs:46:68 1: oci::index::tests::test_can_open_new_index at ./src/index.rs:74:9 2: oci::index::tests::test_can_open_new_index::{{closure}} at ./src/index.rs:70:5 3: core::ops::function::FnOnce::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/ops/function.rs:227:5 4: core::ops::function::FnOnce::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/ops/function.rs:227:5 test::__rust_begin_short_backtrace at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/test/src/lib.rs:576:5 5: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/alloc/src/boxed.rs:1546:9 <std::panic::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panic.rs:344:9 std::panicking::try::do_call at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panicking.rs:379:40 std::panicking::try at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panicking.rs:343:19 std::panic::catch_unwind at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panic.rs:431:14 test::run_test_in_process at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/test/src/lib.rs:599:18 test::run_test::run_test_inner::{{closure}} at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/test/src/lib.rs:491:39 6: test::run_test::run_test_inner::{{closure}} at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/test/src/lib.rs:518:37 std::sys_common::backtrace::__rust_begin_short_backtrace at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/sys_common/backtrace.rs:125:18 7: std::thread::Builder::spawn_unchecked::{{closure}}::{{closure}} at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/thread/mod.rs:474:17 <std::panic::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panic.rs:344:9 std::panicking::try::do_call at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panicking.rs:379:40 std::panicking::try at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panicking.rs:343:19 std::panic::catch_unwind at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panic.rs:431:14 std::thread::Builder::spawn_unchecked::{{closure}} at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/thread/mod.rs:473:30 core::ops::function::FnOnce::call_once{{vtable.shim}} at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/ops/function.rs:227:5 8: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/alloc/src/boxed.rs:1546:9 <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/alloc/src/boxed.rs:1546:9 std::sys::unix::thread::Thread::new::thread_start at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/sys/unix/thread.rs:71:17 9: start_thread 10: clone ', oci/src/index.rs:74:45 stack backtrace: thread 'tests::test_put_get_index' panicked at 'called `Result::unwrap()` on an `Err` value: invalid image schema: -1 0: oci::index::Index::open at ./src/index.rs:46:68 1: oci::Image::get_index at ./src/lib.rs:130:9 2: oci::tests::test_put_get_index at ./src/lib.rs:186:22 3: oci::tests::test_put_get_index::{{closure}} at ./src/lib.rs:173:5 4: core::ops::function::FnOnce::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/ops/function.rs:227:5 5: core::ops::function::FnOnce::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/ops/function.rs:227:5 test::__rust_begin_short_backtrace at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/test/src/lib.rs:576:5 6: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/alloc/src/boxed.rs:1546:9 <std::panic::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panic.rs:344:9 std::panicking::try::do_call at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panicking.rs:379:40 std::panicking::try at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panicking.rs:343:19 std::panic::catch_unwind at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panic.rs:431:14 test::run_test_in_process at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/test/src/lib.rs:599:18 test::run_test::run_test_inner::{{closure}} at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/test/src/lib.rs:491:39 7: test::run_test::run_test_inner::{{closure}} at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/test/src/lib.rs:518:37 std::sys_common::backtrace::__rust_begin_short_backtrace at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/sys_common/backtrace.rs:125:18 8: std::thread::Builder::spawn_unchecked::{{closure}}::{{closure}} at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/thread/mod.rs:474:17 <std::panic::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panic.rs:344:9 std::panicking::try::do_call at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panicking.rs:379:40 std::panicking::try at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panicking.rs:343:19 std::panic::catch_unwind at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panic.rs:431:14 std::thread::Builder::spawn_unchecked::{{closure}} at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/thread/mod.rs:473:30 core::ops::function::FnOnce::call_once{{vtable.shim}} at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/ops/function.rs:227:5 9: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/alloc/src/boxed.rs:1546:9 <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/alloc/src/boxed.rs:1546:9 std::sys::unix::thread::Thread::new::thread_start at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/sys/unix/thread.rs:71:17 10: start_thread 11: clone ', oci/src/lib.rs:186:41 0: rust_begin_unwind at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panicking.rs:493:5 1: core::panicking::panic_fmt at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/panicking.rs:92:14 2: core::result::unwrap_failed at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/result.rs:1355:5 3: core::result::Result<T,E>::unwrap at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/result.rs:1037:23 4: oci::index::tests::test_can_open_new_index at ./src/index.rs:74:9 5: oci::index::tests::test_can_open_new_index::{{closure}} at ./src/index.rs:70:5 6: core::ops::function::FnOnce::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/ops/function.rs:227:5 7: core::ops::function::FnOnce::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/ops/function.rs:227:5 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. stack backtrace: 0: rust_begin_unwind at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/std/src/panicking.rs:493:5 1: core::panicking::panic_fmt at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/panicking.rs:92:14 2: core::result::unwrap_failed at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/result.rs:1355:5 3: core::result::Result<T,E>::unwrap at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/result.rs:1037:23 4: oci::tests::test_put_get_index at ./src/lib.rs:186:22 5: oci::tests::test_put_get_index::{{closure}} at ./test index::tests::test_can_open_new_index ... src/lib.rs:FAILED173:5 6: core::ops::function::FnOnce::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/ops/function.rs:227:5 7: core::ops::function::FnOnce::call_once at /rustc/132b4e5d167b7e622fcc11fa2b67b931105b4de1/library/core/src/ops/function.rs:227:5 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. Signed-off-by: Tycho Andersen <[email protected]>
1 parent 468d21e commit 8049c6a

File tree

8 files changed

+97
-57
lines changed

8 files changed

+97
-57
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ jobs:
1313
- uses: actions/checkout@v2
1414
- uses: actions-rs/toolchain@v1
1515
with:
16-
toolchain: stable
16+
toolchain: nightly
17+
components: clippy, rustfmt
1718
- name: install dependencies
1819
run: |
1920
sudo apt-get install libfuse-dev libzstd-dev libxxhash-dev

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
SRC=$(shell find . -name \*.rs | grep -v "^./target")
22

33
target/debug/puzzlefs: $(SRC)
4-
cargo build
4+
cargo +nightly build
55

66
.PHONY: check
77
check:
8-
RUST_BACKTRACE=1 cargo test -- --nocapture
8+
RUST_BACKTRACE=1 cargo +nightly test -- --nocapture
99

1010
.PHONY: lint
1111
lint: $(SRC)
1212
rustfmt --check $(SRC)
13-
cargo clippy --all-targets --all-features -- -D warnings -A clippy::upper-case-acronyms
13+
cargo +nightly clippy --all-targets --all-features -- -D warnings -A clippy::upper-case-acronyms
1414

1515
.PHONY: fmt
1616
fmt:

format/src/error.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
extern crate serde_cbor;
2+
extern crate serde_json;
3+
4+
use std::backtrace::Backtrace;
5+
use std::error::Error;
6+
use std::fmt;
7+
use std::io;
8+
use std::os::raw::c_int;
9+
10+
use nix::errno::Errno;
11+
use thiserror::Error;
12+
13+
#[derive(Error)]
14+
pub enum WireFormatError {
15+
#[error("cannot turn local ref into a digest")]
16+
LocalRefError(Backtrace),
17+
#[error("cannot seek to other blob")]
18+
SeekOtherError(Backtrace),
19+
#[error("no value present")]
20+
ValueMissing(Backtrace),
21+
#[error("invalid image schema: {0}")]
22+
InvalidImageSchema(i32, Backtrace),
23+
#[error("invalid image version: {0}")]
24+
InvalidImageVersion(String, Backtrace),
25+
#[error("fs error: {0}")]
26+
IOError(#[from] io::Error, Backtrace),
27+
#[error("deserialization error (cbor): {0}")]
28+
CBORError(#[from] serde_cbor::Error, Backtrace),
29+
#[error("deserialization error (json): {0}")]
30+
JSONError(#[from] serde_json::Error, Backtrace),
31+
}
32+
33+
impl WireFormatError {
34+
pub fn to_errno(&self) -> c_int {
35+
match self {
36+
WireFormatError::LocalRefError(..) => Errno::EINVAL as c_int,
37+
WireFormatError::SeekOtherError(..) => Errno::ESPIPE as c_int,
38+
WireFormatError::ValueMissing(..) => Errno::ENOENT as c_int,
39+
WireFormatError::InvalidImageSchema(..) => Errno::EINVAL as c_int,
40+
WireFormatError::InvalidImageVersion(..) => Errno::EINVAL as c_int,
41+
WireFormatError::IOError(ioe, ..) => {
42+
ioe.raw_os_error().unwrap_or(Errno::EINVAL as i32) as c_int
43+
}
44+
WireFormatError::CBORError(..) => Errno::EINVAL as c_int,
45+
WireFormatError::JSONError(..) => Errno::EINVAL as c_int,
46+
}
47+
}
48+
49+
pub fn from_errno(errno: Errno) -> Self {
50+
Self::IOError(
51+
io::Error::from_raw_os_error(errno as i32),
52+
Backtrace::capture(),
53+
)
54+
}
55+
}
56+
57+
impl fmt::Debug for WireFormatError {
58+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {
59+
// This sucks, but otherwise we get astonishingly ugly stack traces. (In both cases, they
60+
// get rendered twice when we do .unwrap() of an error in tests, which sucks, but...)
61+
write!(f, "{}", &self)?;
62+
self.backtrace()
63+
.map(|b| write!(f, "\n{}", b))
64+
.unwrap_or(Ok(()))
65+
}
66+
}
67+
68+
pub type Result<T> = std::result::Result<T, WireFormatError>;

format/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1+
#![feature(backtrace)]
12
mod types;
23
pub use types::*;
4+
5+
mod error;
6+
pub use error::*;

format/src/types.rs

Lines changed: 5 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,30 @@
11
extern crate serde_cbor;
2-
extern crate serde_json;
32
extern crate xattr;
43

4+
use std::backtrace::Backtrace;
55
use std::convert::TryInto;
66
use std::ffi::OsString;
77
use std::fs;
88
use std::io;
99
use std::io::{Read, Seek};
1010
use std::mem;
11-
use std::os::raw::c_int;
1211
use std::os::unix::fs::{FileTypeExt, MetadataExt};
1312
use std::path::Path;
1413
use std::vec::Vec;
1514

16-
use nix::errno::Errno;
1715
use nix::sys::stat;
1816
use serde::de::Error as SerdeError;
1917
use serde::de::Visitor;
2018
use serde::{Deserialize, Deserializer, Serialize, Serializer};
21-
use thiserror::Error;
2219

2320
use compression::{Compression, Decompressor};
2421

22+
use crate::error::{Result, WireFormatError};
23+
2524
// To get off the ground here, we just use serde and cbor for most things, except for the fixed
2625
// size Inode which depends being a fixed size (and cbor won't generate it that way) in the later
2726
// format.
2827

29-
#[derive(Error, Debug)]
30-
pub enum WireFormatError {
31-
#[error("cannot turn local ref into a digest")]
32-
LocalRefError,
33-
#[error("cannot seek to other blob")]
34-
SeekOtherError,
35-
#[error("no value present")]
36-
ValueMissing,
37-
#[error("invalid image schema")]
38-
InvalidImageSchema(i32),
39-
#[error("invalid image version")]
40-
InvalidImageVersion(String),
41-
#[error("fs error")]
42-
IOError(#[from] io::Error),
43-
#[error("deserialization error (cbor)")]
44-
CBORError(#[from] serde_cbor::Error),
45-
#[error("deserialization error (json)")]
46-
JSONError(#[from] serde_json::Error),
47-
}
48-
49-
impl WireFormatError {
50-
pub fn to_errno(&self) -> c_int {
51-
match self {
52-
WireFormatError::LocalRefError => Errno::EINVAL as c_int,
53-
WireFormatError::SeekOtherError => Errno::ESPIPE as c_int,
54-
WireFormatError::ValueMissing => Errno::ENOENT as c_int,
55-
WireFormatError::InvalidImageSchema(_) => Errno::EINVAL as c_int,
56-
WireFormatError::InvalidImageVersion(_) => Errno::EINVAL as c_int,
57-
WireFormatError::IOError(ioe) => {
58-
ioe.raw_os_error().unwrap_or(Errno::EINVAL as i32) as c_int
59-
}
60-
WireFormatError::CBORError(_) => Errno::EINVAL as c_int,
61-
WireFormatError::JSONError(_) => Errno::EINVAL as c_int,
62-
}
63-
}
64-
65-
pub fn from_errno(errno: Errno) -> Self {
66-
Self::IOError(io::Error::from_raw_os_error(errno as i32))
67-
}
68-
}
69-
70-
pub type Result<T> = std::result::Result<T, WireFormatError>;
71-
7228
/*
7329
*
7430
* TODO: use these wrappers like the spec says
@@ -92,7 +48,7 @@ fn read_one<'a, T: Deserialize<'a>, R: Read>(r: R) -> Result<T> {
9248
// read one value.
9349
let mut iter = serde_cbor::Deserializer::from_reader(r).into_iter::<T>();
9450
let v = iter.next().transpose()?;
95-
v.ok_or(WireFormatError::ValueMissing)
51+
v.ok_or_else(|| WireFormatError::ValueMissing(Backtrace::capture()))
9652
}
9753

9854
#[derive(Serialize, Deserialize, Debug)]
@@ -491,7 +447,7 @@ impl MetadataBlob {
491447

492448
pub fn seek_ref(&mut self, r: &BlobRef) -> Result<u64> {
493449
match r.kind {
494-
BlobRefKind::Other { .. } => Err(WireFormatError::SeekOtherError),
450+
BlobRefKind::Other { .. } => Err(WireFormatError::SeekOtherError(Backtrace::capture())),
495451
BlobRefKind::Local => self
496452
.f
497453
.seek(io::SeekFrom::Start(r.offset))

oci/src/descriptor.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::backtrace::Backtrace;
12
use std::collections::HashMap;
23
use std::convert::{TryFrom, TryInto};
34
use std::fmt;
@@ -45,7 +46,7 @@ impl TryFrom<BlobRef> for Digest {
4546
fn try_from(v: BlobRef) -> std::result::Result<Self, Self::Error> {
4647
match v.kind {
4748
BlobRefKind::Other { digest } => Ok(Digest(digest)),
48-
BlobRefKind::Local => Err(WireFormatError::LocalRefError),
49+
BlobRefKind::Local => Err(WireFormatError::LocalRefError(Backtrace::capture())),
4950
}
5051
}
5152
}
@@ -55,7 +56,7 @@ impl TryFrom<&BlobRef> for Digest {
5556
fn try_from(v: &BlobRef) -> std::result::Result<Self, Self::Error> {
5657
match v.kind {
5758
BlobRefKind::Other { digest } => Ok(Digest(digest)),
58-
BlobRefKind::Local => Err(WireFormatError::LocalRefError),
59+
BlobRefKind::Local => Err(WireFormatError::LocalRefError(Backtrace::capture())),
5960
}
6061
}
6162
}

oci/src/index.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::backtrace::Backtrace;
12
use std::collections::HashMap;
23
use std::fs;
34
use std::path::Path;
@@ -40,7 +41,10 @@ impl Index {
4041
let index_file = fs::File::open(p)?;
4142
let index = serde_json::from_reader::<_, Index>(index_file)?;
4243
if index.version != PUZZLEFS_SCHEMA_VERSION {
43-
Err(WireFormatError::InvalidImageSchema(index.version))
44+
Err(WireFormatError::InvalidImageSchema(
45+
index.version,
46+
Backtrace::capture(),
47+
))
4448
} else {
4549
Ok(index)
4650
}

oci/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
#![feature(backtrace)]
2+
13
extern crate hex;
24

5+
use std::backtrace::Backtrace;
36
use std::convert::TryFrom;
47
use std::fs;
58
use std::io;
@@ -54,7 +57,10 @@ impl<'a> Image<'a> {
5457
let layout_file = fs::File::open(oci_dir.join(IMAGE_LAYOUT_PATH))?;
5558
let layout = serde_json::from_reader::<_, OCILayout>(layout_file)?;
5659
if layout.version != PUZZLEFS_IMAGE_LAYOUT_VERSION {
57-
Err(WireFormatError::InvalidImageVersion(layout.version))
60+
Err(WireFormatError::InvalidImageVersion(
61+
layout.version,
62+
Backtrace::capture(),
63+
))
5864
} else {
5965
Ok(Image { oci_dir })
6066
}

0 commit comments

Comments
 (0)