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