cli_integration_test/
lib.rs1use assert_cmd::Command;
2use fs_extra::dir::create_all;
3use fs_extra::file::read_to_string;
4use fs_extra::file::write_all;
5use std::collections::HashMap;
6use std::fmt::{Display, Formatter};
7use std::fs::{set_permissions, OpenOptions, Permissions};
8use std::io;
9use std::io::Write as IoWrite;
10use std::os::unix::fs::PermissionsExt;
11use std::path::{Path, PathBuf};
12use tempdir::TempDir;
13use walkdir::WalkDir;
14
15pub struct IntegrationTestEnvironment {
16 tmp_dir: TempDir,
17 entries: HashMap<PathBuf, Option<String>>,
18 cfg_command_callback: Box<dyn Fn(PathBuf, Command) -> Command>,
19}
20
21impl IntegrationTestEnvironment {
22 pub fn new<L>(label: L) -> Self
23 where
24 L: AsRef<str>,
25 {
26 let label = label.as_ref().to_string();
27 let tmp_dir = TempDir::new(&label).expect("fail to create tmp directory");
28 Self {
29 tmp_dir,
30 entries: HashMap::new(),
31 cfg_command_callback: Box::new(|_, c| c),
32 }
33 }
34
35 pub fn set_cfg_command_callback(
36 &mut self,
37 callback: impl Fn(PathBuf, Command) -> Command + 'static,
38 ) {
39 self.cfg_command_callback = Box::new(callback);
40 }
41
42 pub fn add_file<P, C>(&mut self, path: P, content: C)
43 where
44 P: AsRef<Path>,
45 C: AsRef<str>,
46 {
47 self.entries.insert(
48 path.as_ref().to_path_buf(),
49 Some(content.as_ref().to_string()),
50 );
51 }
52
53 pub fn read_file<P>(&self, path: P) -> String
54 where
55 P: AsRef<Path>,
56 {
57 let path = self.tmp_dir.path().join(path.as_ref());
58 read_to_string(&path).expect(format!("fail to read file {:?}", path).as_str())
59 }
60
61 pub fn file_exists<P: AsRef<Path>>(&self, path: P) -> bool {
62 let path = self.tmp_dir.path().join(path.as_ref());
63 path.exists()
64 }
65
66 pub fn add_dir<P>(&mut self, path: P)
67 where
68 P: AsRef<Path>,
69 {
70 self.entries.insert(path.as_ref().to_path_buf(), None);
71 }
72
73 pub fn setup(&self) {
74 for (path, content) in self.entries.iter() {
75 let path = self.tmp_dir.path().join(path);
76 if let Some(content) = content {
77 if let Some(path) = path.parent() {
78 create_all(path, false)
79 .expect(format!("fail to create directory {:?}", path).as_str())
80 }
81 write_all(path, content).expect("fail to create file");
82 } else {
83 create_all(&path, false)
84 .expect(format!("fail to create directory {:?}", path).as_str())
85 }
86 }
87 }
88
89 pub fn set_exec_permission<P: AsRef<Path>>(&self, file: P) -> io::Result<()> {
90 let file = self.tmp_dir.path().join(file.as_ref());
91 let permissions = Permissions::from_mode(0o755);
92 set_permissions(file, permissions)?;
93 Ok(())
94 }
95
96 pub fn set_update_file_time<P: AsRef<Path>>(&self, file: P) -> io::Result<()> {
97 let content = self.read_file(file.as_ref());
98 let file = self.tmp_dir.path().join(file.as_ref());
99 let mut file = OpenOptions::new().write(true).truncate(true).open(file)?;
100 write!(file, "{}", content)?;
101 Ok(())
102 }
103
104 pub fn tree(&self) -> Vec<PathBuf> {
105 let mut tree: Vec<PathBuf> = WalkDir::new(self.tmp_dir.path())
106 .into_iter()
107 .filter_map(|dir_entry| {
108 if let Ok(dir_entry) = dir_entry {
109 if let Ok(dir_entry) = dir_entry.path().strip_prefix(self.tmp_dir.path()) {
110 return Some(dir_entry.to_path_buf());
111 }
112 }
113 None
114 })
115 .collect();
116 tree.sort();
117 tree
118 }
119
120 pub fn command<C>(&self, crate_name: C) -> io::Result<Command>
121 where
122 C: AsRef<str>,
123 {
124 let mut command = Command::cargo_bin(crate_name).unwrap();
125 command.current_dir(&self.tmp_dir.path());
126 let command = (self.cfg_command_callback)(self.path()?.clone().to_path_buf(), command);
127 Ok(command)
128 }
129
130 pub fn path(&self) -> io::Result<PathBuf> {
131 self.tmp_dir.path().canonicalize()
132 }
133}
134
135impl Display for IntegrationTestEnvironment {
136 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
137 for e in self.tree() {
138 writeln!(f, "{}", e.to_string_lossy())?;
139 }
140 Ok(())
141 }
142}
143
144#[cfg(test)]
145mod test {
146 use crate::IntegrationTestEnvironment;
147 use predicates::prelude::Predicate;
148 use predicates::str::contains;
149
150 #[test]
151 fn integration_test_environment() {
152 let mut e = IntegrationTestEnvironment::new("test");
153 e.add_file("file1", "test 1");
154 e.add_file("dir/file2", "test 2");
155 e.add_dir("emptry_dir");
156 e.setup();
157 e.set_exec_permission("dir/file2").unwrap();
158 let display = e.to_string();
159 assert!(contains("file1").eval(display.as_str()));
160 assert!(contains("dir/file2").eval(display.as_str()));
161 assert!(contains("emptry_dir").eval(display.as_str()));
162 assert!(contains("test 1").eval(e.read_file("file1").as_str()));
163 }
164}
165
166#[macro_export]
167macro_rules! println_output {
168 ($v:ident) => {
169 println!(
170 "{}",
171 String::from_utf8($v.stderr.clone()).expect("fail to read stderr")
172 );
173 println!("---------------------------");
174 println!(
175 "{}",
176 String::from_utf8($v.stdout.clone()).expect("fail to read stdout")
177 );
178 println!("---------------------------");
179 println!("{}", $v.status);
180 };
181}
182
183#[macro_export]
184macro_rules! println_result_output {
185 ($v:ident) => {
186 match $v {
187 Ok(output) => {
188 println_output!(output);
189 }
190 Err(outputError) => {
191 println!("output error !!");
192 println!("{}", outputError.to_string());
193 }
194 }
195 };
196}