Skip to content

Commit e59dfd3

Browse files
authored
Merge pull request #41 from epage/ext
Improve fixture support
2 parents cbd5ef7 + 759e0d4 commit e59dfd3

File tree

7 files changed

+556
-77
lines changed

7 files changed

+556
-77
lines changed

src/fixture/child.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use std::path;
2+
3+
/// Access paths within [`TempDir`] for testing.
4+
///
5+
/// See [`ChildPath`] trait implementations.
6+
///
7+
/// ```rust
8+
/// use assert_fs::prelude::*;
9+
///
10+
/// let temp = assert_fs::TempDir::new().unwrap();
11+
/// let input_file = temp.child("foo.txt");
12+
/// input_file.touch().unwrap();
13+
/// temp.close().unwrap();
14+
/// ```
15+
///
16+
/// [`TempDir`]: struct.TempDir.html
17+
/// [`ChildPath`]: struct.ChildPath.html
18+
pub trait PathChild {
19+
/// Access a path within the temp directory.
20+
///
21+
/// # Examples
22+
///
23+
/// ```rust
24+
/// use assert_fs::prelude::*;
25+
///
26+
/// let temp = assert_fs::TempDir::new().unwrap();
27+
/// println!("{}", temp.path().display());
28+
/// println!("{}", temp.child("foo/bar.txt").path().display());
29+
/// temp.close().unwrap();
30+
/// ```
31+
fn child<P>(&self, path: P) -> ChildPath
32+
where
33+
P: AsRef<path::Path>;
34+
}
35+
36+
impl PathChild for super::TempDir {
37+
fn child<P>(&self, path: P) -> ChildPath
38+
where
39+
P: AsRef<path::Path>,
40+
{
41+
ChildPath::new(self.path().join(path.as_ref()))
42+
}
43+
}
44+
45+
/// A path within a [`TempDir`]
46+
///
47+
/// See Trait Implementations.
48+
///
49+
/// # Examples
50+
///
51+
/// ```rust
52+
/// use assert_fs::prelude::*;
53+
///
54+
/// let temp = assert_fs::TempDir::new().unwrap();
55+
///
56+
/// let input_file = temp.child("foo.txt");
57+
/// input_file.touch().unwrap();
58+
///
59+
/// temp.child("bar.txt").touch().unwrap();
60+
///
61+
/// temp.close().unwrap();
62+
/// ```
63+
///
64+
/// [`TempDir`]: struct.TempDir.html
65+
pub struct ChildPath {
66+
path: path::PathBuf,
67+
}
68+
69+
impl ChildPath {
70+
/// Wrap a path for use with extension traits.
71+
///
72+
/// See trait implementations or [`PathChild`] for more details.
73+
///
74+
/// [`PathChild`]: trait.PathChild.html
75+
pub fn new<P>(path: P) -> Self
76+
where
77+
P: Into<path::PathBuf>,
78+
{
79+
Self { path: path.into() }
80+
}
81+
82+
/// Access the path.
83+
pub fn path(&self) -> &path::Path {
84+
&self.path
85+
}
86+
}

