1 #![allow(
2     // Clippy bug: https://github.com/rust-lang/rust-clippy/issues/7422
3     clippy::nonstandard_macro_braces,
4 )]
5 
6 mod drop;
7 
8 use crate::drop::{DetectDrop, Flag};
9 use anyhow::{Context, Error, Result};
10 use std::fmt::{self, Display};
11 use thiserror::Error;
12 
13 // https://github.com/dtolnay/anyhow/issues/18
14 #[test]
test_inference() -> Result<()>15 fn test_inference() -> Result<()> {
16     let x = "1";
17     let y: u32 = x.parse().context("...")?;
18     assert_eq!(y, 1);
19     Ok(())
20 }
21 
22 macro_rules! context_type {
23     ($name:ident) => {
24         #[derive(Debug)]
25         struct $name {
26             message: &'static str,
27             #[allow(dead_code)]
28             drop: DetectDrop,
29         }
30 
31         impl Display for $name {
32             fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33                 f.write_str(self.message)
34             }
35         }
36     };
37 }
38 
39 context_type!(HighLevel);
40 context_type!(MidLevel);
41 
42 #[derive(Error, Debug)]
43 #[error("{message}")]
44 struct LowLevel {
45     message: &'static str,
46     drop: DetectDrop,
47 }
48 
49 struct Dropped {
50     low: Flag,
51     mid: Flag,
52     high: Flag,
53 }
54 
55 impl Dropped {
none(&self) -> bool56     fn none(&self) -> bool {
57         !self.low.get() && !self.mid.get() && !self.high.get()
58     }
59 
all(&self) -> bool60     fn all(&self) -> bool {
61         self.low.get() && self.mid.get() && self.high.get()
62     }
63 }
64 
make_chain() -> (Error, Dropped)65 fn make_chain() -> (Error, Dropped) {
66     let dropped = Dropped {
67         low: Flag::new(),
68         mid: Flag::new(),
69         high: Flag::new(),
70     };
71 
72     let low = LowLevel {
73         message: "no such file or directory",
74         drop: DetectDrop::new(&dropped.low),
75     };
76 
77     // impl Context for Result<T, E>
78     let mid = Err::<(), LowLevel>(low)
79         .context(MidLevel {
80             message: "failed to load config",
81             drop: DetectDrop::new(&dropped.mid),
82         })
83         .unwrap_err();
84 
85     // impl Context for Result<T, Error>
86     let high = Err::<(), Error>(mid)
87         .context(HighLevel {
88             message: "failed to start server",
89             drop: DetectDrop::new(&dropped.high),
90         })
91         .unwrap_err();
92 
93     (high, dropped)
94 }
95 
96 #[test]
test_downcast_ref()97 fn test_downcast_ref() {
98     let (err, dropped) = make_chain();
99 
100     assert!(!err.is::<String>());
101     assert!(err.downcast_ref::<String>().is_none());
102 
103     assert!(err.is::<HighLevel>());
104     let high = err.downcast_ref::<HighLevel>().unwrap();
105     assert_eq!(high.to_string(), "failed to start server");
106 
107     assert!(err.is::<MidLevel>());
108     let mid = err.downcast_ref::<MidLevel>().unwrap();
109     assert_eq!(mid.to_string(), "failed to load config");
110 
111     assert!(err.is::<LowLevel>());
112     let low = err.downcast_ref::<LowLevel>().unwrap();
113     assert_eq!(low.to_string(), "no such file or directory");
114 
115     assert!(dropped.none());
116     drop(err);
117     assert!(dropped.all());
118 }
119 
120 #[test]
test_downcast_high()121 fn test_downcast_high() {
122     let (err, dropped) = make_chain();
123 
124     let err = err.downcast::<HighLevel>().unwrap();
125     assert!(!dropped.high.get());
126     assert!(dropped.low.get() && dropped.mid.get());
127 
128     drop(err);
129     assert!(dropped.all());
130 }
131 
132 #[test]
test_downcast_mid()133 fn test_downcast_mid() {
134     let (err, dropped) = make_chain();
135 
136     let err = err.downcast::<MidLevel>().unwrap();
137     assert!(!dropped.mid.get());
138     assert!(dropped.low.get() && dropped.high.get());
139 
140     drop(err);
141     assert!(dropped.all());
142 }
143 
144 #[test]
test_downcast_low()145 fn test_downcast_low() {
146     let (err, dropped) = make_chain();
147 
148     let err = err.downcast::<LowLevel>().unwrap();
149     assert!(!dropped.low.get());
150     assert!(dropped.mid.get() && dropped.high.get());
151 
152     drop(err);
153     assert!(dropped.all());
154 }
155 
156 #[test]
test_unsuccessful_downcast()157 fn test_unsuccessful_downcast() {
158     let (err, dropped) = make_chain();
159 
160     let err = err.downcast::<String>().unwrap_err();
161     assert!(dropped.none());
162 
163     drop(err);
164     assert!(dropped.all());
165 }
166 
167 #[test]
test_root_cause()168 fn test_root_cause() {
169     let (err, _) = make_chain();
170 
171     assert_eq!(err.root_cause().to_string(), "no such file or directory");
172 }
173