Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature backtrace #526

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
53a048d
Add 'handle-panics' feature
target-san Oct 5, 2024
639f686
Add conditional scoped panic hook implementation
target-san Oct 5, 2024
343d74f
Silence out panic from test case
target-san Oct 5, 2024
b438970
Updated changelog
target-san Oct 5, 2024
6081fe8
Rename internal module to `internal`
target-san Oct 5, 2024
eb7fdfc
Licensing header
target-san Oct 5, 2024
bb9a900
'backtrace' feature and changelog entry
target-san Oct 5, 2024
b9f344f
Conditional implementation of backtrace object
target-san Oct 5, 2024
4b03828
Extend `Reason` with ability to store backtrace
target-san Oct 5, 2024
bfffcf4
Collect backtrace upon panic or with prop_assert
target-san Oct 5, 2024
55499bf
Slight tweak of reason's detailed display
target-san Oct 6, 2024
692e56a
Tweak errors display
target-san Oct 6, 2024
b135667
Add location handling and construction from PanicInfo, if needed
target-san Oct 6, 2024
c74159c
Fix prop_assert macro
target-san Oct 6, 2024
1eb50fe
Reason from PanicInfo
target-san Oct 6, 2024
9031491
Formatting of affected files
target-san Oct 6, 2024
c21f418
Nicer addition of location info to Reason
target-san Oct 7, 2024
7e1fdd2
Spelling
target-san Oct 7, 2024
e26d531
Merge branch 'main' into feature/backtrace
target-san Dec 12, 2024
eaa8223
Fix test
target-san Dec 12, 2024
cdcb423
Simplify mod, enable only if handle-panics is enabled
target-san Dec 12, 2024
670351b
Allow construct from `dyn Any`
target-san Dec 12, 2024
60a14b3
Separate paths ofr handle-panics enabled and disabled
target-san Dec 12, 2024
9211183
Run tests for handle-panics and backtrace
target-san Dec 12, 2024
d6b3009
Use old-style access to thread-local cells, to allow builds on pinned
target-san Dec 12, 2024
f7337f1
Fix some import inconsistencies
target-san Dec 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ jobs:
run: cd proptest && cargo build --verbose
- name: Run tests
run: cd proptest && cargo test --verbose
- name: Run tests handle-panics
run: cd proptest && cargo test --verbose --features handle-panics
- name: Run tests backtrace
run: cd proptest && cargo test --verbose --features backtrace
- name: Run tests handle-panics+backtrace
run: cd proptest && cargo test --verbose --features "handle-panics backtrace"
- name: Build coverage no-default-features
if: ${{ matrix.build == 'stable' }}
env:
Expand Down
2 changes: 2 additions & 0 deletions proptest/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

- When running persisted regressions, the most recently added regression is now run first.
- Added `handle-panics` feature which enables catching panics raised in tests and turning them into failures
- Added `backtrace` feature which enables capturing backtraces for both test failures and panics,
if `handle-panics` feature is enabled

## 1.5.0

Expand Down
6 changes: 6 additions & 0 deletions proptest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ bit-set = ["dep:bit-set", "dep:bit-vec"]
# In particular, hides all intermediate panics flowing into stderr during shrink phase
handle-panics = ["std"]

# Enables gathering of failure backtraces
# * when test failure is reported via `prop_assert_*` macro
# * when normal assertion fails or panic fires, if `handle-panics` feature is enabled too
backtrace = ["std"]

[dependencies]
bitflags = "2"
unarray = "0.1.4"
Expand Down Expand Up @@ -123,5 +128,6 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dev-dependencies]
assert_matches = "1.5.0"
regex = "1.0"
trybuild = "=1.0.0"
1 change: 1 addition & 0 deletions proptest/src/arbitrary/_alloc/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::std_facade::{
binary_heap, btree_map, btree_set, fmt, linked_list, vec, vec_deque, Arc,
BTreeMap, BTreeSet, BinaryHeap, Box, LinkedList, Rc, Vec, VecDeque,
};
#[cfg(feature = "std")]
use core::hash::Hash;
use core::ops::{Bound, RangeInclusive};