src/fixture/dir.rs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
use std::path;
2+
3+
use tempfile;
4+
5+
use super::errors::*;
6+
7+
/// A directory in the filesystem that is automatically deleted when
8+
/// it goes out of scope.
9+
///
10+
/// The [`TempDir`] type creates a directory on the file system that
11+
/// is deleted once it goes out of scope. At construction, the
12+
/// `TempDir` creates a new directory with a randomly generated name.
13+
///
14+
/// The constructor, [`TempDir::new()`], creates directories in
15+
/// the location returned by [`std::env::temp_dir()`].
16+
///
17+
/// After creating a `TempDir`, work with the file system by doing
18+
/// standard [`std::fs`] file system operations on its [`Path`],
19+
/// which can be retrieved with [`TempDir::path()`]. Once the `TempDir`
20+
/// value is dropped, the directory at the path will be deleted, along
21+
/// with any files and directories it contains. It is your responsibility
22+
/// to ensure that no further file system operations are attempted
23+
/// inside the temporary directory once it has been deleted.
24+
///
25+
/// # Resource Leaking
26+
///
27+
/// Various platform-specific conditions may cause `TempDir` to fail
28+
/// to delete the underlying directory. It's important to ensure that
29+
/// handles (like [`File`] and [`ReadDir`]) to files inside the
30+
/// directory are dropped before the `TempDir` goes out of scope. The
31+
/// `TempDir` destructor will silently ignore any errors in deleting
32+
/// the directory; to instead handle errors call [`TempDir::close()`].
33+
///
34+
/// Note that if the program exits before the `TempDir` destructor is
35+
/// run, such as via [`std::process::exit()`], by segfaulting, or by
36+
/// receiving a signal like `SIGINT`, then the temporary directory
37+
/// will not be deleted.
38+
///
39+
/// # Examples
40+
///
41+
/// Create a temporary directory with a generated name:
42+
///
43+
/// ```
44+
/// use assert_fs::fixture::TempDir;
45+
///
46+
/// let tmp_dir = TempDir::new().unwrap();
47+
///
48+
/// // Ensure deletion happens.
49+
/// tmp_dir.close().unwrap();
50+
/// ```
51+
///
52+
/// [`File`]: http://doc.rust-lang.org/std/fs/struct.File.html
53+
/// [`Path`]: http://doc.rust-lang.org/std/path/struct.Path.html
54+
/// [`ReadDir`]: http://doc.rust-lang.org/std/fs/struct.ReadDir.html
55+
/// [`TempDir::close()`]: struct.TempDir.html#method.close
56+
/// [`TempDir::new()`]: struct.TempDir.html#method.new
57+
/// [`TempDir::path()`]: struct.TempDir.html#method.path
58+
/// [`TempDir`]: struct.TempDir.html
59+
/// [`std::env::temp_dir()`]: https://doc.rust-lang.org/std/env/fn.temp_dir.html
60+
/// [`std::fs`]: http://doc.rust-lang.org/std/fs/index.html
61+
/// [`std::process::exit()`]: http://doc.rust-lang.org/std/process/fn.exit.html
62+
pub struct TempDir {
63+
temp: Inner,
64+
}
65+
66+
enum Inner {
67+
Temp(tempfile::TempDir),
68+
Persisted(path::PathBuf),
69+
}
70+
71+
impl TempDir {
72+
/// Attempts to make a temporary directory inside of `env::temp_dir()`.
73+
///
74+
/// The directory and everything inside it will be automatically deleted
75+
/// once the returned `TempDir` is destroyed.
76+
///
77+
/// # Errors
78+
///
79+
/// If the directory can not be created, `Err` is returned.
80+
///
81+
/// # Examples
82+
///
83+
/// ```
84+
/// use assert_fs::fixture::TempDir;
85+
///
86+
/// let tmp_dir = TempDir::new().unwrap();
87+
///
88+
/// // Ensure deletion happens.
89+
/// tmp_dir.close().unwrap();
90+
/// ```
91+
pub fn new() -> Result<Self, FixtureError> {
92+
let temp = tempfile::TempDir::new().chain(FixtureError::new(FixtureKind::CreateDir))?;
93+
let temp = Inner::Temp(temp);
94+
Ok(Self { temp })
95+
}
96+
97+
/// Conditionally persist the temporary directory for debug purposes.
98+
///
99+
/// # Examples
100+
///
101+
/// ```no_run
102+
/// use assert_fs::fixture::TempDir;
103+
///
104+
/// let tmp_dir = TempDir::new().unwrap().persist_if(true);
105+
///
106+
/// // Ensure deletion happens.
107+
/// tmp_dir.close().unwrap();
108+
/// ```
109+
pub fn persist_if(self, yes: bool) -> Self {
110+
if !yes {
111+
return self;
112+
}
113+
114+
let path = match self.temp {
115+
Inner::Temp(temp) => temp.into_path(),
116+
Inner::Persisted(path) => path,
117+
};
118+
let temp = Inner::Persisted(path);
119+
Self { temp }
120+
}
121+
122+
/// Accesses the [`Path`] to the temporary directory.
123+
///
124+
/// [`Path`]: http://doc.rust-lang.org/std/path/struct.Path.html
125+
///
126+
/// # Examples
127+
///
128+
/// ```
129+
/// use assert_fs::fixture::TempDir;
130+
///
131+
/// let tmp_dir = TempDir::new().unwrap();
132+
///
133+
/// println!("{}", tmp_dir.path().display());
134+
///
135+
/// // Ensure deletion happens.
136+
/// tmp_dir.close().unwrap();
137+
/// ```
138+
pub fn path(&self) -> &path::Path {
139+
match self.temp {
140+
Inner::Temp(ref temp) => temp.path(),
141+
Inner::Persisted(ref path) => path.as_path(),
142+
}
143+
}
144+
145+
/// Closes and removes the temporary directory, returing a `Result`.
146+
///
147+
/// Although `TempDir` removes the directory on drop, in the destructor
148+
/// any errors are ignored. To detect errors cleaning up the temporary
149+
/// directory, call `close` instead.
150+
///
151+
/// # Errors
152+
///
153+
/// This function may return a variety of [`std::io::Error`]s that result from deleting
154+
/// the files and directories contained with the temporary directory,
155+
/// as well as from deleting the temporary directory itself. These errors
156+
/// may be platform specific.
157+
///
158+
/// [`std::io::Error`]: http://doc.rust-lang.org/std/io/struct.Error.html
159+
///
160+
/// # Examples
161+
///
162+
/// ```
163+
/// use assert_fs::fixture::TempDir;
164+
///
165+
/// let tmp_dir = TempDir::new().unwrap();
166+
///
167+
/// // Ensure deletion happens.
168+
/// tmp_dir.close().unwrap();
169+
/// ```
170+
pub fn close(self) -> Result<(), FixtureError> {
171+
match self.temp {
172+
Inner::Temp(temp) => temp
173+
.close()
174+
.chain(FixtureError::new(FixtureKind::Cleanup))?,
175+
Inner::Persisted(_) => (),
176+
}
177+
Ok(())
178+
}
179+
}

src/errors.rs renamed to src/fixture/errors.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,17 @@ pub enum FixtureKind {
4949
CopyFile,
5050
/// Failed when creating a directory.
5151
CreateDir,
52+
/// Failed to cleanup fixture.
53+
Cleanup,
5254
}
5355

5456
impl fmt::Display for FixtureKind {
5557
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5658
match *self {
57-
FixtureKind::Walk => write!(f, "Failed when walking the source tree"),
58-
FixtureKind::CopyFile => write!(f, "Failed when copying a file"),
59-
FixtureKind::CreateDir => write!(f, "Failed when creating a directory"),
59+
FixtureKind::Walk => write!(f, "Failed when walking the source tree,"),
60+
FixtureKind::CopyFile => write!(f, "Failed when copying a file."),
61+
FixtureKind::CreateDir => write!(f, "Failed when creating a directory."),
62+
FixtureKind::Cleanup => write!(f, "Failed to cleanup fixture."),
6063
}
6164
}
6265
}

0 commit comments

Comments
 (0)