Skip to content

Commit e5209df

Browse files
committed
feat(fixture): NamedTempFile support
Unlike traditional named temporary files, this creates a temp dir and offers a `Path` to a non-existent file within it. This is useful for having more control over the file name, use with output of a program, etc. Fixes #33
1 parent 39b2913 commit e5209df

File tree

3 files changed

+172
-0
lines changed

3 files changed

+172
-0
lines changed

src/fixture/file.rs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use std::ffi;
2+
use std::path;
3+
4+
use tempfile;
5+
6+
use super::errors::*;
7+
8+
/// A potential file in the filesystem that is automatically deleted when
9+
/// it goes out of scope.
10+
///
11+
/// The [`NamedTempFile`] type creates a directory on the file system that
12+
/// is deleted once it goes out of scope. At construction, the
13+
/// `NamedTempFile` creates a new directory with a randomly generated name.
14+
///
15+
/// The constructor, [`NamedTempFile::new(name)`], creates directories in
16+
/// the location returned by [`std::env::temp_dir()`].
17+
///
18+
/// After creating a `NamedTempFile`, work with the file system by doing
19+
/// standard [`std::fs`] file system operations on its [`Path`],
20+
/// which can be retrieved with [`NamedTempFile::path()`]. Once the `NamedTempFile`
21+
/// value is dropped, the parent directory will be deleted, along with the file. It is your
22+
/// responsibility to ensure that no further file system operations are attempted inside the
23+
/// temporary directory once it has been deleted.
24+
///
25+
/// # Resource Leaking
26+
///
27+
/// Various platform-specific conditions may cause `NamedTempFile` to fail
28+
/// to delete the underlying directory. It's important to ensure that
29+
/// handles (like [`File`] and [`ReadDir`]) to the file inside the
30+
/// directory is dropped before the `NamedTempFile` goes out of scope. The
31+
/// `NamedTempFile` destructor will silently ignore any errors in deleting
32+
/// the directory; to instead handle errors call [`NamedTempFile::close()`].
33+
///
34+
/// Note that if the program exits before the `NamedTempFile` 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 file.
42+
///
43+
/// ```
44+
/// use assert_fs::fixture::NamedTempFile;
45+
///
46+
/// let tmp_file = NamedTempFile::new("foo.rs").unwrap();
47+
///
48+
/// // Ensure deletion happens.
49+
/// tmp_file.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+
/// [`NamedTempFile::close()`]: struct.NamedTempFile.html#method.close
56+
/// [`NamedTempFile::new()`]: struct.NamedTempFile.html#method.new
57+
/// [`NamedTempFile::path()`]: struct.NamedTempFile.html#method.path
58+
/// [`NamedTempFile`]: struct.NamedTempFile.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 NamedTempFile {
63+
temp: tempfile::TempDir,
64+
path: path::PathBuf,
65+
}
66+
67+
impl NamedTempFile {
68+
/// Attempts to make a temporary file inside of `env::temp_dir()`.
69+
///
70+
/// The file and parent directory will be automatically deleted once the returned
71+
/// `NamedTempFile` is destroyed.
72+
///
73+
/// # Errors
74+
///
75+
/// If the parent directory can not be created, `Err` is returned.
76+
///
77+
/// # Examples
78+
///
79+
/// ```
80+
/// use assert_fs::fixture::NamedTempFile;
81+
///
82+
/// let tmp_file = NamedTempFile::new("foo.rs").unwrap();
83+
///
84+
/// // Ensure deletion happens.
85+
/// tmp_file.close().unwrap();
86+
/// ```
87+
pub fn new<S>(name: S) -> Result<Self, FixtureError>
88+
where
89+
S: AsRef<ffi::OsStr>
90+
{
91+
let temp = tempfile::TempDir::new()
92+
.chain(FixtureError::new(FixtureKind::CreateDir))?;
93+
let path = temp.path().join(name.as_ref());
94+
Ok(Self { temp, path })
95+
}
96+
97+
/// Accesses the [`Path`] to the temporary file.
98+
///
99+
/// [`Path`]: http://doc.rust-lang.org/std/path/struct.Path.html
100+
///
101+
/// # Examples
102+
///
103+
/// ```
104+
/// use assert_fs::fixture::NamedTempFile;
105+
///
106+
/// let tmp_file = NamedTempFile::new("foo.rs").unwrap();
107+
///
108+
/// println!("{}", tmp_file.path().display());
109+
///
110+
/// // Ensure deletion happens.
111+
/// tmp_file.close().unwrap();
112+
/// ```
113+
pub fn path(&self) -> &path::Path {
114+
&self.path
115+
}
116+
117+
/// Closes and removes the temporary file and parent directory, returing a `Result`.
118+
///
119+
/// Although `NamedTempFile` removes the directory on drop, in the destructor
120+
/// any errors are ignored. To detect errors cleaning up the temporary
121+
/// directory, call `close` instead.
122+
///
123+
/// # Errors
124+
///
125+
/// This function may return a variety of [`std::io::Error`]s that result from deleting the
126+
/// temporary file and parent directory, These errors may be platform specific.
127+
///
128+
/// [`std::io::Error`]: http://doc.rust-lang.org/std/io/struct.Error.html
129+
///
130+
/// # Examples
131+
///
132+
/// ```
133+
/// use assert_fs::fixture::NamedTempFile;
134+
///
135+
/// let tmp_file = NamedTempFile::new("foo.rs").unwrap();
136+
///
137+
/// // Ensure deletion happens.
138+
/// tmp_file.close().unwrap();
139+
/// ```
140+
pub fn close(self) -> Result<(), FixtureError> {
141+
self.temp.close()
142+
.chain(FixtureError::new(FixtureKind::Cleanup))?;
143+
Ok(())
144+
}
145+
}

