panic_control/
panic_control.rs

1// Copyright 2017 Mikhail Zabaluev <mikhail.zabaluev@gmail.com>
2// See the COPYRIGHT file at the top-level directory of this source tree.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10
11//! Controlled panics using dynamic type checking.
12//!
13//! Sometimes there is a need to test how Rust code behaves on occurrence
14//! of a panic. A panic can be invoked on purpose in a thread spawned by the
15//! test and the effects observed after the thread is joined.
16//! The problem with "benign" panics is that it may be cumbersome to tell them
17//! apart from panics indicating actual errors, such as assertion failures.
18//!
19//! Another issue is the behavior of the default panic hook.
20//! It is very useful for getting information about the cause of an
21//! unexpected thread panic, but for tests causing panics on purpose it
22//! produces annoying output noise. The panic hook can be overridden,
23//! but custom panic hooks affect the entire program, which in typical
24//! usage is the test runner; it is easy to misuse them causing important
25//! error information to go unreported.
26//!
27//! The simplest way, as provided by the standard library, to propagate
28//! a panic that occurred in a child thread to the thread that spawned it
29//! is to call `unwrap` on the result of `JoinHandle::join`. Unfortunately,
30//! due to [an issue](https://github.com/rust-lang/rfcs/issues/1389) with
31//! the implementation of `Any`, the resulting panic message does not relay
32//! information from the child thread's panic.
33//!
34//! This crate provides utilities and an ergonomic interface for testing
35//! panics in a controlled and output-friendly way using dynamic type checks
36//! to discern between expected and unexpected panics.
37//!
38//! # Expected Panic Type
39//!
40//! The recommended way to designate panics as expected is by using values of
41//! a custom type as the parameter for `panic!`. The type could be as simple
42//! as a token unit-like struct, or it can be equipped to carry additional
43//! information from the panic site.
44//! Any panic value type shall be `Sized`, `'static`, and `Send`.
45//! For the value to be usable in testing, it should also implement
46//! at least `Debug` and `PartialEq`.
47//!
48//! # Examples
49//!
50//! ```
51//! use panic_control::{Context, Outcome};
52//! use panic_control::{chain_hook_ignoring, spawn_quiet};
53//! use panic_control::ThreadResultExt;
54//!
55//! use std::thread;
56//!
57//! #[derive(Debug, PartialEq, Eq)]
58//! enum Expected {
59//!     Token,
60//!     Int(i32),
61//!     String(String)
62//! }
63//!
64//! // Rust's stock test runner does not provide a way to do global
65//! // initialization and the tests are run in parallel in a random
66//! // order by default. So this is our solution, to be called at
67//! // the beginning of every test exercising a panic with an
68//! // Expected value.
69//! fn silence_expected_panics() {
70//!     use std::sync::{Once, ONCE_INIT};
71//!     static HOOK_ONCE: Once = ONCE_INIT;
72//!     HOOK_ONCE.call_once(|| {
73//!         chain_hook_ignoring::<Expected>()
74//!     });
75//! }
76//!
77//! # struct TypeUnderTest;
78//! # impl TypeUnderTest {
79//! #     fn new() -> TypeUnderTest { TypeUnderTest }
80//! #     fn doing_fine(&self) -> bool { true }
81//! # }
82//! // ...
83//!
84//! silence_expected_panics();
85//! let thread_builder = thread::Builder::new()
86//!                      .name("My panicky thread".into());
87//! let ctx = Context::<Expected>::from(thread_builder);
88//! let h = ctx.spawn(|| {
89//!     let unwind_me = TypeUnderTest::new();
90//!     assert!(unwind_me.doing_fine());
91//!          // ^-- If this fails, join() will return Err
92//!     panic!(Expected::String("Rainbows and unicorns!".into()));
93//! });
94//! let outcome = h.join().unwrap_or_propagate();
95//! match outcome {
96//!     Outcome::Panicked(Expected::String(s)) => {
97//!         println!("thread panicked as expected: {}", s);
98//!     }
99//!     _ => panic!("unexpected value returned from join()")
100//! }
101//!
102//! let ctx = Context::<Expected>::new();
103//! let h = ctx.spawn_quiet(|| {
104//!     let h = spawn_quiet(|| {
105//!         panic!("Sup dawg, we heard you like panics \
106//!                 so we put a panic in your panic!");
107//!     });
108//!     h.join().unwrap_or_propagate();
109//! });
110//! let res = h.join();
111//! let msg = res.panic_value_as_str().unwrap();
112//! assert!(msg.contains("panic in your panic"));
113//! ```
114
115use std::any::Any;
116use std::cell::Cell;
117use std::fmt;
118use std::fmt::Debug;
119use std::marker;
120use std::panic;
121use std::panic::{PanicInfo};
122use std::thread;
123use std::sync::{Once, ONCE_INIT};
124
125
126/// Enumerates the expected outcomes from joining a panic-checked thread.
127///
128/// `Outcome` values are returned in the `Ok` result variant
129/// of the `join()` method of a `CheckedJoinHandle`.
130#[derive(Copy, Clone, Debug, PartialEq, Eq)]
131pub enum Outcome<T, P> {
132    /// Indicates that the thread closure has
133    /// returned normally, and provides the return value.
134    NoPanic(T),
135    /// Indicates that the thread has panicked with the expected type,
136    /// and provides the panic value.
137    Panicked(P)
138}
139
140impl<T, P> Outcome<T, P> {
141    /// Tests whether the value contains the variant `Panicked`.
142    pub fn has_panicked(&self) -> bool {
143        match *self {
144            Outcome::NoPanic(_) => false,
145            Outcome::Panicked(_) => true,
146        }
147    }
148}
149
150/// Wraps `std::thread::JoinHandle` for panic value discrimination.
151///
152/// A `CheckedJoinHandle` works like a standard `JoinHandle`,
153/// except that its `join()` method dynamically checks the type of
154/// the possible panic value for matching the type that is the
155/// parameter of the `Context` this handle was obtained from,
156/// and if the type matches, returns the resolved value in the
157/// "successful panic" result variant.
158///
159/// See the documentation of the `join()` method for details and
160/// an example of use.
161pub struct CheckedJoinHandle<T, P> {
162    thread_handle: thread::JoinHandle<T>,
163    phantom: marker::PhantomData<P>
164}
165
166impl<T, P> Debug for CheckedJoinHandle<T, P> {
167    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
168        f.write_str("CheckedJoinHandle { .. }")
169    }
170}
171
172impl<T, P: Any> CheckedJoinHandle<T, P> {
173
174    /// Works like `std::thread::JoinHandle::join()`, except that when
175    /// the child thread's panic value is of the expected type, it is
176    /// returned in `Ok(Outcome::Panicked(_))`. If the child thread's
177    /// closure returns normally, its return value is returned in
178    /// `Ok(Outcome::NoPanic(_))`
179    ///
180    /// # Examples
181    ///
182    /// ```
183    /// use panic_control::{Context, Outcome};
184    /// use std::thread;
185    ///
186    /// #[derive(Debug, PartialEq, Eq)]
187    /// struct Expected(pub u32);
188    ///
189    /// let ctx = Context::<Expected>::new();
190    /// let h = ctx.spawn(|| {
191    ///     panic!(Expected(42));
192    /// });
193    ///
194    /// let outcome = h.join().unwrap();
195    ///
196    /// match outcome {
197    ///     Outcome::Panicked(Expected(n)) => {
198    ///         println!("thread panicked as expected with {}", n);
199    ///     }
200    ///     _ => panic!("unexpected return value from join()")
201    /// }
202    /// ```
203    pub fn join(self) -> thread::Result<Outcome<T, P>> {
204        match self.thread_handle.join() {
205            Ok(rv) => Ok(Outcome::NoPanic(rv)),
206            Err(box_any) => {
207                match box_any.downcast::<P>() {
208                    Ok(p)  => Ok(Outcome::Panicked(*p)),
209                    Err(e) => Err(e)
210                }
211            }
212        }
213    }
214}
215
216impl<T, P> CheckedJoinHandle<T, P> {
217
218    /// Returns a reference to the underlying `JoinHandle`.
219    pub fn as_thread_join_handle(&self) -> &thread::JoinHandle<T> {
220        &self.thread_handle
221    }
222
223    /// Converts into the underlying `JoinHandle`,
224    /// giving up panic discrimination.
225    pub fn into_thread_join_handle(self) -> thread::JoinHandle<T> {
226        self.thread_handle
227    }
228}
229
230impl<T, P> AsRef<thread::JoinHandle<T>> for CheckedJoinHandle<T, P> {
231    fn as_ref(&self) -> &thread::JoinHandle<T> {
232        self.as_thread_join_handle()
233    }
234}
235
236impl<T, P> Into<thread::JoinHandle<T>> for CheckedJoinHandle<T, P> {
237    fn into(self) -> thread::JoinHandle<T> {
238        self.into_thread_join_handle()
239    }
240}
241
242/// The launch pad for a thread checked for the expected panic type.
243///
244/// The generic type `Context` serves as the type system's anchor for the
245/// expected type of the panic value, which is given as the type parameter.
246/// It can be constructed from an `std::thread::Builder` providing
247/// a customized thread configuration.
248///
249/// The method `spawn()` is used to spawn a new thread, similarly to
250/// how the function `std::thread::spawn()` works. See the documentation
251/// of the `spawn()` method for detailed description and examples.
252///
253/// # Examples
254///
255/// The example below demonstrates how to construct a `Context` from a
256/// configured `std::thread::Builder` using the implementation of
257/// the standard trait `From`.
258///
259/// ```
260/// use panic_control::Context;
261/// use std::thread;
262///
263/// #[derive(Debug, PartialEq, Eq)]
264/// struct ExpectedToken;
265///
266/// let thread_builder = thread::Builder::new()
267///                      .name("My panicky thread".into())
268///                      .stack_size(65 * 1024);
269/// let ctx = Context::<ExpectedToken>::from(thread_builder);
270/// let h = ctx.spawn(|| {
271///     // ...
272/// });
273/// h.join().unwrap();
274/// ```
275pub struct Context<P> {
276    thread_builder: thread::Builder,
277    phantom: marker::PhantomData<P>
278}
279
280impl<P> Debug for Context<P>
281{
282    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
283        f.debug_struct("Context")
284            .field("thread_builder", &self.thread_builder)
285            .finish()
286    }
287}
288
289impl<P: Any> Default for Context<P> {
290    fn default() -> Context<P> { Context::new() }
291}
292
293impl<P: Any> Context<P> {
294
295    /// Constructs a context with the default thread configuration.
296    ///
297    /// The type parameter can be specified explicitly:
298    ///
299    /// ```
300    /// # use panic_control::Context;
301    /// # #[derive(Debug, PartialEq)] struct Expected(pub i32);
302    /// let ctx = Context::<Expected>::new();
303    /// ```
304    pub fn new() -> Context<P> {
305        Context {
306            thread_builder: thread::Builder::new(),
307            phantom: marker::PhantomData
308        }
309    }
310
311    /// Spawns a new thread taking ownership of the `Context`, and
312    /// returns the `CheckedJoinHandle` for the thread. Other than the
313    /// return value, and panicking on an OS failure to create a thread,
314    /// this method behaves exactly like the `spawn()`
315    /// method of `std::thread::Builder` does, and if the `Context`
316    /// was constructed from a `std::thread::Builder`, its
317    /// thread configuration will be applied.
318    ///
319    /// # Panics
320    ///
321    /// Panics if the underlying call to `std::thread::Builder::spawn()`
322    /// returns an `Err` value. Any panics that function can cause apply
323    /// as well.
324    ///
325    /// # Examples
326    ///
327    /// The example below uses some kludges to work around a compiler
328    /// quirk:
329    ///
330    /// ```
331    /// # use panic_control::{Context, Outcome};
332    /// # #[derive(Debug, PartialEq)] struct Expected(pub i32);
333    /// let ctx = Context::<Expected>::new();
334    ///
335    /// #[allow(unreachable_code)]
336    /// let h = ctx.spawn(|| {
337    ///     panic!(Expected(42));
338    ///     ()
339    /// });
340    ///
341    /// let outcome = h.join().unwrap();
342    /// assert_eq!(outcome, Outcome::Panicked(Expected(42)));
343    /// ```
344    ///
345    /// Note that unless the unreachable return expression is present,
346    /// the compiler would have no information to infer the unspecified
347    /// first type parameter of `Outcome`, so it would settle on a default type
348    /// that is going to be changed to `!` in Rust 1.26. In previous versions
349    /// of the compiler, code that invokes this behavior is
350    /// [denied by a lint](https://github.com/rust-lang/rust/issues/39216),
351    /// so the following example does not compile:
352    ///
353    /// ```ignore
354    /// # use panic_control::{Context, Outcome};
355    /// # #[derive(Debug, PartialEq)] struct Expected(pub i32);
356    /// let ctx = Context::<Expected>::new();
357    /// let h = ctx.spawn(|| {
358    ///     panic!(Expected(42));
359    /// });
360    /// let outcome = h.join().unwrap();
361    /// assert_eq!(outcome, Outcome::Panicked(Expected(42)));
362    /// ```
363    ///
364    /// A way to avoid the future incompatibility without resorting
365    /// to warning overrides is to match the `Outcome` value without
366    /// touching the sensitive parts:
367    ///
368    /// ```
369    /// # use panic_control::{Context, Outcome};
370    /// # #[derive(Debug, PartialEq)] struct Expected(pub i32);
371    /// let ctx = Context::<Expected>::new();
372    /// let h = ctx.spawn(|| {
373    ///     panic!(Expected(42));
374    /// });
375    /// let outcome = h.join().unwrap();
376    /// match outcome {
377    ///     Outcome::Panicked(Expected(n)) => assert_eq!(n, 42),
378    ///     _ => panic!("join() returned an unexpected Outcome value")
379    /// }
380    /// ```
381    pub fn spawn<T, F>(self, f: F) -> CheckedJoinHandle<T, P>
382        where F: FnOnce() -> T,
383              F: Send + 'static,
384              T: Send + 'static
385    {
386        let thread_handle = self.thread_builder.spawn(f).unwrap();
387        CheckedJoinHandle {
388            thread_handle: thread_handle,
389            phantom: self.phantom
390        }
391    }
392
393    /// Like `spawn()`, but disables the panic hook
394    /// if the spawned thread panics.
395    ///
396    /// The process-global panic hook, either installed with
397    /// `std::panic::set_hook()` or the standard library default,
398    /// gets augmented with a filter that disables invocation of the
399    /// hook closure if the spawned thread panics.
400    ///
401    /// This function can be used in any order together with other
402    /// functions and methods of this crate that modify the panic hook.
403    ///
404    /// # Caveats
405    ///
406    /// Note that the suppression can apply to the default panic hook
407    /// that is normally used to report assertion failures and other
408    /// unexpected panics on the standard error stream.
409    /// The only remaining way to observe the panic is by checking
410    /// the result of `join()` for the spawned thread.
411    ///
412    /// Other code within the program that modifies the panic hook,
413    /// concurrently to, or after, a call to this function, may cause
414    /// the suppression not to work as intended. See the documentation
415    /// on the function `disable_hook_in_current_thread()` for possible
416    /// pitfalls.
417    ///
418    /// # Examples
419    ///
420    /// ```
421    /// # use panic_control::{Context, Outcome};
422    /// # #[derive(Debug, PartialEq)] struct Expected(pub i32);
423    /// let ctx = Context::<Expected>::new();
424    /// let h = ctx.spawn_quiet(|| {
425    ///     assert!(false, "I'm panicking, \
426    ///         but you can only learn about it through join()");
427    /// });
428    /// let res = h.join();
429    /// assert!(res.is_err());
430    /// ```
431    pub fn spawn_quiet<T, F>(self, f: F) -> CheckedJoinHandle<T, P>
432        where F: FnOnce() -> T,
433              F: Send + 'static,
434              T: Send + 'static
435    {
436        self.spawn(|| {
437            disable_hook_in_current_thread();
438            f()
439        })
440    }
441}
442
443impl<P: Any> From<thread::Builder> for Context<P> {
444    fn from(builder: thread::Builder) -> Context<P> {
445        Context {
446            thread_builder: builder,
447            phantom: marker::PhantomData
448        }
449    }
450}
451
452/// Helpful extension methods for `std::thread::Result`.
453///
454/// The `Result` type defined in `std::thread` is a
455/// specialization of the standard `Result` with a `Box<Any>`
456/// in the `Err` variant, which receives the payload
457/// value of a panic.
458/// As such, `Result` does not provide convenient ways
459/// to examine the content of the panic value. Furthermore,
460/// the generic implementations of `unwrap()` and related methods
461/// use the `Debug` implementation of the content of `Err` to format
462/// the panic message, which is
463/// [not very useful](https://github.com/rust-lang/rfcs/issues/1389)
464/// in case of `Any`.
465///
466/// When this trait is used in a lexical scope, it augments
467/// any `Result` value that matches the specialization of
468/// `std::thread::Result` with methods that facilitate
469/// examination and reporting of the possible string value
470/// which is most often found in the dynamically typed
471/// `Err` variant. The methods are meant to be used on the
472/// result of `std::thread::JoinHandle::join()` or
473/// `CheckedJoinHandle::join()`.
474///
475/// # Examples
476///
477/// ```
478/// use panic_control::ThreadResultExt;
479///
480/// use panic_control::Context;
481/// use std::thread;
482///
483/// let h = thread::spawn(|| {
484///     42
485/// });
486/// let n = h.join().unwrap_or_propagate();
487/// assert_eq!(n, 42);
488///
489/// #[derive(Debug, PartialEq, Eq)]
490/// struct Expected;
491///
492/// let ctx = Context::<Expected>::new();
493/// let h = ctx.spawn_quiet(|| {
494///     panic!("No coffee beans left in the bag!");
495/// });
496/// let res = h.join();
497/// let msg = res.panic_value_as_str().unwrap();
498/// println!("{}", msg);
499/// ```
500///
501pub trait ThreadResultExt<T> : sealed::ThreadResultExtSealed {
502
503    /// Unwraps a result, yielding the content of an `Ok`.
504    ///
505    /// # Panics
506    ///
507    /// Panics if the value is an `Err`, with a panic message appended with
508    /// the `Err`'s string value if that is found to be such, or a generic
509    /// message otherwise. The message is meant to relay information from
510    /// a panic in a child thread that is observed through this result value,
511    /// as returned by `std::thread::JoinHandle::join()` or
512    /// `CheckedJoinHandle::join()`.
513    fn unwrap_or_propagate(self) -> T;
514
515    /// If the value is an `Err` and its content is a string, returns the
516    /// content as a string slice. Otherwise returns `None`.
517    fn panic_value_as_str(&self) -> Option<&str>;
518}
519
520fn str_from_any(something: &Any) -> Option<&str> {
521    if let Some(s) = something.downcast_ref::<&'static str>() {
522        Some(*s)
523    } else if let Some(s) = something.downcast_ref::<String>() {
524        Some(&s[..])
525    } else {
526        None
527    }
528}
529
530fn propagate_panic(box_any: Box<Any + Send>) -> ! {
531    match str_from_any(box_any.as_ref()) {
532        Some(s) => panic!("observed an unexpected thread panic: {}", s),
533        None => panic!("observed an unexpected thread panic \
534                        with undetermined value")
535    }
536}
537
538mod sealed {
539    use std::thread;
540
541    // trait ThreadResultExt is sealed by this crate: it should only
542    // make sense for thread::Result.
543
544    pub trait ThreadResultExtSealed { }
545    impl<T> ThreadResultExtSealed for thread::Result<T> { }
546}
547
548impl<T> ThreadResultExt<T> for thread::Result<T> {
549
550    fn unwrap_or_propagate(self) -> T {
551        match self {
552            Ok(rv) => rv,
553            Err(e) => propagate_panic(e)
554        }
555    }
556
557    fn panic_value_as_str(&self) -> Option<&str> {
558        match *self {
559            Ok(_) => None,
560            Err(ref ref_box_any) => str_from_any(&**ref_box_any)
561        }
562    }
563}
564
565/// Augments the panic hook, filtering out panics of a particular type.
566///
567/// The current panic hook, either installed with `std::panic::set_hook()`
568/// or the standard library default, gets chained, again using
569/// `std::panic::set_hook()`, behind
570/// a dynamic type check for the panic payload. If it is found to be
571/// of the same type as the type parameter of this generic function,
572/// the chained hook is not called.
573///
574/// # Caveats
575///
576/// Every call to this function allocates state data and increases the
577/// filtering chain for the process-global panic hook, so it should not be
578/// called repeatedly unless necessary.
579/// Other code within the program that modifies the panic hook, concurrently
580/// to, or after, the call to this function, may cause the hook chain to stop
581/// working as intended.
582/// This function interoperates in a predictable way only with the other
583/// functions and methods of this crate that modify the panic hook,
584/// and only when used in strictly serialized order with those functions,
585/// unless noted otherwise in those functions' documentation.
586/// This function is only intended to be used in tests or initialization
587/// code of a program;
588/// libraries other than those designed for test purposes should avoid
589/// using it.
590///
591/// # Examples
592///
593/// ```
594/// use panic_control::chain_hook_ignoring;
595/// use std::sync::{Once, ONCE_INIT};
596///
597/// #[derive(Debug, PartialEq, Eq)]
598/// struct PanicToken;
599///
600/// #[derive(Debug, PartialEq, Eq)]
601/// struct PanicMessage(pub String);
602///
603/// static HOOK_ONCE: Once = ONCE_INIT;
604/// HOOK_ONCE.call_once(|| {
605///     chain_hook_ignoring::<PanicToken>();
606///     chain_hook_ignoring::<PanicMessage>();
607/// });
608/// ```
609pub fn chain_hook_ignoring<P: 'static>() {
610    chain_hook_ignoring_if(|_: &P| { true })
611}
612
613/// Augments the panic hook, filtering out panics with a
614/// statically typed closure.
615///
616/// The current panic hook, either installed with `std::panic::set_hook()`
617/// or the standard library default, gets chained, again using
618/// `std::panic::set_hook()`, behind
619/// the boolean predicate closure passed as the parameter, testing a value
620/// of a particular type.
621/// If the panic payload is found to be of the same type and the predicate
622/// returns true, the chained hook is not called.
623///
624/// # Caveats
625///
626/// Every call to this function allocates state data and increases the
627/// filtering chain for the process-global panic hook, so it should not be
628/// called repeatedly unless necessary.
629/// Other code within the program that modifies the panic hook, concurrently
630/// to, or after, the call to this function, may cause the hook chain to stop
631/// working as intended.
632/// This function interoperates in a predictable way only with the other
633/// functions and methods of this crate that modify the panic hook,
634/// and only when used in strictly serialized order with those functions,
635/// unless noted otherwise in those functions' documentation.
636/// This function is only intended to be used in tests or initialization
637/// code of a program;
638/// libraries other than those designed for test purposes should avoid
639/// using it.
640///
641/// # Examples
642///
643/// The value types most commonly used in panics are `&'string str` or
644/// `String`, depending on whether the `panic!` macro was used in the single
645/// parameter form or the formatting form, respectively. The example below
646/// filters out either kind if the string message contains a particular
647/// substring.
648///
649/// ```
650/// use panic_control::chain_hook_ignoring_if;
651/// use std::sync::{Once, ONCE_INIT};
652///
653/// const MAGIC: &str = "Move along, nothing to see here";
654///
655/// static HOOK_ONCE: Once = ONCE_INIT;
656/// HOOK_ONCE.call_once(|| {
657///     chain_hook_ignoring_if(|s: &&'static str| {
658///         s.contains(MAGIC)
659///     });
660///     chain_hook_ignoring_if(|s: &String| {
661///         s.contains(MAGIC)
662///     });
663/// });
664/// ```
665pub fn chain_hook_ignoring_if<P, F>(predicate: F)
666    where F: Fn(&P) -> bool,
667          F: Send,
668          F: Sync,
669          F: 'static,
670          P: 'static
671{
672    chain_hook_ignoring_full(move |info| {
673        match info.payload().downcast_ref::<P>() {
674            Some(p) => predicate(p),
675            None => false
676        }
677    })
678}
679
680/// Augments the panic hook, filtering out panics with a free-form check.
681///
682/// The current panic hook, either installed with `std::panic::set_hook()`
683/// or the standard library default, gets chained, again using
684/// `std::panic::set_hook()`, behind
685/// the boolean predicate closure passed as the parameter, testing the
686/// `std::panic::PanicInfo` structure passed to the panic hook.
687/// If the predicate returns true, the chained hook is not called.
688///
689/// # Caveats
690///
691/// Every call to this function allocates state data and increases the
692/// filtering chain for the process-global panic hook, so it should not be
693/// called repeatedly unless necessary.
694/// Other code within the program that modifies the panic hook, concurrently
695/// to, or after, the call to this function, may cause the hook chain to stop
696/// working as intended.
697/// This function interoperates in a predictable way only with the other
698/// functions and methods of this crate that modify the panic hook,
699/// and only when used in strictly serialized order with those functions,
700/// unless noted otherwise in those functions' documentation.
701/// This function is only intended to be used in tests or initialization
702/// code of a program;
703/// libraries other than those designed for test purposes should avoid
704/// using it.
705///
706/// # Examples
707///
708/// This example filters out any non-string panics:
709///
710/// ```
711/// use panic_control::chain_hook_ignoring_full;
712/// use std::sync::{Once, ONCE_INIT};
713///
714/// static HOOK_ONCE: Once = ONCE_INIT;
715/// HOOK_ONCE.call_once(|| {
716///     chain_hook_ignoring_full(|info| {
717///         let payload = info.payload();
718///         !(payload.is::<&'static str>() ||
719///           payload.is::<String>())
720///     });
721/// });
722/// ```
723pub fn chain_hook_ignoring_full<F>(predicate: F)
724    where F: Fn(&PanicInfo) -> bool,
725          F: Send,
726          F: Sync,
727          F: 'static
728{
729    // Make sure the thread filter hook is set up,
730    // in case some other thread is calling
731    // {disable,enable}_hook_in_current_thread()
732    init_thread_filter_hook();
733    chain_hook_waive_init_thread_filter(predicate)
734}
735
736fn chain_hook_waive_init_thread_filter<F>(predicate: F)
737    where F: Fn(&PanicInfo) -> bool,
738          F: Send,
739          F: Sync,
740          F: 'static
741{
742    let next_hook = panic::take_hook();
743    panic::set_hook(Box::new(move |info| {
744            if !predicate(info) {
745                next_hook(info);
746            }
747        }));
748}
749
750thread_local!(static IGNORE_HOOK: Cell<bool> = Cell::new(false));
751
752fn init_thread_filter_hook() {
753    static HOOK_ONCE: Once = ONCE_INIT;
754    HOOK_ONCE.call_once(|| {
755        // Avoid recursion and deadlocking on HOOK_ONCE here
756        chain_hook_waive_init_thread_filter(|_| {
757            IGNORE_HOOK.with(|cell| { cell.get() })
758        });
759    });
760}
761
762/// Disables the panic hook for the current thread.
763///
764/// The process-global panic hook, either installed with
765/// `std::panic::set_hook()` or the standard library default,
766/// gets augmented with a filter that disables invocation of the
767/// hook closure if the thread that is calling this function panics.
768///
769/// This function does not allocate resources when called repeatedly in
770/// the same thread, and it can be used in any order together with other
771/// functions and methods of this crate that modify the panic hook.
772///
773/// # Caveats
774///
775/// Note that the suppression can apply to the default panic hook
776/// that is normally used to report assertion failures and other
777/// unexpected panics on the standard error stream.
778///
779/// Other code within the program that modifies the panic hook, concurrently
780/// to, or after, a call to this function, may cause the hook chain to stop
781/// working as intended.
782/// This function interoperates in a predictable way only with the other
783/// functions and methods of this crate that modify the panic hook.
784/// Libraries other than those designed for test purposes should avoid
785/// using this function.
786pub fn disable_hook_in_current_thread() {
787    init_thread_filter_hook();
788    IGNORE_HOOK.with(|cell| {
789        cell.set(true);
790    });
791}
792
793/// Enables the panic hook for the current thread.
794///
795/// If the panic hook has been disabled for the current thread with
796/// `disable_hook_in_current_thread()`, calling this function enables it
797/// back.
798///
799/// This function does not allocate resources when called repeatedly in
800/// the same thread, and it can be used in any order together with other
801/// functions and methods of this crate that modify the panic hook.
802///
803/// # Caveats
804///
805/// Other code within the program that modifies the panic hook, concurrently
806/// to, or after, a call to this function, may cause the hook chain to stop
807/// working as intended.
808/// This function interoperates only with the other
809/// functions and methods of this crate that modify the panic hook.
810/// Libraries other than those designed for test purposes should avoid
811/// using this function.
812pub fn enable_hook_in_current_thread() {
813    init_thread_filter_hook();
814    IGNORE_HOOK.with(|cell| {
815        cell.set(false);
816    });
817}
818
819/// Like `std::thread::spawn()`, but disables the panic hook
820/// if the spawned thread panics.
821///
822/// The process-global panic hook, either installed with
823/// `std::panic::set_hook()` or the standard library default,
824/// gets augmented with a filter that disables invocation of the
825/// hook closure if the spawned thread panics.
826///
827/// This function can be used in any order together with other
828/// functions and methods of this crate that modify the panic hook.
829///
830/// # Caveats
831///
832/// Note that the suppression can apply to the default panic hook
833/// that is normally used to report assertion failures and other
834/// unexpected panics on the standard error stream.
835/// The only remaining way to observe the panic is by checking
836/// the result of `join()` for the spawned thread.
837///
838/// Other code within the program that modifies the panic hook,
839/// concurrently to, or after, a call to this function, may cause
840/// the suppression not to work as intended. See the documentation
841/// on the function `disable_hook_in_current_thread()` for possible
842/// pitfalls.
843///
844/// # Examples
845///
846/// ```
847/// use panic_control::spawn_quiet;
848///
849/// let h = spawn_quiet(|| {
850///     assert!(false, "I'm panicking, \
851///         but you can only learn about it through join()");
852/// });
853/// let res = h.join();
854/// assert!(res.is_err());
855/// ```
856pub fn spawn_quiet<T, F>(f: F) -> thread::JoinHandle<T>
857    where F: FnOnce() -> T,
858          F: Send + 'static,
859          T: Send + 'static
860{
861    thread::spawn(|| {
862        disable_hook_in_current_thread();
863        f()
864    })
865}
866
867
868#[cfg(test)]
869mod tests {
870    use super::{Context, Outcome};
871    use super::{ThreadResultExt};
872    use super::chain_hook_ignoring;
873    use std::sync::{Once, ONCE_INIT};
874    use std::thread;
875
876    #[derive(Debug, PartialEq, Eq)]
877    struct Expected(pub u32);
878
879    fn silence_expected_panics() {
880        static GUARD: Once = ONCE_INIT;
881        GUARD.call_once(|| {
882            chain_hook_ignoring::<Expected>()
883        });
884    }
885
886    #[test]
887    fn no_panic() {
888        let ctx = Context::<Expected>::new();
889        let h = ctx.spawn(|| {
890            42
891        });
892        let outcome = h.join().unwrap();
893        assert_eq!(outcome, Outcome::NoPanic(42));
894    }
895
896    #[test]
897    fn context_default() {
898        let ctx = Context::<Expected>::default();
899        // Also use spawn_quiet and exercise the normal return path there
900        let h = ctx.spawn_quiet(|| {
901            42
902        });
903        let outcome = h.join().unwrap();
904        assert_eq!(outcome, Outcome::NoPanic(42));
905    }
906
907    #[test]
908    fn expected_panic() {
909        silence_expected_panics();
910        let ctx = Context::<Expected>::new();
911        let h = ctx.spawn(|| {
912            panic!(Expected(42));
913        });
914        let outcome = h.join().unwrap();
915        match outcome {
916            Outcome::Panicked(Expected(n)) => assert_eq!(n, 42),
917            _ => panic!("unexpected Outcome value returned from join()")
918        }
919    }
920
921    #[test]
922    fn int_literal_gotcha() {
923        let h = Context::<u32>::new().spawn_quiet(|| {
924            panic!(42);
925        });
926        // This wouldn't work:
927        //     let outcome = h.join().unwrap();
928        //     assert_eq!(outcome, Outcome::Panicked(42));
929        let res = h.join();
930        assert!(res.is_err());
931    }
932
933    #[test]
934    fn from_thread_builder() {
935        silence_expected_panics();
936        const THREAD_NAME: &str = "a panicky thread";
937        let thread_builder = thread::Builder::new()
938                                .name(THREAD_NAME.into());
939        let ctx = Context::<Expected>::from(thread_builder);
940        let h = ctx.spawn(|| {
941            let thread = thread::current();
942            let name = thread.name();
943            assert!(name.is_some());
944            assert_eq!(name.unwrap(), THREAD_NAME);
945            42
946        });
947        h.join().unwrap_or_propagate();
948    }
949
950    #[test]
951    fn checked_join_handle_inherent_as_ref() {
952        const THREAD_NAME: &str = "a non-panicky thread";
953        let thread_builder = thread::Builder::new()
954                                .name(THREAD_NAME.into());
955        let ctx = Context::<Expected>::from(thread_builder);
956        let h = ctx.spawn(|| {});
957        {
958            let h = h.as_thread_join_handle();
959            let name = h.thread().name().unwrap();
960            assert_eq!(name, THREAD_NAME);
961        }
962        h.join().unwrap_or_propagate();
963    }
964
965    #[test]
966    fn checked_join_handle_trait_as_ref() {
967        const THREAD_NAME: &str = "a non-panicky thread";
968        let thread_builder = thread::Builder::new()
969                                .name(THREAD_NAME.into());
970        let ctx = Context::<Expected>::from(thread_builder);
971        let h = ctx.spawn(|| {});
972        {
973            let h: &thread::JoinHandle<()> = h.as_ref();
974            let name = h.thread().name().unwrap();
975            assert_eq!(name, THREAD_NAME);
976        }
977        h.join().unwrap_or_propagate();
978    }
979
980    #[test]
981    fn checked_join_handle_inherent_into() {
982        silence_expected_panics();
983        let ctx = Context::<Expected>::new();
984        let h = ctx.spawn(|| {
985            panic!(Expected(42));
986        });
987        let h = h.into_thread_join_handle();
988        let res = h.join();
989        assert!(res.is_err());
990    }
991
992    #[test]
993    fn checked_join_handle_trait_into() {
994        silence_expected_panics();
995        let ctx = Context::<Expected>::new();
996        #[allow(unreachable_code)]
997        let h = ctx.spawn(|| {
998            panic!(Expected(42));
999            ()
1000        });
1001        let h: thread::JoinHandle<()> = h.into();
1002        let res = h.join();
1003        assert!(res.is_err());
1004    }
1005
1006    #[test]
1007    fn debug_impls_omit_phantom() {
1008        let ctx = Context::<Expected>::new();
1009        let repr = format!("{:?}", ctx);
1010        assert!(repr.starts_with("Context"));
1011        assert!(!repr.contains("phantom"));
1012        assert!(!repr.contains("PhantomData"));
1013        let h = ctx.spawn(|| { });
1014        let repr = format!("{:?}", h);
1015        assert_eq!(repr, "CheckedJoinHandle { .. }");
1016        h.join().unwrap();
1017    }
1018}