Skip to main content

nu_test_support/playground/
play.rs

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