Expand Down
7 changes: 4 additions & 3 deletions proptest/src/sugar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -751,10 +751,11 @@ macro_rules! prop_assert {

($cond:expr, $($fmt:tt)*) => {
if !$cond {
let message = format!($($fmt)*);
let message = format!("{} at {}:{}", message, file!(), line!());
return ::core::result::Result::Err(
$crate::test_runner::TestCaseError::fail(message));
$crate::test_runner::TestCaseError::fail(
$crate::test_runner::Reason::with_location_and_backtrace(format!($($fmt)*))
)
);
}
};
}
Expand Down
135 changes: 135 additions & 0 deletions proptest/src/test_runner/backtrace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//-
// Copyright 2024
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use core::fmt;
/// Holds test failure backtrace, if captured
///
/// If feature `backtrace` is disabled, it's a zero-sized struct with no logic
///
/// If `backtrace` is enabled, attempts to capture backtrace using `std::backtrace::Backtrace` -
/// if requested
#[derive(Clone, Default)]
pub struct Backtrace(internal::Backtrace);

impl Backtrace {
/// Creates empty backtrace object
///
/// Used when client code doesn't care
pub fn empty() -> Self {
Self(internal::Backtrace::empty())
}
/// Tells whether there's backtrace captured
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Attempts to capture backtrace - but only if `backtrace` feature is enabled
#[inline(always)]
pub fn capture() -> Self {
Self(internal::Backtrace::capture())
}
}

impl fmt::Debug for Backtrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}

impl fmt::Display for Backtrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}

#[cfg(feature = "backtrace")]
mod internal {
use core::fmt;
use std::backtrace as bt;
use std::sync::Arc;

// `std::backtrace::Backtrace` isn't `Clone`, so we have
// to use `Arc` to also maintain `Send + Sync`
#[derive(Clone, Default)]
pub struct Backtrace(Option<Arc<bt::Backtrace>>);

impl Backtrace {
pub fn empty() -> Self {
Self(None)
}

pub fn is_empty(&self) -> bool {
self.0.is_none()
}

#[inline(always)]
pub fn capture() -> Self {
let bt = bt::Backtrace::capture();
// Store only if we have backtrace
if bt.status() == bt::BacktraceStatus::Captured {
Self(Some(Arc::new(bt)))
} else {
Self(None)
}
}
}

impl fmt::Debug for Backtrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref arc) = self.0 {
fmt::Debug::fmt(arc.as_ref(), f)
} else {
Ok(())
}
}
}

impl fmt::Display for Backtrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref arc) = self.0 {
fmt::Display::fmt(arc.as_ref(), f)
} else {
Ok(())
}
}
}
}

#[cfg(not(feature = "backtrace"))]
mod internal {
use core::fmt;

#[derive(Clone, Default)]
pub struct Backtrace;

impl Backtrace {
pub fn empty() -> Self {
Self
}

pub fn is_empty(&self) -> bool {
true
}

pub fn capture() -> Self {
Self
}
}

impl fmt::Debug for Backtrace {
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(())
}
}

impl fmt::Display for Backtrace {
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(())
}
}
}
6 changes: 4 additions & 2 deletions proptest/src/test_runner/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ impl fmt::Display for TestCaseError {
TestCaseError::Reject(ref whence) => {
write!(f, "Input rejected at {}", whence)
}
TestCaseError::Fail(ref why) => write!(f, "Case failed: {}", why),
TestCaseError::Fail(ref why) => {
write!(f, "Case failed: {}", why.display_detailed())
}
}
}
}
Expand Down Expand Up @@ -115,7 +117,7 @@ impl<T: fmt::Debug> fmt::Display for TestError<T> {
match *self {
TestError::Abort(ref why) => write!(f, "Test aborted: {}", why),
TestError::Fail(ref why, ref what) => {
writeln!(f, "Test failed: {}.", why)?;
writeln!(f, "Test failed: {}", why.display_detailed())?;
write!(f, "minimal failing input: {:#?}", what)
}
}
Expand Down
2 changes: 2 additions & 0 deletions proptest/src/test_runner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//! You do not normally need to access things in this module directly except
//! when implementing new low-level strategies.

mod backtrace;
mod config;
mod errors;
mod failure_persistence;
Expand All @@ -21,6 +22,7 @@ mod replay;
mod result_cache;
mod rng;
mod runner;
#[cfg(feature = "handle-panics")]
mod scoped_panic_hook;

pub use self::config::*;
Expand Down
Loading