src/fixture/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Initialize the filesystem to use as test fixtures.
22
33
mod dir;
4+
mod file;
45
mod errors;
56
mod tools;
67
mod child;
@@ -9,3 +10,4 @@ pub use self::errors::*;
910
pub use self::tools::*;
1011
pub use self::child::*;
1112
pub use self::dir::*;
13+
pub use self::file::*;

src/fixture/tools.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use globwalk;
1010
use super::errors::*;
1111
use super::ChildPath;
1212
use super::TempDir;
13+
use super::NamedTempFile;
1314

1415
/// Create empty directories at [`ChildPath`].
1516
///
@@ -63,6 +64,12 @@ impl FileTouch for ChildPath {
6364
}
6465
}
6566

67+
impl FileTouch for NamedTempFile {
68+
fn touch(&self) -> io::Result<()> {
69+
touch(self.path())
70+
}
71+
}
72+
6673
/// Write a binary file at [`ChildPath`].
6774
///
6875
/// [`ChildPath`]: struct.ChildPath.html
@@ -92,6 +99,12 @@ impl FileWriteBin for ChildPath {
9299
}
93100
}
94101

102+
impl FileWriteBin for NamedTempFile {
103+
fn write_binary(&self, data: &[u8]) -> io::Result<()> {
104+
write_binary(self.path(), data)
105+
}
106+
}
107+
95108
/// Write a text file at [`ChildPath`].
96109
///
97110
/// [`ChildPath`]: struct.ChildPath.html
@@ -121,6 +134,12 @@ impl FileWriteStr for ChildPath {
121134
}
122135
}
123136

137+
impl FileWriteStr for NamedTempFile {
138+
fn write_str(&self, data: &str) -> io::Result<()> {
139+
write_str(self.path(), data)
140+
}
141+
}
142+
124143
/// Write (copy) a file to [`ChildPath`].
125144
///
126145
/// [`ChildPath`]: struct.ChildPath.html
@@ -151,6 +170,12 @@ impl FileWriteFile for ChildPath {
151170
}
152171
}
153172

173+
impl FileWriteFile for NamedTempFile {
174+
fn write_file(&self, data: &path::Path) -> io::Result<()> {
175+
write_file(self.path(), data)
176+
}
177+
}
178+
154179
/// Copy files into [`TempDir`].
155180
///
156181
/// [`TempDir`]: struct.TempDir.html

0 commit comments

Comments
 (0)