assert_fs/fixture/
tools.rs

1//! Initialize the filesystem to use as test fixtures.
2
3use std::fs;
4use std::io::Write;
5use std::path;
6
7use super::errors::FixtureError;
8use super::errors::FixtureKind;
9use super::errors::ResultChainExt;
10use super::ChildPath;
11use super::NamedTempFile;
12use super::TempDir;
13
14/// Create empty directories at [`ChildPath`].
15///
16pub trait PathCreateDir {
17    /// Create an empty directory at [`ChildPath`].
18    ///
19    /// # Examples
20    ///
21    /// ```rust
22    /// use assert_fs::prelude::*;
23    ///
24    /// let temp = assert_fs::TempDir::new().unwrap();
25    /// temp.child("subdir").create_dir_all().unwrap();
26    /// temp.close().unwrap();
27    /// ```
28    ///
29    fn create_dir_all(&self) -> Result<(), FixtureError>;
30}
31
32impl PathCreateDir for ChildPath {
33    fn create_dir_all(&self) -> Result<(), FixtureError> {
34        create_dir_all(self.path())
35    }
36}
37
38/// Create empty files at [`ChildPath`].
39///
40pub trait FileTouch {
41    /// Create an empty file at [`ChildPath`].
42    ///
43    /// # Examples
44    ///
45    /// ```rust
46    /// use assert_fs::prelude::*;
47    ///
48    /// let temp = assert_fs::TempDir::new().unwrap();
49    /// temp.child("foo.txt").touch().unwrap();
50    /// temp.close().unwrap();
51    /// ```
52    ///
53    fn touch(&self) -> Result<(), FixtureError>;
54}
55
56impl FileTouch for ChildPath {
57    fn touch(&self) -> Result<(), FixtureError> {
58        touch(self.path())
59    }
60}
61
62impl FileTouch for NamedTempFile {
63    fn touch(&self) -> Result<(), FixtureError> {
64        touch(self.path())
65    }
66}
67
68/// Write a binary file at [`ChildPath`].
69///
70pub trait FileWriteBin {
71    /// Write a binary file at [`ChildPath`].
72    ///
73    /// # Examples
74    ///
75    /// ```rust
76    /// use assert_fs::prelude::*;
77    ///
78    /// let temp = assert_fs::TempDir::new().unwrap();
79    /// temp
80    ///     .child("foo.txt")
81    ///     .write_binary(b"To be or not to be...")
82    ///     .unwrap();
83    /// temp.close().unwrap();
84    /// ```
85    ///
86    fn write_binary(&self, data: &[u8]) -> Result<(), FixtureError>;
87}
88
89impl FileWriteBin for ChildPath {
90    fn write_binary(&self, data: &[u8]) -> Result<(), FixtureError> {
91        write_binary(self.path(), data)
92    }
93}
94
95impl FileWriteBin for NamedTempFile {
96    fn write_binary(&self, data: &[u8]) -> Result<(), FixtureError> {
97        write_binary(self.path(), data)
98    }
99}
100
101/// Write a text file at [`ChildPath`].
102///
103pub trait FileWriteStr {
104    /// Write a text file at [`ChildPath`].
105    ///
106    /// # Examples
107    ///
108    /// ```rust
109    /// use assert_fs::prelude::*;
110    ///
111    /// let temp = assert_fs::TempDir::new().unwrap();
112    /// temp
113    ///    .child("foo.txt")
114    ///    .write_str("To be or not to be...")
115    ///    .unwrap();
116    /// temp.close().unwrap();
117    /// ```
118    ///
119    fn write_str(&self, data: &str) -> Result<(), FixtureError>;
120}
121
122impl FileWriteStr for ChildPath {
123    fn write_str(&self, data: &str) -> Result<(), FixtureError> {
124        write_str(self.path(), data)
125    }
126}
127
128impl FileWriteStr for NamedTempFile {
129    fn write_str(&self, data: &str) -> Result<(), FixtureError> {
130        write_str(self.path(), data)
131    }
132}
133
134/// Write (copy) a file to [`ChildPath`].
135///
136pub trait FileWriteFile {
137    /// Write (copy) a file to [`ChildPath`].
138    ///
139    /// # Examples
140    ///
141    /// ```rust
142    /// use std::path::Path;
143    /// use assert_fs::prelude::*;
144    ///
145    /// let temp = assert_fs::TempDir::new().unwrap();
146    /// temp
147    ///    .child("foo.txt")
148    ///    .write_file(Path::new("Cargo.toml"))
149    ///    .unwrap();
150    /// temp.close().unwrap();
151    /// ```
152    ///
153    fn write_file(&self, data: &path::Path) -> Result<(), FixtureError>;
154}
155
156impl FileWriteFile for ChildPath {
157    fn write_file(&self, data: &path::Path) -> Result<(), FixtureError> {
158        write_file(self.path(), data)
159    }
160}
161
162impl FileWriteFile for NamedTempFile {
163    fn write_file(&self, data: &path::Path) -> Result<(), FixtureError> {
164        write_file(self.path(), data)
165    }
166}
167
168/// Copy files into [`TempDir`].
169///
170pub trait PathCopy {
171    /// Copy files and directories into the current path from the `source` according to the glob
172    /// `patterns`.
173    ///
174    /// # Examples
175    ///
176    /// ```rust
177    /// use assert_fs::prelude::*;
178    ///
179    /// let temp = assert_fs::TempDir::new().unwrap();
180    /// temp.copy_from(".", &["*.rs"]).unwrap();
181    /// temp.close().unwrap();
182    /// ```
183    fn copy_from<P, S>(&self, source: P, patterns: &[S]) -> Result<(), FixtureError>
184    where
185        P: AsRef<path::Path>,
186        S: AsRef<str>;
187}
188
189impl PathCopy for TempDir {
190    fn copy_from<P, S>(&self, source: P, patterns: &[S]) -> Result<(), FixtureError>
191    where
192        P: AsRef<path::Path>,
193        S: AsRef<str>,
194    {
195        copy_files(self.path(), source.as_ref(), patterns)
196    }
197}
198
199impl PathCopy for ChildPath {
200    fn copy_from<P, S>(&self, source: P, patterns: &[S]) -> Result<(), FixtureError>
201    where
202        P: AsRef<path::Path>,
203        S: AsRef<str>,
204    {
205        copy_files(self.path(), source.as_ref(), patterns)
206    }
207}
208
209/// Create a symlink to the target
210///
211pub trait SymlinkToFile {
212    /// Create a symlink to the target
213    ///
214    /// # Examples
215    ///
216    /// ```rust
217    /// use assert_fs::prelude::*;
218    ///
219    /// let temp = assert_fs::TempDir::new().unwrap();
220    /// let real_file = temp.child("real_file");
221    /// real_file.touch().unwrap();
222    ///
223    /// temp.child("link_file").symlink_to_file(real_file.path()).unwrap();
224    ///
225    /// temp.close().unwrap();
226    /// ```
227    fn symlink_to_file<P>(&self, target: P) -> Result<(), FixtureError>
228    where
229        P: AsRef<path::Path>;
230}
231
232impl SymlinkToFile for ChildPath {
233    fn symlink_to_file<P>(&self, target: P) -> Result<(), FixtureError>
234    where
235        P: AsRef<path::Path>,
236    {
237        symlink_to_file(self.path(), target.as_ref())
238    }
239}
240
241impl SymlinkToFile for NamedTempFile {
242    fn symlink_to_file<P>(&self, target: P) -> Result<(), FixtureError>
243    where
244        P: AsRef<path::Path>,
245    {
246        symlink_to_file(self.path(), target.as_ref())
247    }
248}
249
250/// Create a symlink to the target
251///
252pub trait SymlinkToDir {
253    /// Create a symlink to the target
254    ///
255    /// # Examples
256    ///
257    /// ```rust
258    /// use assert_fs::prelude::*;
259    ///
260    /// let temp = assert_fs::TempDir::new().unwrap();
261    /// let real_dir = temp.child("real_dir");
262    /// real_dir.create_dir_all().unwrap();
263    ///
264    /// temp.child("link_dir").symlink_to_dir(real_dir.path()).unwrap();
265    ///
266    /// temp.close().unwrap();
267    /// ```
268    fn symlink_to_dir<P>(&self, target: P) -> Result<(), FixtureError>
269    where
270        P: AsRef<path::Path>;
271}
272
273impl SymlinkToDir for ChildPath {
274    fn symlink_to_dir<P>(&self, target: P) -> Result<(), FixtureError>
275    where
276        P: AsRef<path::Path>,
277    {
278        symlink_to_dir(self.path(), target.as_ref())
279    }
280}
281
282impl SymlinkToDir for TempDir {
283    fn symlink_to_dir<P>(&self, target: P) -> Result<(), FixtureError>
284    where
285        P: AsRef<path::Path>,
286    {
287        symlink_to_dir(self.path(), target.as_ref())
288    }
289}
290
291fn ensure_parent_dir(path: &path::Path) -> Result<(), FixtureError> {
292    if let Some(parent) = path.parent() {
293        fs::create_dir_all(parent).chain(FixtureError::new(FixtureKind::CreateDir))?;
294    }
295    Ok(())
296}
297
298fn create_dir_all(path: &path::Path) -> Result<(), FixtureError> {
299    fs::create_dir_all(path).chain(FixtureError::new(FixtureKind::CreateDir))?;
300    Ok(())
301}
302
303fn touch(path: &path::Path) -> Result<(), FixtureError> {
304    ensure_parent_dir(path)?;
305    fs::File::create(path).chain(FixtureError::new(FixtureKind::WriteFile))?;
306    Ok(())
307}
308
309fn write_binary(path: &path::Path, data: &[u8]) -> Result<(), FixtureError> {
310    ensure_parent_dir(path)?;
311    let mut file = fs::File::create(path).chain(FixtureError::new(FixtureKind::WriteFile))?;
312    file.write_all(data)
313        .chain(FixtureError::new(FixtureKind::WriteFile))?;
314    Ok(())
315}
316
317fn write_str(path: &path::Path, data: &str) -> Result<(), FixtureError> {
318    ensure_parent_dir(path)?;
319    write_binary(path, data.as_bytes()).chain(FixtureError::new(FixtureKind::WriteFile))
320}
321
322fn write_file(path: &path::Path, data: &path::Path) -> Result<(), FixtureError> {
323    ensure_parent_dir(path)?;
324    fs::copy(data, path).chain(FixtureError::new(FixtureKind::CopyFile))?;
325    Ok(())
326}
327
328fn copy_files<S>(
329    target: &path::Path,
330    source: &path::Path,
331    patterns: &[S],
332) -> Result<(), FixtureError>
333where
334    S: AsRef<str>,
335{
336    // `walkdir`, on Windows, seems to convert "." into "" which then fails.
337    let source = source
338        .canonicalize()
339        .chain(FixtureError::new(FixtureKind::Walk))?;
340    for entry in globwalk::GlobWalkerBuilder::from_patterns(&source, patterns)
341        .follow_links(true)
342        .build()
343        .chain(FixtureError::new(FixtureKind::Walk))?
344    {
345        let entry = entry.chain(FixtureError::new(FixtureKind::Walk))?;
346        let rel = entry
347            .path()
348            .strip_prefix(&source)
349            .expect("entries to be under `source`");
350        let target_path = target.join(rel);
351        if entry.file_type().is_dir() {
352            fs::create_dir_all(target_path).chain(FixtureError::new(FixtureKind::CreateDir))?;
353        } else if entry.file_type().is_file() {
354            fs::create_dir_all(target_path.parent().expect("at least `target` exists"))
355                .chain(FixtureError::new(FixtureKind::CreateDir))?;
356            fs::copy(entry.path(), target_path).chain(FixtureError::new(FixtureKind::CopyFile))?;
357        }
358    }
359    Ok(())
360}
361
362#[cfg(windows)]
363fn symlink_to_file(link: &path::Path, target: &path::Path) -> Result<(), FixtureError> {
364    std::os::windows::fs::symlink_file(target, link)
365        .chain(FixtureError::new(FixtureKind::Symlink))?;
366    Ok(())
367}
368
369#[cfg(windows)]
370fn symlink_to_dir(link: &path::Path, target: &path::Path) -> Result<(), FixtureError> {
371    std::os::windows::fs::symlink_dir(target, link)
372        .chain(FixtureError::new(FixtureKind::Symlink))?;
373    Ok(())
374}
375
376#[cfg(not(windows))]
377fn symlink_to_file(link: &path::Path, target: &path::Path) -> Result<(), FixtureError> {
378    std::os::unix::fs::symlink(target, link).chain(FixtureError::new(FixtureKind::Symlink))?;
379    Ok(())
380}
381
382#[cfg(not(windows))]
383fn symlink_to_dir(link: &path::Path, target: &path::Path) -> Result<(), FixtureError> {
384    std::os::unix::fs::symlink(target, link).chain(FixtureError::new(FixtureKind::Symlink))?;
385    Ok(())
386}