libcontainer/
test_utils.rs1use nix::sys::wait;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Serialize, Deserialize)]
8struct ErrorEnclosure {
9 source: Option<Box<ErrorEnclosure>>,
10 description: String,
11}
12
13impl ErrorEnclosure {
14 pub fn new<T>(e: &T) -> ErrorEnclosure
15 where
16 T: ?Sized + std::error::Error,
17 {
18 ErrorEnclosure {
19 description: e.to_string(),
20 source: e.source().map(|s| Box::new(ErrorEnclosure::new(s))),
21 }
22 }
23}
24
25impl std::fmt::Display for ErrorEnclosure {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 write!(f, "{}", self.description)
28 }
29}
30
31impl std::error::Error for ErrorEnclosure {
32 fn source(&self) -> Option<&(dyn 'static + std::error::Error)> {
33 self.source
34 .as_ref()
35 .map(|source| &**source as &(dyn 'static + std::error::Error))
36 }
37
38 fn description(&self) -> &str {
39 &self.description
40 }
41}
42
43type ClosureResult = Result<(), ErrorEnclosure>;
44
45#[derive(Debug, thiserror::Error)]
46pub enum TestError {
47 #[error("failed to create channel")]
48 Channel(#[from] crate::channel::ChannelError),
49 #[error("failed to fork")]
50 Fork(#[source] nix::Error),
51 #[error("failed to wait for child process")]
52 Wait(#[source] nix::Error),
53 #[error("failed to run function in child process")]
54 Execution(#[source] Box<dyn std::error::Error + Send + Sync>),
55 #[error("the closure caused the child process to panic")]
56 Panic,
57}
58
59#[derive(Debug, thiserror::Error)]
60pub enum TestCallbackError {
61 #[error("{0}")]
62 Custom(String),
63 #[error("{0:?}")]
64 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
65}
66
67impl From<&str> for TestCallbackError {
68 fn from(s: &str) -> Self {
69 TestCallbackError::Custom(s.to_string())
70 }
71}
72
73impl From<String> for TestCallbackError {
74 fn from(s: String) -> Self {
75 TestCallbackError::Custom(s)
76 }
77}
78
79pub fn test_in_child_process<F>(cb: F) -> Result<(), TestError>
80where
81 F: FnOnce() -> Result<(), TestCallbackError> + std::panic::UnwindSafe,
82{
83 let (mut sender, mut receiver) = crate::channel::channel::<ClosureResult>()?;
84 match unsafe { nix::unistd::fork().map_err(TestError::Fork)? } {
85 nix::unistd::ForkResult::Parent { child } => {
86 sender.close().map_err(TestError::Channel)?;
88 let res = receiver.recv().map_err(TestError::Channel)?;
89 wait::waitpid(child, None).map_err(TestError::Wait)?;
90 res.map_err(|err| TestError::Execution(Box::new(err)))?;
91 }
92 nix::unistd::ForkResult::Child => {
93 receiver.close().map_err(TestError::Channel)?;
95 let test_result = match std::panic::catch_unwind(cb) {
96 Ok(ret) => ret.map_err(|err| ErrorEnclosure::new(&err)),
97 Err(_) => Err(ErrorEnclosure::new(&TestError::Panic)),
98 };
99
100 let _ = sender.send(test_result);
103 std::process::exit(0);
104 }
105 };
106
107 Ok(())
108}
109
110#[cfg(test)]
111mod tests {
112 use core::panic;
113
114 use anyhow::{Result, bail};
115
116 use super::*;
117
118 #[test]
119 fn test_child_process() -> Result<()> {
120 if test_in_child_process(|| Err(TestCallbackError::Custom("test error".to_string())))
121 .is_ok()
122 {
123 bail!("expecting the child process to return an error")
124 }
125
126 Ok(())
127 }
128
129 #[test]
130 fn test_panic_child_process() -> Result<()> {
131 let ret = test_in_child_process(|| {
132 panic!("test panic");
133 });
134 if ret.is_ok() {
135 bail!("expecting the child process to panic")
136 }
137
138 Ok(())
139 }
140}