nu_test_support/playground/
play.rs

1use super::Director;
2use crate::fs::{self, Stub};
3use nu_glob::{Uninterruptible, glob};
4#[cfg(not(target_arch = "wasm32"))]
5use nu_path::Path;
6use nu_path::{AbsolutePath, AbsolutePathBuf};
7use std::str;
8use tempfile::{TempDir, tempdir};
9
10#[derive(Default, Clone, Debug)]
11pub struct EnvironmentVariable {
12    pub name: String,
13    pub value: String,
14}
15
16impl EnvironmentVariable {
17    fn new(name: &str, value: &str) -> Self {
18        Self {
19            name: name.to_string(),
20            value: value.to_string(),
21        }
22    }
23}
24
25pub struct Playground<'a> {
26    _root: TempDir,
27    tests: String,
28    cwd: AbsolutePathBuf,
29    config: Option<AbsolutePathBuf>,
30    environment_vars: Vec<EnvironmentVariable>,
31    dirs: &'a Dirs,
32}
33
34#[derive(Clone)]
35pub struct Dirs {
36    pub root: AbsolutePathBuf,
37    pub test: AbsolutePathBuf,
38    pub fixtures: AbsolutePathBuf,
39}
40
41impl Dirs {
42    pub fn formats(&self) -> AbsolutePathBuf {
43        self.fixtures.join("formats")
44    }
45
46    pub fn root(&self) -> &AbsolutePath {
47        &self.root
48    }
49
50    pub fn test(&self) -> &AbsolutePath {
51        &self.test
52    }
53}
54
55impl Playground<'_> {
56    pub fn root(&self) -> &AbsolutePath {
57        &self.dirs.root
58    }
59
60    pub fn cwd(&self) -> &AbsolutePath {
61        &self.cwd
62    }
63
64    pub fn back_to_playground(&mut self) -> &mut Self {
65        self.cwd = self.root().join(&self.tests);
66        self
67    }
68
69    pub fn play(&mut self) -> &mut Self {
70        self
71    }
72
73    pub fn setup(topic: &str, block: impl FnOnce(Dirs, &mut Playground)) {
74        let temp = tempdir().expect("Could not create a tempdir");
75
76        let root = AbsolutePathBuf::try_from(temp.path())
77            .expect("Tempdir is not an absolute path")
78            .canonicalize()
79            .expect("Could not canonicalize tempdir");
80
81        let test = root.join(topic);
82        if test.exists() {
83            std::fs::remove_dir_all(&test).expect("Could not remove directory");
84        }
85        std::fs::create_dir(&test).expect("Could not create directory");
86        let test = test
87            .canonicalize()
88            .expect("Could not canonicalize test path");
89
90        let fixtures = fs::fixtures()
91            .canonicalize()
92            .expect("Could not canonicalize fixtures path");
93
94        let dirs = Dirs {
95            root: root.into(),
96            test: test.as_path().into(),
97            fixtures: fixtures.into(),
98        };
99
100        let mut playground = Playground {
101            _root: temp,
102            tests: topic.to_string(),
103            cwd: test.into(),
104            config: None,
105            environment_vars: Vec::default(),
106            dirs: &dirs,
107        };
108
109        block(dirs.clone(), &mut playground);
110    }
111
112    pub fn with_config(&mut self, source_file: AbsolutePathBuf) -> &mut Self {
113        self.config = Some(source_file);
114        self
115    }
116
117    pub fn with_env(&mut self, name: &str, value: &str) -> &mut Self {
118        self.environment_vars
119            .push(EnvironmentVariable::new(name, value));
120        self
121    }
122
123    pub fn get_config(&self) -> Option<&str> {
124        self.config
125            .as_ref()
126            .map(|cfg| cfg.to_str().expect("could not convert path."))
127    }
128
129    pub fn build(&mut self) -> Director {
130        Director {
131            cwd: Some(self.dirs.test().into()),
132            config: self.config.clone().map(|cfg| cfg.into()),
133            environment_vars: self.environment_vars.clone(),
134            ..Default::default()
135        }
136    }
137
138    pub fn cococo(&mut self, arg: &str) -> Director {
139        self.build().cococo(arg)
140    }
141
142    pub fn pipeline(&mut self, commands: &str) -> Director {
143        self.build().pipeline(commands)
144    }
145
146    pub fn mkdir(&mut self, directory: &str) -> &mut Self {
147        self.cwd.push(directory);
148        std::fs::create_dir_all(&self.cwd).expect("can not create directory");
149        self.back_to_playground();
150        self
151    }
152
153    #[cfg(not(target_arch = "wasm32"))]
154    pub fn symlink(&mut self, from: impl AsRef<Path>, to: impl AsRef<Path>) -> &mut Self {
155        let from = self.cwd.join(from);
156        let to = self.cwd.join(to);
157
158        let create_symlink = {
159            #[cfg(unix)]
160            {
161                std::os::unix::fs::symlink
162            }
163
164            #[cfg(windows)]
165            {
166                if from.is_file() {
167                    std::os::windows::fs::symlink_file
168                } else if from.is_dir() {
169                    std::os::windows::fs::symlink_dir
170                } else {
171                    panic!("symlink from must be a file or dir")
172                }
173            }
174        };
175
176        create_symlink(from, to).expect("can not create symlink");
177        self.back_to_playground();
178        self
179    }
180
181    pub fn with_files(&mut self, files: &[Stub]) -> &mut Self {
182        let endl = fs::line_ending();
183
184        files
185            .iter()
186            .map(|f| {
187                let mut permission_set = false;
188                let mut write_able = true;
189                let (file_name, contents) = match *f {
190                    Stub::EmptyFile(name) => (name, String::new()),
191                    Stub::FileWithContent(name, content) => (name, content.to_string()),
192                    Stub::FileWithContentToBeTrimmed(name, content) => (
193                        name,
194                        content
195                            .lines()
196                            .skip(1)
197                            .map(|line| line.trim())
198                            .collect::<Vec<&str>>()
199                            .join(&endl),
200                    ),
201                    Stub::FileWithPermission(name, is_write_able) => {
202                        permission_set = true;
203                        write_able = is_write_able;
204                        (name, "check permission".to_string())
205                    }
206                };
207
208                let path = self.cwd.join(file_name);
209
210                std::fs::write(&path, contents.as_bytes()).expect("can not create file");
211                if permission_set {
212                    let err_perm = "can not set permission";
213                    let mut perm = std::fs::metadata(path.clone())
214                        .expect(err_perm)
215                        .permissions();
216                    perm.set_readonly(!write_able);
217                    std::fs::set_permissions(path, perm).expect(err_perm);
218                }
219            })
220            .for_each(drop);
221        self.back_to_playground();
222        self
223    }
224
225    pub fn within(&mut self, directory: &str) -> &mut Self {
226        self.cwd.push(directory);
227        if !(self.cwd.exists() && self.cwd.is_dir()) {
228            std::fs::create_dir(&self.cwd).expect("can not create directory");
229        }
230        self
231    }
232
233    pub fn glob_vec(pattern: &str) -> Vec<std::path::PathBuf> {
234        let glob = glob(pattern, Uninterruptible);
235
236        glob.expect("invalid pattern")
237            .map(|path| {
238                if let Ok(path) = path {
239                    path
240                } else {
241                    unreachable!()
242                }
243            })
244            .collect()
245    }
246}