├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── fake │ ├── mod.rs │ ├── node.rs │ ├── registry.rs │ └── tempdir.rs ├── lib.rs ├── mock.rs └── os.rs └── tests └── fs.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | nofications: 7 | email: false 8 | script: 9 | - cargo build --verbose --all-features 10 | - cargo test --verbose --all-features 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased](https://github.com/iredelmeier/filesystem-rs/compare/v0.4.4...HEAD) 4 | 5 | ### Fixed 6 | 7 | * `FakeFileSystem::copy_file` uses `ErrorKind::NotFound` on attempts to copy a file that doesn't exist 8 | * `FakeFileSystem::remove_dir_all` requires all descendants to be readable, corresponding to the behaviour of `OsFileSystem::remove_dir_all` 9 | 10 | ## [v0.4.4](https://github.com/olivierlacan/keep-a-changelog/compare/v0.4.3...v0.4.4) 11 | 12 | ### Added 13 | 14 | * `FileSystem::read_file_into` method (thanks @jean-airoldie) 15 | 16 | ### Fixed 17 | 18 | * `FakeFileSystem::read_dir` now returns only children, not all descendants (thanks @jean-airoldie) 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filesystem" 3 | version = "0.4.4" 4 | description = "Real, fake, and mock implementations of file system operations" 5 | authors = ["Isobel Redelmeier "] 6 | license = "MIT" 7 | repository = "https://github.com/iredelmeier/filesystem-rs" 8 | readme = "README.md" 9 | categories = [ 10 | "development-tools::testing", 11 | "filesystem" 12 | ] 13 | keywords = [ 14 | "filesystem", 15 | "testing", 16 | "mock", 17 | "fake" 18 | ] 19 | 20 | [lib] 21 | bench = false 22 | doctest = false 23 | test = false 24 | 25 | [[test]] 26 | name = "fs" 27 | required-features = ["fake", "temp"] 28 | 29 | [features] 30 | default = ["fake", "temp"] 31 | 32 | fake = [] 33 | mock = ["pseudo"] 34 | temp = ["rand", "tempdir"] 35 | testing = ["mock", "fake"] 36 | 37 | [dependencies] 38 | pseudo = { version = "^0.1.0", optional = true } 39 | rand = { version = "^0.4", optional = true } 40 | tempdir = { version = "^0.3", optional = true } 41 | 42 | [dev-dependencies] 43 | pseudo = "^0.1.0" 44 | tempdir = "^0.3" 45 | 46 | [badges] 47 | travis-ci = { repository = "iredelmeier/filesystem-rs" } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Isobel Redelmeier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # filesystem-rs 2 | 3 | ### Real, fake, and mock implementations of file system operations. 4 | 5 | [![Build Status](https://travis-ci.org/iredelmeier/filesystem-rs.svg?branch=master)](https://travis-ci.org/iredelmeier/filesystem-rs) 6 | [![Docs](https://docs.rs/filesystem/badge.svg)](https://docs.rs/filesystem) 7 | [![Crates.io](https://img.shields.io/crates/v/filesystem.svg)](https://crates.io/crates/filesystem) 8 | 9 | [Documentation](https://docs.rs/filesystem) 10 | 11 | filesystem-rs provides real, fake, and mock implementations of file system-related functionality. It abstracts away details of certain common but complex operations (e.g., setting permissions) and makes it easier to test any file system-related logic without having to wait for slow I/O operations or coerce the file system into particular states. 12 | -------------------------------------------------------------------------------- /src/fake/mod.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::{OsStr, OsString}; 3 | use std::io::Result; 4 | use std::iter::Iterator; 5 | use std::path::{Path, PathBuf}; 6 | use std::sync::{Arc, Mutex, MutexGuard}; 7 | use std::vec::IntoIter; 8 | 9 | use FileSystem; 10 | #[cfg(unix)] 11 | use UnixFileSystem; 12 | #[cfg(feature = "temp")] 13 | use {TempDir, TempFileSystem}; 14 | 15 | #[cfg(feature = "temp")] 16 | pub use self::tempdir::FakeTempDir; 17 | 18 | use self::registry::Registry; 19 | 20 | mod node; 21 | mod registry; 22 | #[cfg(feature = "temp")] 23 | mod tempdir; 24 | 25 | /// An in-memory file system. 26 | #[derive(Clone, Debug, Default)] 27 | pub struct FakeFileSystem { 28 | registry: Arc>, 29 | } 30 | 31 | impl FakeFileSystem { 32 | pub fn new() -> Self { 33 | let registry = Registry::new(); 34 | 35 | FakeFileSystem { 36 | registry: Arc::new(Mutex::new(registry)), 37 | } 38 | } 39 | 40 | fn apply(&self, path: &Path, f: F) -> T 41 | where 42 | F: FnOnce(&MutexGuard, &Path) -> T, 43 | { 44 | let registry = self.registry.lock().unwrap(); 45 | let storage; 46 | let path = if path.is_relative() { 47 | storage = registry 48 | .current_dir() 49 | .unwrap_or_else(|_| PathBuf::from("/")) 50 | .join(path); 51 | &storage 52 | } else { 53 | path 54 | }; 55 | 56 | f(®istry, path) 57 | } 58 | 59 | fn apply_mut(&self, path: &Path, mut f: F) -> T 60 | where 61 | F: FnMut(&mut MutexGuard, &Path) -> T, 62 | { 63 | let mut registry = self.registry.lock().unwrap(); 64 | let storage; 65 | let path = if path.is_relative() { 66 | storage = registry 67 | .current_dir() 68 | .unwrap_or_else(|_| PathBuf::from("/")) 69 | .join(path); 70 | &storage 71 | } else { 72 | path 73 | }; 74 | 75 | f(&mut registry, path) 76 | } 77 | 78 | fn apply_mut_from_to(&self, from: &Path, to: &Path, mut f: F) -> T 79 | where 80 | F: FnMut(&mut MutexGuard, &Path, &Path) -> T, 81 | { 82 | let mut registry = self.registry.lock().unwrap(); 83 | let from_storage; 84 | let from = if from.is_relative() { 85 | from_storage = registry 86 | .current_dir() 87 | .unwrap_or_else(|_| PathBuf::from("/")) 88 | .join(from); 89 | &from_storage 90 | } else { 91 | from 92 | }; 93 | let to_storage; 94 | let to = if to.is_relative() { 95 | to_storage = registry 96 | .current_dir() 97 | .unwrap_or_else(|_| PathBuf::from("/")) 98 | .join(to); 99 | &to_storage 100 | } else { 101 | to 102 | }; 103 | 104 | f(&mut registry, from, to) 105 | } 106 | } 107 | 108 | impl FileSystem for FakeFileSystem { 109 | type DirEntry = DirEntry; 110 | type ReadDir = ReadDir; 111 | 112 | fn current_dir(&self) -> Result { 113 | let registry = self.registry.lock().unwrap(); 114 | registry.current_dir() 115 | } 116 | 117 | fn set_current_dir>(&self, path: P) -> Result<()> { 118 | self.apply_mut(path.as_ref(), |r, p| r.set_current_dir(p.to_path_buf())) 119 | } 120 | 121 | fn is_dir>(&self, path: P) -> bool { 122 | self.apply(path.as_ref(), |r, p| r.is_dir(p)) 123 | } 124 | 125 | fn is_file>(&self, path: P) -> bool { 126 | self.apply(path.as_ref(), |r, p| r.is_file(p)) 127 | } 128 | 129 | fn create_dir>(&self, path: P) -> Result<()> { 130 | self.apply_mut(path.as_ref(), |r, p| r.create_dir(p)) 131 | } 132 | 133 | fn create_dir_all>(&self, path: P) -> Result<()> { 134 | self.apply_mut(path.as_ref(), |r, p| r.create_dir_all(p)) 135 | } 136 | 137 | fn remove_dir>(&self, path: P) -> Result<()> { 138 | self.apply_mut(path.as_ref(), |r, p| r.remove_dir(p)) 139 | } 140 | 141 | fn remove_dir_all>(&self, path: P) -> Result<()> { 142 | self.apply_mut(path.as_ref(), |r, p| r.remove_dir_all(p)) 143 | } 144 | 145 | fn read_dir>(&self, path: P) -> Result { 146 | let path = path.as_ref(); 147 | 148 | self.apply(path, |r, p| r.read_dir(p)).map(|entries| { 149 | let entries = entries 150 | .iter() 151 | .map(|e| { 152 | let file_name = e.file_name().unwrap_or_else(|| e.as_os_str()); 153 | 154 | Ok(DirEntry::new(path, &file_name)) 155 | }) 156 | .collect(); 157 | 158 | ReadDir::new(entries) 159 | }) 160 | } 161 | 162 | fn create_file(&self, path: P, buf: B) -> Result<()> 163 | where 164 | P: AsRef, 165 | B: AsRef<[u8]>, 166 | { 167 | self.apply_mut(path.as_ref(), |r, p| r.create_file(p, buf.as_ref())) 168 | } 169 | 170 | fn write_file(&self, path: P, buf: B) -> Result<()> 171 | where 172 | P: AsRef, 173 | B: AsRef<[u8]>, 174 | { 175 | self.apply_mut(path.as_ref(), |r, p| r.write_file(p, buf.as_ref())) 176 | } 177 | 178 | fn overwrite_file(&self, path: P, buf: B) -> Result<()> 179 | where 180 | P: AsRef, 181 | B: AsRef<[u8]>, 182 | { 183 | self.apply_mut(path.as_ref(), |r, p| r.overwrite_file(p, buf.as_ref())) 184 | } 185 | 186 | fn read_file>(&self, path: P) -> Result> { 187 | self.apply(path.as_ref(), |r, p| r.read_file(p)) 188 | } 189 | 190 | fn read_file_to_string>(&self, path: P) -> Result { 191 | self.apply(path.as_ref(), |r, p| r.read_file_to_string(p)) 192 | } 193 | 194 | fn read_file_into(&self, path: P, mut buf: B) -> Result 195 | where 196 | P: AsRef, 197 | B: AsMut>, 198 | { 199 | self.apply(path.as_ref(), |r, p| r.read_file_into(p, buf.as_mut())) 200 | } 201 | 202 | fn remove_file>(&self, path: P) -> Result<()> { 203 | self.apply_mut(path.as_ref(), |r, p| r.remove_file(p)) 204 | } 205 | 206 | fn copy_file(&self, from: P, to: Q) -> Result<()> 207 | where 208 | P: AsRef, 209 | Q: AsRef, 210 | { 211 | self.apply_mut_from_to(from.as_ref(), to.as_ref(), |r, from, to| { 212 | r.copy_file(from, to) 213 | }) 214 | } 215 | 216 | fn rename(&self, from: P, to: Q) -> Result<()> 217 | where 218 | P: AsRef, 219 | Q: AsRef, 220 | { 221 | self.apply_mut_from_to(from.as_ref(), to.as_ref(), |r, from, to| r.rename(from, to)) 222 | } 223 | 224 | fn readonly>(&self, path: P) -> Result { 225 | self.apply(path.as_ref(), |r, p| r.readonly(p)) 226 | } 227 | 228 | fn set_readonly>(&self, path: P, readonly: bool) -> Result<()> { 229 | self.apply_mut(path.as_ref(), |r, p| r.set_readonly(p, readonly)) 230 | } 231 | 232 | fn len>(&self, path: P) -> u64 { 233 | self.apply(path.as_ref(), |r, p| r.len(p)) 234 | } 235 | } 236 | 237 | #[derive(Debug, Clone)] 238 | pub struct DirEntry { 239 | parent: PathBuf, 240 | file_name: OsString, 241 | } 242 | 243 | impl DirEntry { 244 | fn new(parent: P, file_name: S) -> Self 245 | where 246 | P: AsRef, 247 | S: AsRef, 248 | { 249 | DirEntry { 250 | parent: parent.as_ref().to_path_buf(), 251 | file_name: file_name.as_ref().to_os_string(), 252 | } 253 | } 254 | } 255 | 256 | impl crate::DirEntry for DirEntry { 257 | fn file_name(&self) -> OsString { 258 | self.file_name.clone() 259 | } 260 | 261 | fn path(&self) -> PathBuf { 262 | self.parent.join(&self.file_name) 263 | } 264 | } 265 | 266 | #[derive(Debug)] 267 | pub struct ReadDir(IntoIter>); 268 | 269 | impl ReadDir { 270 | fn new(entries: Vec>) -> Self { 271 | ReadDir(entries.into_iter()) 272 | } 273 | } 274 | 275 | impl Iterator for ReadDir { 276 | type Item = Result; 277 | 278 | fn next(&mut self) -> Option { 279 | self.0.next() 280 | } 281 | } 282 | 283 | impl crate::ReadDir for ReadDir {} 284 | 285 | #[cfg(unix)] 286 | impl UnixFileSystem for FakeFileSystem { 287 | fn mode>(&self, path: P) -> Result { 288 | self.apply(path.as_ref(), |r, p| r.mode(p)) 289 | } 290 | 291 | fn set_mode>(&self, path: P, mode: u32) -> Result<()> { 292 | self.apply_mut(path.as_ref(), |r, p| r.set_mode(p, mode)) 293 | } 294 | } 295 | 296 | #[cfg(feature = "temp")] 297 | impl TempFileSystem for FakeFileSystem { 298 | type TempDir = FakeTempDir; 299 | 300 | fn temp_dir>(&self, prefix: S) -> Result { 301 | let base = env::temp_dir(); 302 | let dir = FakeTempDir::new(Arc::downgrade(&self.registry), &base, prefix.as_ref()); 303 | 304 | self.create_dir_all(&dir.path()).and(Ok(dir)) 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/fake/node.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | pub struct File { 3 | pub contents: Vec, 4 | pub mode: u32, 5 | } 6 | 7 | impl File { 8 | pub fn new(contents: Vec) -> Self { 9 | File { 10 | contents, 11 | mode: 0o644, 12 | } 13 | } 14 | } 15 | 16 | #[derive(Debug, Clone, Default)] 17 | pub struct Dir { 18 | pub mode: u32, 19 | } 20 | 21 | impl Dir { 22 | pub fn new() -> Self { 23 | Dir { mode: 0o644 } 24 | } 25 | } 26 | 27 | #[derive(Debug, Clone)] 28 | pub enum Node { 29 | File(File), 30 | Dir(Dir), 31 | } 32 | 33 | impl Node { 34 | pub fn is_file(&self) -> bool { 35 | match *self { 36 | Self::File(_) => true, 37 | _ => false, 38 | } 39 | } 40 | 41 | pub fn is_dir(&self) -> bool { 42 | match *self { 43 | Self::Dir(_) => true, 44 | _ => false, 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/fake/registry.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::io::{Error, ErrorKind, Result}; 3 | use std::path::{Path, PathBuf}; 4 | 5 | use super::node::{Dir, File, Node}; 6 | 7 | #[derive(Debug, Clone, Default)] 8 | pub struct Registry { 9 | cwd: PathBuf, 10 | files: HashMap, 11 | } 12 | 13 | impl Registry { 14 | pub fn new() -> Self { 15 | let cwd = PathBuf::from("/"); 16 | let mut files = HashMap::new(); 17 | 18 | files.insert(cwd.clone(), Node::Dir(Dir::new())); 19 | 20 | Registry { cwd, files } 21 | } 22 | 23 | pub fn current_dir(&self) -> Result { 24 | self.get_dir(&self.cwd).map(|_| self.cwd.clone()) 25 | } 26 | 27 | pub fn set_current_dir(&mut self, cwd: PathBuf) -> Result<()> { 28 | match self.get_dir(&cwd) { 29 | Ok(_) => { 30 | self.cwd = cwd; 31 | Ok(()) 32 | } 33 | Err(e) => Err(e), 34 | } 35 | } 36 | 37 | pub fn is_dir(&self, path: &Path) -> bool { 38 | self.get(path).map(Node::is_dir).unwrap_or(false) 39 | } 40 | 41 | pub fn is_file(&self, path: &Path) -> bool { 42 | self.get(path).map(Node::is_file).unwrap_or(false) 43 | } 44 | 45 | pub fn create_dir(&mut self, path: &Path) -> Result<()> { 46 | self.insert(path.to_path_buf(), Node::Dir(Dir::new())) 47 | } 48 | 49 | pub fn create_dir_all(&mut self, path: &Path) -> Result<()> { 50 | // Based on std::fs::DirBuilder::create_dir_all 51 | if path == Path::new("") { 52 | return Ok(()); 53 | } 54 | 55 | match self.create_dir(path) { 56 | Ok(_) => return Ok(()), 57 | Err(ref e) if e.kind() == ErrorKind::NotFound => {} 58 | Err(_) if self.is_dir(path) => return Ok(()), 59 | Err(e) => return Err(e), 60 | } 61 | 62 | match path.parent() { 63 | Some(p) => self.create_dir_all(p)?, 64 | None => return Err(create_error(ErrorKind::Other)), 65 | } 66 | 67 | self.create_dir_all(path) 68 | } 69 | 70 | pub fn remove_dir(&mut self, path: &Path) -> Result<()> { 71 | match self.get_dir(path) { 72 | Ok(_) if self.descendants(path).is_empty() => {} 73 | Ok(_) => return Err(create_error(ErrorKind::Other)), 74 | Err(e) => return Err(e), 75 | }; 76 | 77 | self.remove(path).and(Ok(())) 78 | } 79 | 80 | pub fn remove_dir_all(&mut self, path: &Path) -> Result<()> { 81 | self.get_dir_mut(path)?; 82 | 83 | let descendants = self.descendants(path); 84 | let all_readable = descendants.iter().all(|(_, mode)| mode & 0o444 != 0); 85 | 86 | if !all_readable { 87 | return Err(create_error(ErrorKind::PermissionDenied)); 88 | } 89 | 90 | for (child, _) in descendants { 91 | self.remove(&child)?; 92 | } 93 | 94 | self.remove(path).and(Ok(())) 95 | } 96 | 97 | pub fn read_dir(&self, path: &Path) -> Result> { 98 | self.get_dir(path)?; 99 | 100 | Ok(self.children(path)) 101 | } 102 | 103 | pub fn create_file(&mut self, path: &Path, buf: &[u8]) -> Result<()> { 104 | let file = File::new(buf.to_vec()); 105 | 106 | self.insert(path.to_path_buf(), Node::File(file)) 107 | } 108 | 109 | pub fn write_file(&mut self, path: &Path, buf: &[u8]) -> Result<()> { 110 | self.get_file_mut(path) 111 | .map(|ref mut f| f.contents = buf.to_vec()) 112 | .or_else(|e| { 113 | if e.kind() == ErrorKind::NotFound { 114 | self.create_file(path, buf) 115 | } else { 116 | Err(e) 117 | } 118 | }) 119 | } 120 | 121 | pub fn overwrite_file(&mut self, path: &Path, buf: &[u8]) -> Result<()> { 122 | self.get_file_mut(path) 123 | .map(|ref mut f| f.contents = buf.to_vec()) 124 | } 125 | 126 | pub fn read_file(&self, path: &Path) -> Result> { 127 | match self.get_file(path) { 128 | Ok(f) if f.mode & 0o444 != 0 => Ok(f.contents.clone()), 129 | Ok(_) => Err(create_error(ErrorKind::PermissionDenied)), 130 | Err(err) => Err(err), 131 | } 132 | } 133 | 134 | pub fn read_file_to_string(&self, path: &Path) -> Result { 135 | match self.read_file(path) { 136 | Ok(vec) => String::from_utf8(vec).map_err(|_| create_error(ErrorKind::InvalidData)), 137 | Err(err) => Err(err), 138 | } 139 | } 140 | 141 | pub fn read_file_into(&self, path: &Path, buf: &mut Vec) -> Result { 142 | match self.get_file(path) { 143 | Ok(f) if f.mode & 0o444 != 0 => { 144 | buf.extend(&f.contents); 145 | Ok(f.contents.len()) 146 | } 147 | Ok(_) => Err(create_error(ErrorKind::PermissionDenied)), 148 | Err(err) => Err(err), 149 | } 150 | } 151 | 152 | pub fn remove_file(&mut self, path: &Path) -> Result<()> { 153 | match self.get_file(path) { 154 | Ok(_) => self.remove(path).and(Ok(())), 155 | Err(e) => Err(e), 156 | } 157 | } 158 | 159 | pub fn copy_file(&mut self, from: &Path, to: &Path) -> Result<()> { 160 | match self.read_file(from) { 161 | Ok(ref buf) => self.write_file(to, buf), 162 | Err(ref err) if err.kind() == ErrorKind::Other => { 163 | Err(create_error(ErrorKind::InvalidInput)) 164 | } 165 | Err(err) => Err(err), 166 | } 167 | } 168 | 169 | pub fn rename(&mut self, from: &Path, to: &Path) -> Result<()> { 170 | match (self.get(from), self.get(to)) { 171 | (Ok(&Node::File(_)), Ok(&Node::File(_))) => { 172 | self.remove_file(to)?; 173 | self.rename_path(from, to.to_path_buf()) 174 | } 175 | (Ok(&Node::File(_)), Err(ref err)) if err.kind() == ErrorKind::NotFound => { 176 | self.rename_path(from, to.to_path_buf()) 177 | } 178 | (Ok(&Node::Dir(_)), Ok(&Node::Dir(_))) if self.descendants(to).is_empty() => { 179 | self.remove(to)?; 180 | self.move_dir(from, to) 181 | } 182 | (Ok(&Node::File(_)), Ok(&Node::Dir(_))) 183 | | (Ok(&Node::Dir(_)), Ok(&Node::File(_))) 184 | | (Ok(&Node::Dir(_)), Ok(&Node::Dir(_))) => Err(create_error(ErrorKind::Other)), 185 | (Ok(&Node::Dir(_)), Err(ref err)) if err.kind() == ErrorKind::NotFound => { 186 | self.move_dir(from, to) 187 | } 188 | (Err(err), _) => Err(err), 189 | (_, Err(err)) => Err(err), 190 | } 191 | } 192 | 193 | pub fn readonly(&self, path: &Path) -> Result { 194 | self.get(path).map(|node| match node { 195 | Node::File(ref file) => file.mode & 0o222 == 0, 196 | Node::Dir(ref dir) => dir.mode & 0o222 == 0, 197 | }) 198 | } 199 | 200 | pub fn set_readonly(&mut self, path: &Path, readonly: bool) -> Result<()> { 201 | self.get_mut(path).map(|node| match node { 202 | Node::File(ref mut file) => { 203 | if readonly { 204 | file.mode &= !0o222 205 | } else { 206 | file.mode |= 0o222 207 | } 208 | } 209 | Node::Dir(ref mut dir) => { 210 | if readonly { 211 | dir.mode &= !0o222 212 | } else { 213 | dir.mode |= 0o222 214 | } 215 | } 216 | }) 217 | } 218 | 219 | pub fn mode(&self, path: &Path) -> Result { 220 | self.get(path).map(|node| match node { 221 | Node::File(ref file) => file.mode, 222 | Node::Dir(ref dir) => dir.mode, 223 | }) 224 | } 225 | 226 | pub fn set_mode(&mut self, path: &Path, mode: u32) -> Result<()> { 227 | self.get_mut(path).map(|node| match node { 228 | Node::File(ref mut file) => file.mode = mode, 229 | Node::Dir(ref mut dir) => dir.mode = mode, 230 | }) 231 | } 232 | 233 | pub fn len(&self, path: &Path) -> u64 { 234 | self.get(path) 235 | .map(|node| match node { 236 | Node::File(ref file) => file.contents.len() as u64, 237 | Node::Dir(_) => 4096, 238 | }) 239 | .unwrap_or(0) 240 | } 241 | 242 | fn get(&self, path: &Path) -> Result<&Node> { 243 | self.files 244 | .get(path) 245 | .ok_or_else(|| create_error(ErrorKind::NotFound)) 246 | } 247 | 248 | fn get_mut(&mut self, path: &Path) -> Result<&mut Node> { 249 | self.files 250 | .get_mut(path) 251 | .ok_or_else(|| create_error(ErrorKind::NotFound)) 252 | } 253 | 254 | fn get_dir(&self, path: &Path) -> Result<&Dir> { 255 | self.get(path).and_then(|node| match node { 256 | Node::Dir(ref dir) => Ok(dir), 257 | Node::File(_) => Err(create_error(ErrorKind::Other)), 258 | }) 259 | } 260 | 261 | fn get_dir_mut(&mut self, path: &Path) -> Result<&mut Dir> { 262 | self.get_mut(path).and_then(|node| match node { 263 | Node::Dir(ref mut dir) if dir.mode & 0o222 != 0 => Ok(dir), 264 | Node::Dir(_) => Err(create_error(ErrorKind::PermissionDenied)), 265 | Node::File(_) => Err(create_error(ErrorKind::Other)), 266 | }) 267 | } 268 | 269 | fn get_file(&self, path: &Path) -> Result<&File> { 270 | self.get(path).and_then(|node| match node { 271 | Node::File(ref file) => Ok(file), 272 | Node::Dir(_) => Err(create_error(ErrorKind::Other)), 273 | }) 274 | } 275 | 276 | fn get_file_mut(&mut self, path: &Path) -> Result<&mut File> { 277 | self.get_mut(path).and_then(|node| match node { 278 | Node::File(ref mut file) if file.mode & 0o222 != 0 => Ok(file), 279 | Node::File(_) => Err(create_error(ErrorKind::PermissionDenied)), 280 | Node::Dir(_) => Err(create_error(ErrorKind::Other)), 281 | }) 282 | } 283 | 284 | fn insert(&mut self, path: PathBuf, file: Node) -> Result<()> { 285 | if self.files.contains_key(&path) { 286 | return Err(create_error(ErrorKind::AlreadyExists)); 287 | } else if let Some(p) = path.parent() { 288 | self.get_dir_mut(p)?; 289 | } 290 | 291 | self.files.insert(path, file); 292 | 293 | Ok(()) 294 | } 295 | 296 | fn remove(&mut self, path: &Path) -> Result { 297 | match self.files.remove(path) { 298 | Some(f) => Ok(f), 299 | None => Err(create_error(ErrorKind::NotFound)), 300 | } 301 | } 302 | 303 | fn descendants(&self, path: &Path) -> Vec<(PathBuf, u32)> { 304 | self.files 305 | .iter() 306 | .filter(|(p, _)| p.starts_with(path) && *p != path) 307 | .map(|(p, n)| { 308 | ( 309 | p.to_path_buf(), 310 | match n { 311 | Node::File(ref file) => file.mode, 312 | Node::Dir(ref dir) => dir.mode, 313 | }, 314 | ) 315 | }) 316 | .collect() 317 | } 318 | 319 | fn children(&self, path: &Path) -> Vec { 320 | self.files 321 | .keys() 322 | .filter(|p| p.parent().map(|parent| parent == path).unwrap_or(false)) 323 | .map(|p| p.to_path_buf()) 324 | .collect() 325 | } 326 | 327 | fn rename_path(&mut self, from: &Path, to: PathBuf) -> Result<()> { 328 | let file = self.remove(from)?; 329 | self.insert(to, file) 330 | } 331 | 332 | fn move_dir(&mut self, from: &Path, to: &Path) -> Result<()> { 333 | self.rename_path(from, to.to_path_buf())?; 334 | 335 | for child in self.children(from) { 336 | let stem = child.strip_prefix(from).unwrap_or(&child); 337 | let new_path = to.join(stem); 338 | 339 | self.rename(&child, &new_path)?; 340 | } 341 | 342 | Ok(()) 343 | } 344 | } 345 | 346 | fn create_error(kind: ErrorKind) -> Error { 347 | // Based on private std::io::ErrorKind::as_str() 348 | let description = match kind { 349 | ErrorKind::NotFound => "entity not found", 350 | ErrorKind::PermissionDenied => "permission denied", 351 | ErrorKind::ConnectionRefused => "connection refused", 352 | ErrorKind::ConnectionReset => "connection reset", 353 | ErrorKind::ConnectionAborted => "connection aborted", 354 | ErrorKind::NotConnected => "not connected", 355 | ErrorKind::AddrInUse => "address in use", 356 | ErrorKind::AddrNotAvailable => "address not available", 357 | ErrorKind::BrokenPipe => "broken pipe", 358 | ErrorKind::AlreadyExists => "entity already exists", 359 | ErrorKind::WouldBlock => "operation would block", 360 | ErrorKind::InvalidInput => "invalid input parameter", 361 | ErrorKind::InvalidData => "invalid data", 362 | ErrorKind::TimedOut => "timed out", 363 | ErrorKind::WriteZero => "write zero", 364 | ErrorKind::Interrupted => "operation interrupted", 365 | ErrorKind::Other => "other os error", 366 | ErrorKind::UnexpectedEof => "unexpected end of file", 367 | _ => "other", 368 | }; 369 | 370 | Error::new(kind, description) 371 | } 372 | -------------------------------------------------------------------------------- /src/fake/tempdir.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::sync::{Mutex, Weak}; 3 | 4 | use rand; 5 | use rand::Rng; 6 | 7 | use TempDir; 8 | 9 | use super::Registry; 10 | 11 | const SUFFIX_LENGTH: usize = 10; 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct FakeTempDir { 15 | registry: Weak>, 16 | path: PathBuf, 17 | } 18 | 19 | impl FakeTempDir { 20 | pub fn new(registry: Weak>, base: &Path, prefix: &str) -> Self { 21 | let mut rng = rand::thread_rng(); 22 | let suffix: String = rng.gen_ascii_chars().take(SUFFIX_LENGTH).collect(); 23 | let name = format!("{}_{}", prefix, suffix); 24 | let path = base.join(prefix).join(name); 25 | 26 | FakeTempDir { registry, path } 27 | } 28 | } 29 | 30 | impl TempDir for FakeTempDir { 31 | fn path(&self) -> &Path { 32 | self.path.as_ref() 33 | } 34 | } 35 | 36 | impl Drop for FakeTempDir { 37 | fn drop(&mut self) { 38 | if let Some(registry) = self.registry.upgrade() { 39 | let _ = registry.lock().unwrap().remove_dir_all(&self.path); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "mock", test))] 2 | extern crate pseudo; 3 | #[cfg(feature = "temp")] 4 | extern crate rand; 5 | #[cfg(feature = "temp")] 6 | extern crate tempdir; 7 | 8 | use std::ffi::OsString; 9 | use std::io::Result; 10 | use std::path::{Path, PathBuf}; 11 | 12 | #[cfg(feature = "fake")] 13 | pub use fake::{FakeFileSystem, FakeTempDir}; 14 | #[cfg(any(feature = "mock", test))] 15 | pub use mock::{FakeError, MockFileSystem}; 16 | pub use os::OsFileSystem; 17 | #[cfg(feature = "temp")] 18 | pub use os::OsTempDir; 19 | 20 | #[cfg(feature = "fake")] 21 | mod fake; 22 | #[cfg(any(feature = "mock", test))] 23 | mod mock; 24 | mod os; 25 | 26 | /// Provides standard file system operations. 27 | pub trait FileSystem { 28 | type DirEntry: DirEntry; 29 | type ReadDir: ReadDir; 30 | 31 | /// Returns the current working directory. 32 | /// This is based on [`std::env::current_dir`]. 33 | /// 34 | /// [`std::env::current_dir`]: https://doc.rust-lang.org/std/env/fn.current_dir.html 35 | fn current_dir(&self) -> Result; 36 | /// Updates the current working directory. 37 | /// This is based on [`std::env::set_current_dir`]. 38 | /// 39 | /// [`std::env::set_current_dir`]: https://doc.rust-lang.org/std/env/fn.set_current_dir.html 40 | fn set_current_dir>(&self, path: P) -> Result<()>; 41 | 42 | /// Determines whether the path exists and points to a directory. 43 | fn is_dir>(&self, path: P) -> bool; 44 | /// Determines whether the path exists and points to a file. 45 | fn is_file>(&self, path: P) -> bool; 46 | 47 | /// Creates a new directory. 48 | /// This is based on [`std::fs::create_dir`]. 49 | /// 50 | /// [`std::fs::create_dir`]: https://doc.rust-lang.org/std/fs/fn.create_dir.html 51 | fn create_dir>(&self, path: P) -> Result<()>; 52 | /// Recursively creates a directory and any missing parents. 53 | /// This is based on [`std::fs::create_dir`]. 54 | /// 55 | /// [`std::fs::create_dir_all`]: https://doc.rust-lang.org/std/fs/fn.create_dir_all.html 56 | fn create_dir_all>(&self, path: P) -> Result<()>; 57 | /// Removes an empty directory. 58 | /// This is based on [`std::fs::remove_dir`]. 59 | /// 60 | /// [`std::fs::remove_dir`]: https://doc.rust-lang.org/std/fs/fn.remove_dir.html 61 | fn remove_dir>(&self, path: P) -> Result<()>; 62 | /// Removes a directory and any child files or directories. 63 | /// This is based on [`std::fs::remove_dir_all`]. 64 | /// 65 | /// [`std::fs::remove_dir_all`]: https://doc.rust-lang.org/std/fs/fn.remove_dir_all.html 66 | fn remove_dir_all>(&self, path: P) -> Result<()>; 67 | /// Returns an iterator over the entries in a directory. 68 | /// This is based on [`std::fs::read_dir`]. 69 | /// 70 | /// [`std::fs::read_dir`]: https://doc.rust-lang.org/std/fs/fn.read_dir.html 71 | fn read_dir>(&self, path: P) -> Result; 72 | 73 | /// Writes `buf` to a new file at `path`. 74 | /// 75 | /// # Errors 76 | /// 77 | /// * A file or directory already exists at `path`. 78 | /// * The parent directory of `path` does not exist. 79 | /// * Current user has insufficient permissions. 80 | fn create_file(&self, path: P, buf: B) -> Result<()> 81 | where 82 | P: AsRef, 83 | B: AsRef<[u8]>; 84 | /// Writes `buf` to a new or existing file at `buf`. 85 | /// This will overwrite any contents that already exist. 86 | /// 87 | /// # Errors 88 | /// 89 | /// * The parent directory of `path` does not exist. 90 | /// * Current user has insufficient permissions. 91 | fn write_file(&self, path: P, buf: B) -> Result<()> 92 | where 93 | P: AsRef, 94 | B: AsRef<[u8]>; 95 | /// Writes `buf` to an existing file at `buf`. 96 | /// This will overwrite any contents that already exist. 97 | /// 98 | /// # Errors 99 | /// 100 | /// * No file `file` does not exist. 101 | /// * The node at `file` is a directory. 102 | /// * Current user has insufficient permissions. 103 | fn overwrite_file(&self, path: P, buf: B) -> Result<()> 104 | where 105 | P: AsRef, 106 | B: AsRef<[u8]>; 107 | /// Returns the contents of `path`. 108 | /// 109 | /// # Errors 110 | /// 111 | /// * `path` does not exist. 112 | /// * `path` is a directory. 113 | /// * Current user has insufficient permissions. 114 | fn read_file>(&self, path: P) -> Result>; 115 | /// Returns the contents of `path` as a string. 116 | /// 117 | /// # Errors 118 | /// 119 | /// * `path` does not exist. 120 | /// * `path` is a directory. 121 | /// * Current user has insufficient permissions. 122 | /// * Contents are not valid UTF-8 123 | fn read_file_to_string>(&self, path: P) -> Result; 124 | /// Writes the contents of `path` into the buffer. If successful, returns 125 | /// the number of bytes that were read. 126 | /// 127 | /// # Errors 128 | /// 129 | /// * `path` does not exist. 130 | /// * `path` is a directory. 131 | /// * Current user has insufficient permissions. 132 | fn read_file_into(&self, path: P, buf: B) -> Result 133 | where 134 | P: AsRef, 135 | B: AsMut>; 136 | /// Removes the file at `path`. 137 | /// This is based on [`std::fs::remove_file`]. 138 | /// 139 | /// [`std::fs::remove_file`]: https://doc.rust-lang.org/std/fs/fn.remove_file.html 140 | fn remove_file>(&self, path: P) -> Result<()>; 141 | /// Copies the file at path `from` to the path `to`. 142 | /// This is based on [`std::fs::copy`]. 143 | /// 144 | /// [`std::fs::copy`]: https://doc.rust-lang.org/std/fs/fn.copy.html 145 | fn copy_file(&self, from: P, to: Q) -> Result<()> 146 | where 147 | P: AsRef, 148 | Q: AsRef; 149 | 150 | /// Renames a file or directory. 151 | /// If both `from` and `to` are files, `to` will be replaced. 152 | /// Based on [`std::fs::rename`]. 153 | /// 154 | /// [`std::fs::rename`]: https://doc.rust-lang.org/std/fs/fn.rename.html 155 | fn rename(&self, from: P, to: Q) -> Result<()> 156 | where 157 | P: AsRef, 158 | Q: AsRef; 159 | 160 | /// Returns `true` if `path` is a readonly file. 161 | /// 162 | /// # Errors 163 | /// 164 | /// * `path` does not exist. 165 | /// * Current user has insufficient permissions. 166 | fn readonly>(&self, path: P) -> Result; 167 | /// Sets or unsets the readonly flag of `path`. 168 | /// 169 | /// # Errors 170 | /// 171 | /// * `path` does not exist. 172 | /// * Current user has insufficient permissions. 173 | fn set_readonly>(&self, path: P, readonly: bool) -> Result<()>; 174 | 175 | /// Returns the length of the node at the path 176 | /// or 0 if the node does not exist. 177 | fn len>(&self, path: P) -> u64; 178 | } 179 | 180 | pub trait DirEntry { 181 | fn file_name(&self) -> OsString; 182 | fn path(&self) -> PathBuf; 183 | } 184 | 185 | pub trait ReadDir: Iterator> {} 186 | 187 | #[cfg(unix)] 188 | pub trait UnixFileSystem { 189 | /// Returns the current mode bits of `path`. 190 | /// 191 | /// # Errors 192 | /// 193 | /// * `path` does not exist. 194 | /// * Current user has insufficient permissions. 195 | fn mode>(&self, path: P) -> Result; 196 | /// Sets the mode bits of `path`. 197 | /// 198 | /// # Errors 199 | /// 200 | /// * `path` does not exist. 201 | /// * Current user has insufficient permissions. 202 | fn set_mode>(&self, path: P, mode: u32) -> Result<()>; 203 | } 204 | 205 | #[cfg(feature = "temp")] 206 | /// Tracks a temporary directory that will be deleted once the struct goes out of scope. 207 | pub trait TempDir { 208 | /// Returns the [`Path`] of the temporary directory. 209 | /// 210 | /// [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html 211 | fn path(&self) -> &Path; 212 | } 213 | 214 | #[cfg(feature = "temp")] 215 | pub trait TempFileSystem { 216 | type TempDir: TempDir; 217 | 218 | /// Creates a new temporary directory. 219 | fn temp_dir>(&self, prefix: S) -> Result; 220 | } 221 | -------------------------------------------------------------------------------- /src/mock.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::ffi::OsString; 3 | use std::io::{Error, ErrorKind}; 4 | use std::path::{Path, PathBuf}; 5 | use std::vec::IntoIter; 6 | 7 | use pseudo::Mock; 8 | 9 | use FileSystem; 10 | 11 | #[derive(Debug, Clone, PartialEq)] 12 | pub struct FakeError { 13 | kind: ErrorKind, 14 | description: String, 15 | } 16 | 17 | #[derive(Debug, Clone, PartialEq)] 18 | pub struct DirEntry { 19 | file_name: PathBuf, 20 | is_file: bool, 21 | } 22 | 23 | impl DirEntry { 24 | pub fn new>(file_name: P, is_file: bool) -> Self { 25 | DirEntry { 26 | file_name: file_name.as_ref().to_path_buf(), 27 | is_file, 28 | } 29 | } 30 | } 31 | 32 | impl crate::DirEntry for DirEntry { 33 | fn path(&self) -> PathBuf { 34 | self.file_name.clone() 35 | } 36 | 37 | fn file_name(&self) -> OsString { 38 | self.file_name.clone().into_os_string() 39 | } 40 | } 41 | 42 | #[derive(Debug)] 43 | pub struct ReadDir(IntoIter>); 44 | 45 | impl ReadDir { 46 | pub fn new() -> Self { 47 | ReadDir(vec![].into_iter()) 48 | } 49 | } 50 | 51 | impl Default for ReadDir { 52 | fn default() -> Self { 53 | Self::new() 54 | } 55 | } 56 | 57 | impl Iterator for ReadDir { 58 | type Item = Result; 59 | 60 | fn next(&mut self) -> Option { 61 | self.0.next() 62 | } 63 | } 64 | 65 | impl crate::ReadDir for ReadDir {} 66 | 67 | impl From for FakeError { 68 | fn from(err: Error) -> Self { 69 | FakeError { 70 | kind: err.kind(), 71 | description: err.description().to_string(), 72 | } 73 | } 74 | } 75 | 76 | impl From for Error { 77 | fn from(err: FakeError) -> Self { 78 | Error::new(err.kind, err.description) 79 | } 80 | } 81 | 82 | #[derive(Debug, Clone)] 83 | pub struct MockFileSystem { 84 | pub current_dir: Mock<(), Result>, 85 | pub set_current_dir: Mock>, 86 | 87 | pub is_dir: Mock, 88 | pub is_file: Mock, 89 | 90 | pub create_dir: Mock>, 91 | pub create_dir_all: Mock>, 92 | pub remove_dir: Mock>, 93 | pub remove_dir_all: Mock>, 94 | pub read_dir: Mock>, FakeError>>, 95 | 96 | pub write_file: Mock<(PathBuf, Vec), Result<(), FakeError>>, 97 | pub overwrite_file: Mock<(PathBuf, Vec), Result<(), FakeError>>, 98 | pub read_file: Mock<(PathBuf), Result, FakeError>>, 99 | pub read_file_to_string: Mock<(PathBuf), Result>, 100 | pub read_file_into: Mock<(PathBuf, Vec), Result>, 101 | pub create_file: Mock<(PathBuf, Vec), Result<(), FakeError>>, 102 | pub remove_file: Mock<(PathBuf), Result<(), FakeError>>, 103 | pub copy_file: Mock<(PathBuf, PathBuf), Result<(), FakeError>>, 104 | 105 | pub rename: Mock<(PathBuf, PathBuf), Result<(), FakeError>>, 106 | 107 | pub readonly: Mock<(PathBuf), Result>, 108 | pub set_readonly: Mock<(PathBuf, bool), Result<(), FakeError>>, 109 | 110 | pub len: Mock<(PathBuf), u64>, 111 | } 112 | 113 | impl MockFileSystem { 114 | pub fn new() -> Self { 115 | MockFileSystem { 116 | current_dir: Mock::new(Ok(PathBuf::new())), 117 | set_current_dir: Mock::new(Ok(())), 118 | 119 | is_dir: Mock::new(true), 120 | is_file: Mock::new(true), 121 | 122 | create_dir: Mock::new(Ok(())), 123 | create_dir_all: Mock::new(Ok(())), 124 | remove_dir: Mock::new(Ok(())), 125 | remove_dir_all: Mock::new(Ok(())), 126 | read_dir: Mock::new(Ok(vec![])), 127 | 128 | write_file: Mock::new(Ok(())), 129 | overwrite_file: Mock::new(Ok(())), 130 | read_file: Mock::new(Ok(vec![])), 131 | read_file_to_string: Mock::new(Ok(String::new())), 132 | read_file_into: Mock::new(Ok(0)), 133 | create_file: Mock::new(Ok(())), 134 | remove_file: Mock::new(Ok(())), 135 | copy_file: Mock::new(Ok(())), 136 | 137 | rename: Mock::new(Ok(())), 138 | 139 | readonly: Mock::new(Ok(false)), 140 | set_readonly: Mock::new(Ok(())), 141 | 142 | len: Mock::new(u64::default()), 143 | } 144 | } 145 | } 146 | 147 | impl Default for MockFileSystem { 148 | fn default() -> Self { 149 | Self::new() 150 | } 151 | } 152 | 153 | impl FileSystem for MockFileSystem { 154 | type DirEntry = DirEntry; 155 | type ReadDir = ReadDir; 156 | 157 | fn current_dir(&self) -> Result { 158 | self.current_dir.call(()).map_err(Error::from) 159 | } 160 | 161 | fn set_current_dir>(&self, path: P) -> Result<(), Error> { 162 | self.set_current_dir 163 | .call(path.as_ref().to_path_buf()) 164 | .map_err(Error::from) 165 | } 166 | 167 | fn is_dir>(&self, path: P) -> bool { 168 | self.is_dir.call(path.as_ref().to_path_buf()) 169 | } 170 | 171 | fn is_file>(&self, path: P) -> bool { 172 | self.is_file.call(path.as_ref().to_path_buf()) 173 | } 174 | 175 | fn create_dir>(&self, path: P) -> Result<(), Error> { 176 | self.create_dir 177 | .call(path.as_ref().to_path_buf()) 178 | .map_err(Error::from) 179 | } 180 | 181 | fn create_dir_all>(&self, path: P) -> Result<(), Error> { 182 | self.create_dir_all 183 | .call(path.as_ref().to_path_buf()) 184 | .map_err(Error::from) 185 | } 186 | 187 | fn remove_dir>(&self, path: P) -> Result<(), Error> { 188 | self.remove_dir 189 | .call(path.as_ref().to_path_buf()) 190 | .map_err(Error::from) 191 | } 192 | 193 | fn remove_dir_all>(&self, path: P) -> Result<(), Error> { 194 | self.remove_dir_all 195 | .call(path.as_ref().to_path_buf()) 196 | .map_err(Error::from) 197 | } 198 | 199 | fn read_dir>(&self, path: P) -> Result { 200 | self.read_dir 201 | .call(path.as_ref().to_path_buf()) 202 | .map(|entries| { 203 | let entries: Vec> = entries 204 | .into_iter() 205 | .map(|e| e.map_err(Error::from)) 206 | .collect(); 207 | 208 | ReadDir(entries.into_iter()) 209 | }) 210 | .map_err(Error::from) 211 | } 212 | 213 | fn write_file(&self, path: P, buf: B) -> Result<(), Error> 214 | where 215 | P: AsRef, 216 | B: AsRef<[u8]>, 217 | { 218 | self.write_file 219 | .call((path.as_ref().to_path_buf(), buf.as_ref().to_vec())) 220 | .map_err(Error::from) 221 | } 222 | 223 | fn overwrite_file(&self, path: P, buf: B) -> Result<(), Error> 224 | where 225 | P: AsRef, 226 | B: AsRef<[u8]>, 227 | { 228 | self.overwrite_file 229 | .call((path.as_ref().to_path_buf(), buf.as_ref().to_vec())) 230 | .map_err(Error::from) 231 | } 232 | 233 | fn read_file>(&self, path: P) -> Result, Error> { 234 | self.read_file 235 | .call(path.as_ref().to_path_buf()) 236 | .map_err(Error::from) 237 | } 238 | 239 | fn read_file_to_string>(&self, path: P) -> Result { 240 | self.read_file_to_string 241 | .call(path.as_ref().to_path_buf()) 242 | .map_err(Error::from) 243 | } 244 | 245 | fn read_file_into(&self, path: P, mut buf: B) -> Result 246 | where 247 | P: AsRef, 248 | B: AsMut>, 249 | { 250 | self.read_file_into 251 | .call((path.as_ref().to_path_buf(), buf.as_mut().clone())) 252 | .map_err(Error::from) 253 | } 254 | 255 | fn create_file(&self, path: P, buf: B) -> Result<(), Error> 256 | where 257 | P: AsRef, 258 | B: AsRef<[u8]>, 259 | { 260 | self.create_file 261 | .call((path.as_ref().to_path_buf(), buf.as_ref().to_vec())) 262 | .map_err(Error::from) 263 | } 264 | 265 | fn remove_file>(&self, path: P) -> Result<(), Error> { 266 | self.remove_file 267 | .call(path.as_ref().to_path_buf()) 268 | .map_err(Error::from) 269 | } 270 | 271 | fn copy_file(&self, from: P, to: Q) -> Result<(), Error> 272 | where 273 | P: AsRef, 274 | Q: AsRef, 275 | { 276 | self.copy_file 277 | .call((from.as_ref().to_path_buf(), to.as_ref().to_path_buf())) 278 | .map_err(Error::from) 279 | } 280 | 281 | fn rename(&self, from: P, to: Q) -> Result<(), Error> 282 | where 283 | P: AsRef, 284 | Q: AsRef, 285 | { 286 | self.rename 287 | .call((from.as_ref().to_path_buf(), to.as_ref().to_path_buf())) 288 | .map_err(Error::from) 289 | } 290 | 291 | fn readonly>(&self, path: P) -> Result { 292 | self.readonly 293 | .call(path.as_ref().to_path_buf()) 294 | .map_err(Error::from) 295 | } 296 | 297 | fn set_readonly>(&self, path: P, readonly: bool) -> Result<(), Error> { 298 | self.set_readonly 299 | .call((path.as_ref().to_path_buf(), readonly)) 300 | .map_err(Error::from) 301 | } 302 | 303 | fn len>(&self, path: P) -> u64 { 304 | self.len.call(path.as_ref().to_path_buf()) 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/os.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::OsString; 3 | use std::fs::{self, File, OpenOptions, Permissions}; 4 | use std::io::{Read, Result, Write}; 5 | #[cfg(unix)] 6 | use std::os::unix::fs::PermissionsExt; 7 | use std::path::{Path, PathBuf}; 8 | 9 | #[cfg(feature = "temp")] 10 | use tempdir; 11 | 12 | #[cfg(unix)] 13 | use UnixFileSystem; 14 | use {DirEntry, FileSystem, ReadDir}; 15 | #[cfg(feature = "temp")] 16 | use {TempDir, TempFileSystem}; 17 | 18 | /// Tracks a temporary directory that will be deleted once the struct goes out of scope. 19 | /// 20 | /// This is a wrapper around a [`TempDir`]. 21 | /// 22 | /// [`TempDir`]: https://doc.rust-lang.org/tempdir/tempdir/struct.TempDir.html 23 | #[cfg(feature = "temp")] 24 | #[derive(Debug)] 25 | pub struct OsTempDir(tempdir::TempDir); 26 | 27 | #[cfg(feature = "temp")] 28 | impl TempDir for OsTempDir { 29 | fn path(&self) -> &Path { 30 | self.0.path() 31 | } 32 | } 33 | 34 | /// An implementation of `FileSystem` that interacts with the actual operating system's file system. 35 | /// 36 | /// This is primarily a wrapper for [`fs`] methods. 37 | /// 38 | /// [`fs`]: https://doc.rust-lang.org/std/fs/index.html 39 | #[derive(Clone, Debug, Default)] 40 | pub struct OsFileSystem {} 41 | 42 | impl OsFileSystem { 43 | pub fn new() -> Self { 44 | OsFileSystem {} 45 | } 46 | } 47 | 48 | impl FileSystem for OsFileSystem { 49 | type DirEntry = fs::DirEntry; 50 | type ReadDir = fs::ReadDir; 51 | 52 | fn current_dir(&self) -> Result { 53 | env::current_dir() 54 | } 55 | 56 | fn set_current_dir>(&self, path: P) -> Result<()> { 57 | env::set_current_dir(path) 58 | } 59 | 60 | fn is_dir>(&self, path: P) -> bool { 61 | path.as_ref().is_dir() 62 | } 63 | 64 | fn is_file>(&self, path: P) -> bool { 65 | path.as_ref().is_file() 66 | } 67 | 68 | fn create_dir>(&self, path: P) -> Result<()> { 69 | fs::create_dir(path) 70 | } 71 | 72 | fn create_dir_all>(&self, path: P) -> Result<()> { 73 | fs::create_dir_all(path) 74 | } 75 | 76 | fn remove_dir>(&self, path: P) -> Result<()> { 77 | fs::remove_dir(path) 78 | } 79 | 80 | fn remove_dir_all>(&self, path: P) -> Result<()> { 81 | fs::remove_dir_all(path) 82 | } 83 | 84 | fn read_dir>(&self, path: P) -> Result { 85 | fs::read_dir(path) 86 | } 87 | 88 | fn write_file(&self, path: P, buf: B) -> Result<()> 89 | where 90 | P: AsRef, 91 | B: AsRef<[u8]>, 92 | { 93 | let mut file = File::create(path)?; 94 | file.write_all(buf.as_ref()) 95 | } 96 | 97 | fn overwrite_file(&self, path: P, buf: B) -> Result<()> 98 | where 99 | P: AsRef, 100 | B: AsRef<[u8]>, 101 | { 102 | let mut file = OpenOptions::new().write(true).truncate(true).open(path)?; 103 | file.write_all(buf.as_ref()) 104 | } 105 | 106 | fn read_file>(&self, path: P) -> Result> { 107 | let mut contents = Vec::::new(); 108 | let mut file = File::open(path)?; 109 | 110 | file.read_to_end(&mut contents)?; 111 | 112 | Ok(contents) 113 | } 114 | 115 | fn read_file_into(&self, path: P, mut buf: B) -> Result 116 | where 117 | P: AsRef, 118 | B: AsMut>, 119 | { 120 | let mut file = File::open(path)?; 121 | file.read_to_end(buf.as_mut()) 122 | } 123 | 124 | fn read_file_to_string>(&self, path: P) -> Result { 125 | let mut contents = String::new(); 126 | let mut file = File::open(path)?; 127 | 128 | file.read_to_string(&mut contents)?; 129 | 130 | Ok(contents) 131 | } 132 | 133 | fn create_file(&self, path: P, buf: B) -> Result<()> 134 | where 135 | P: AsRef, 136 | B: AsRef<[u8]>, 137 | { 138 | let mut file = OpenOptions::new().write(true).create_new(true).open(path)?; 139 | 140 | file.write_all(buf.as_ref()) 141 | } 142 | 143 | fn remove_file>(&self, path: P) -> Result<()> { 144 | fs::remove_file(path) 145 | } 146 | 147 | fn copy_file(&self, from: P, to: Q) -> Result<()> 148 | where 149 | P: AsRef, 150 | Q: AsRef, 151 | { 152 | fs::copy(from, to).and(Ok(())) 153 | } 154 | 155 | fn rename(&self, from: P, to: Q) -> Result<()> 156 | where 157 | P: AsRef, 158 | Q: AsRef, 159 | { 160 | fs::rename(from, to) 161 | } 162 | 163 | fn readonly>(&self, path: P) -> Result { 164 | permissions(path.as_ref()).map(|p| p.readonly()) 165 | } 166 | 167 | fn set_readonly>(&self, path: P, readonly: bool) -> Result<()> { 168 | let mut permissions = permissions(path.as_ref())?; 169 | 170 | permissions.set_readonly(readonly); 171 | 172 | fs::set_permissions(path, permissions) 173 | } 174 | 175 | fn len>(&self, path: P) -> u64 { 176 | fs::metadata(path.as_ref()).map(|md| md.len()).unwrap_or(0) 177 | } 178 | } 179 | 180 | impl DirEntry for fs::DirEntry { 181 | fn file_name(&self) -> OsString { 182 | self.file_name() 183 | } 184 | 185 | fn path(&self) -> PathBuf { 186 | self.path() 187 | } 188 | } 189 | 190 | impl ReadDir for fs::ReadDir {} 191 | 192 | #[cfg(unix)] 193 | impl UnixFileSystem for OsFileSystem { 194 | fn mode>(&self, path: P) -> Result { 195 | permissions(path.as_ref()).map(|p| p.mode()) 196 | } 197 | 198 | fn set_mode>(&self, path: P, mode: u32) -> Result<()> { 199 | let mut permissions = permissions(path.as_ref())?; 200 | 201 | permissions.set_mode(mode); 202 | 203 | fs::set_permissions(path, permissions) 204 | } 205 | } 206 | 207 | #[cfg(feature = "temp")] 208 | impl TempFileSystem for OsFileSystem { 209 | type TempDir = OsTempDir; 210 | 211 | fn temp_dir>(&self, prefix: S) -> Result { 212 | tempdir::TempDir::new(prefix.as_ref()).map(OsTempDir) 213 | } 214 | } 215 | 216 | fn permissions(path: &Path) -> Result { 217 | let metadata = fs::metadata(path)?; 218 | 219 | Ok(metadata.permissions()) 220 | } 221 | -------------------------------------------------------------------------------- /tests/fs.rs: -------------------------------------------------------------------------------- 1 | extern crate filesystem; 2 | 3 | use std::io::ErrorKind; 4 | use std::path::{Path, PathBuf}; 5 | 6 | #[cfg(unix)] 7 | use filesystem::UnixFileSystem; 8 | use filesystem::{DirEntry, FakeFileSystem, FileSystem, OsFileSystem, TempDir, TempFileSystem}; 9 | 10 | macro_rules! make_test { 11 | ($test:ident, $fs:expr) => { 12 | #[test] 13 | fn $test() { 14 | let fs = $fs(); 15 | let temp_dir = fs.temp_dir("test").unwrap(); 16 | 17 | super::$test(&fs, temp_dir.path()); 18 | } 19 | }; 20 | } 21 | 22 | macro_rules! test_fs { 23 | ($name:ident, $fs:expr) => { 24 | mod $name { 25 | use super::*; 26 | 27 | make_test!(set_current_dir_fails_if_node_does_not_exists, $fs); 28 | make_test!(set_current_dir_fails_if_node_is_a_file, $fs); 29 | 30 | make_test!(is_dir_returns_true_if_node_is_dir, $fs); 31 | make_test!(is_dir_returns_false_if_node_is_file, $fs); 32 | make_test!(is_dir_returns_false_if_node_does_not_exist, $fs); 33 | 34 | make_test!(is_file_returns_true_if_node_is_file, $fs); 35 | make_test!(is_file_returns_false_if_node_is_dir, $fs); 36 | make_test!(is_file_returns_false_if_node_does_not_exist, $fs); 37 | 38 | make_test!(create_dir_creates_new_dir, $fs); 39 | make_test!(create_dir_fails_if_dir_already_exists, $fs); 40 | make_test!(create_dir_fails_if_parent_does_not_exist, $fs); 41 | 42 | make_test!(create_dir_all_creates_dirs_in_path, $fs); 43 | make_test!(create_dir_all_still_succeeds_if_any_dir_already_exists, $fs); 44 | 45 | make_test!(remove_dir_deletes_dir, $fs); 46 | make_test!(remove_dir_does_not_affect_parent, $fs); 47 | make_test!(remove_dir_fails_if_node_does_not_exist, $fs); 48 | make_test!(remove_dir_fails_if_node_is_a_file, $fs); 49 | make_test!(remove_dir_fails_if_dir_is_not_empty, $fs); 50 | 51 | make_test!(remove_dir_all_removes_dir_and_contents, $fs); 52 | make_test!(remove_dir_all_fails_if_node_is_a_file, $fs); 53 | #[cfg(unix)] 54 | make_test!( 55 | remove_dir_all_removes_dir_and_contents_if_descendant_not_writable, 56 | $fs 57 | ); 58 | #[cfg(unix)] 59 | make_test!( 60 | remove_dir_all_removes_dir_and_contents_if_descendant_not_executable, 61 | $fs 62 | ); 63 | #[cfg(unix)] 64 | make_test!(remove_dir_all_fails_if_descendant_not_readable, $fs); 65 | 66 | make_test!(read_dir_returns_dir_entries, $fs); 67 | make_test!(read_dir_fails_if_node_does_not_exist, $fs); 68 | make_test!(read_dir_fails_if_node_is_a_file, $fs); 69 | 70 | make_test!(write_file_writes_to_new_file, $fs); 71 | make_test!(write_file_overwrites_contents_of_existing_file, $fs); 72 | make_test!(write_file_fails_if_file_is_readonly, $fs); 73 | make_test!(write_file_fails_if_node_is_a_directory, $fs); 74 | 75 | make_test!(overwrite_file_overwrites_contents_of_existing_file, $fs); 76 | make_test!(overwrite_file_fails_if_node_does_not_exist, $fs); 77 | make_test!(overwrite_file_fails_if_file_is_readonly, $fs); 78 | make_test!(overwrite_file_fails_if_node_is_a_directory, $fs); 79 | 80 | make_test!(read_file_returns_contents_as_bytes, $fs); 81 | make_test!(read_file_fails_if_file_does_not_exist, $fs); 82 | 83 | make_test!(read_file_to_string_returns_contents_as_string, $fs); 84 | make_test!(read_file_to_string_fails_if_file_does_not_exist, $fs); 85 | make_test!(read_file_to_string_fails_if_contents_are_not_utf8, $fs); 86 | 87 | make_test!(read_file_into_writes_bytes_to_buffer, $fs); 88 | make_test!(read_file_into_fails_if_file_does_not_exist, $fs); 89 | 90 | make_test!(create_file_writes_to_new_file, $fs); 91 | make_test!(create_file_fails_if_file_already_exists, $fs); 92 | 93 | make_test!(remove_file_removes_a_file, $fs); 94 | make_test!(remove_file_fails_if_file_does_not_exist, $fs); 95 | make_test!(remove_file_fails_if_node_is_a_directory, $fs); 96 | 97 | make_test!(copy_file_copies_a_file, $fs); 98 | make_test!(copy_file_overwrites_destination_file, $fs); 99 | make_test!(copy_file_fails_if_original_file_does_not_exist, $fs); 100 | make_test!(copy_file_fails_if_destination_file_is_readonly, $fs); 101 | make_test!(copy_file_fails_if_original_node_is_directory, $fs); 102 | make_test!(copy_file_fails_if_destination_node_is_directory, $fs); 103 | 104 | make_test!(rename_renames_a_file, $fs); 105 | make_test!(rename_renames_a_directory, $fs); 106 | make_test!(rename_overwrites_destination_file, $fs); 107 | make_test!(rename_overwrites_empty_destination_directory, $fs); 108 | make_test!(rename_renames_all_descendants, $fs); 109 | make_test!(rename_fails_if_original_path_does_not_exist, $fs); 110 | make_test!( 111 | rename_fails_if_original_and_destination_are_different_types, 112 | $fs 113 | ); 114 | make_test!(rename_fails_if_destination_directory_is_not_empty, $fs); 115 | 116 | make_test!(readonly_returns_write_permission, $fs); 117 | make_test!(readonly_fails_if_node_does_not_exist, $fs); 118 | 119 | make_test!(set_readonly_toggles_write_permission_of_file, $fs); 120 | make_test!(set_readonly_toggles_write_permission_of_dir, $fs); 121 | make_test!(set_readonly_fails_if_node_does_not_exist, $fs); 122 | 123 | make_test!(len_returns_size_of_file, $fs); 124 | make_test!(len_returns_size_of_directory, $fs); 125 | make_test!(len_returns_0_if_node_does_not_exist, $fs); 126 | 127 | #[cfg(unix)] 128 | make_test!(mode_returns_permissions, $fs); 129 | #[cfg(unix)] 130 | make_test!(mode_fails_if_node_does_not_exist, $fs); 131 | 132 | #[cfg(unix)] 133 | make_test!(set_mode_sets_permissions, $fs); 134 | #[cfg(unix)] 135 | make_test!(set_mode_fails_if_node_does_not_exist, $fs); 136 | 137 | make_test!(temp_dir_creates_tempdir, $fs); 138 | make_test!(temp_dir_creates_unique_dir, $fs); 139 | } 140 | }; 141 | } 142 | 143 | test_fs!(os, OsFileSystem::new); 144 | test_fs!(fake, FakeFileSystem::new); 145 | 146 | fn set_current_dir_fails_if_node_does_not_exists(fs: &T, parent: &Path) { 147 | let path = parent.join("does_not_exist"); 148 | 149 | let result = fs.set_current_dir(path); 150 | 151 | assert!(result.is_err()); 152 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 153 | } 154 | 155 | fn set_current_dir_fails_if_node_is_a_file(fs: &T, parent: &Path) { 156 | let path = parent.join("file"); 157 | 158 | fs.create_file(&path, "").unwrap(); 159 | 160 | let result = fs.set_current_dir(path); 161 | 162 | assert!(result.is_err()); 163 | assert_eq!(result.unwrap_err().kind(), ErrorKind::Other); 164 | } 165 | 166 | fn is_dir_returns_true_if_node_is_dir(fs: &T, parent: &Path) { 167 | let path = parent.join("new_dir"); 168 | 169 | fs.create_dir(&path).unwrap(); 170 | 171 | assert!(fs.is_dir(&path)); 172 | } 173 | 174 | fn is_dir_returns_false_if_node_is_file(fs: &T, parent: &Path) { 175 | let path = parent.join("new_dir"); 176 | 177 | fs.create_file(&path, "").unwrap(); 178 | 179 | assert!(!fs.is_dir(&path)); 180 | } 181 | 182 | fn is_dir_returns_false_if_node_does_not_exist(fs: &T, parent: &Path) { 183 | assert!(!fs.is_dir(parent.join("does_not_exist"))); 184 | } 185 | 186 | fn is_file_returns_true_if_node_is_file(fs: &T, parent: &Path) { 187 | let path = parent.join("new_file"); 188 | 189 | fs.create_file(&path, "").unwrap(); 190 | 191 | assert!(fs.is_file(&path)); 192 | } 193 | 194 | fn is_file_returns_false_if_node_is_dir(fs: &T, parent: &Path) { 195 | let path = parent.join("new_dir"); 196 | 197 | fs.create_dir(&path).unwrap(); 198 | 199 | assert!(!fs.is_file(&path)); 200 | } 201 | 202 | fn is_file_returns_false_if_node_does_not_exist(fs: &T, parent: &Path) { 203 | assert!(!fs.is_file(parent.join("does_not_exist"))); 204 | } 205 | 206 | fn create_dir_creates_new_dir(fs: &T, parent: &Path) { 207 | let path = parent.join("new_dir"); 208 | 209 | let result = fs.create_dir(&path); 210 | 211 | assert!(result.is_ok()); 212 | assert!(fs.is_dir(path)); 213 | } 214 | 215 | fn create_dir_fails_if_dir_already_exists(fs: &T, parent: &Path) { 216 | let path = parent.join("new_dir"); 217 | 218 | fs.create_dir(&path).unwrap(); 219 | 220 | let result = fs.create_dir(&path); 221 | 222 | assert!(result.is_err()); 223 | assert_eq!(result.unwrap_err().kind(), ErrorKind::AlreadyExists); 224 | } 225 | 226 | fn create_dir_fails_if_parent_does_not_exist(fs: &T, parent: &Path) { 227 | let path = parent.join("parent/new_dir"); 228 | 229 | let result = fs.create_dir(&path); 230 | 231 | assert!(result.is_err()); 232 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 233 | } 234 | 235 | fn create_dir_all_creates_dirs_in_path(fs: &T, parent: &Path) { 236 | let result = fs.create_dir_all(parent.join("a/b/c")); 237 | 238 | assert!(result.is_ok()); 239 | assert!(fs.is_dir(parent.join("a"))); 240 | assert!(fs.is_dir(parent.join("a/b"))); 241 | assert!(fs.is_dir(parent.join("a/b/c"))); 242 | } 243 | 244 | fn create_dir_all_still_succeeds_if_any_dir_already_exists(fs: &T, parent: &Path) { 245 | fs.create_dir_all(parent.join("a/b")).unwrap(); 246 | 247 | let result = fs.create_dir_all(parent.join("a/b/c")); 248 | 249 | assert!(result.is_ok()); 250 | assert!(fs.is_dir(parent.join("a"))); 251 | assert!(fs.is_dir(parent.join("a/b"))); 252 | assert!(fs.is_dir(parent.join("a/b/c"))); 253 | } 254 | 255 | fn remove_dir_deletes_dir(fs: &T, parent: &Path) { 256 | let path = parent.join("dir"); 257 | 258 | fs.create_dir(&path).unwrap(); 259 | 260 | let result = fs.remove_dir(&path); 261 | 262 | assert!(result.is_ok()); 263 | assert!(!fs.is_dir(&path)); 264 | } 265 | 266 | fn remove_dir_does_not_affect_parent(fs: &T, parent: &Path) { 267 | let path = parent.join("parent/child"); 268 | 269 | fs.create_dir_all(&path).unwrap(); 270 | 271 | let result = fs.remove_dir(&path); 272 | 273 | assert!(result.is_ok()); 274 | assert!(fs.is_dir(parent.join("parent"))); 275 | assert!(!fs.is_dir(parent.join("child"))); 276 | } 277 | 278 | fn remove_dir_fails_if_node_does_not_exist(fs: &T, parent: &Path) { 279 | let result = fs.remove_dir(parent.join("does_not_exist")); 280 | 281 | assert!(result.is_err()); 282 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 283 | } 284 | 285 | fn remove_dir_fails_if_node_is_a_file(fs: &T, parent: &Path) { 286 | let path = parent.join("file"); 287 | 288 | fs.create_file(&path, "").unwrap(); 289 | 290 | let result = fs.remove_dir(&path); 291 | 292 | assert!(result.is_err()); 293 | assert_eq!(result.unwrap_err().kind(), ErrorKind::Other); 294 | assert!(fs.is_file(&path)); 295 | } 296 | 297 | fn remove_dir_fails_if_dir_is_not_empty(fs: &T, parent: &Path) { 298 | let path = parent.join("dir"); 299 | let child = path.join("file"); 300 | 301 | fs.create_dir(&path).unwrap(); 302 | fs.create_file(&child, "").unwrap(); 303 | 304 | let result = fs.remove_dir(&path); 305 | 306 | assert!(result.is_err()); 307 | assert_eq!(result.unwrap_err().kind(), ErrorKind::Other); 308 | assert!(fs.is_dir(&path)); 309 | assert!(fs.is_file(&child)); 310 | } 311 | 312 | fn remove_dir_all_removes_dir_and_contents(fs: &T, parent: &Path) { 313 | let path = parent.join("dir"); 314 | let child = path.join("file"); 315 | 316 | fs.create_dir(&path).unwrap(); 317 | fs.create_file(&child, "").unwrap(); 318 | 319 | let result = fs.remove_dir_all(&path); 320 | 321 | assert!(result.is_ok()); 322 | assert!(!fs.is_dir(&path)); 323 | assert!(!fs.is_file(&child)); 324 | assert!(fs.is_dir(parent)); 325 | } 326 | 327 | fn remove_dir_all_fails_if_node_is_a_file(fs: &T, parent: &Path) { 328 | let path = parent.join("file"); 329 | 330 | fs.create_file(&path, "").unwrap(); 331 | 332 | let result = fs.remove_dir_all(&path); 333 | 334 | assert!(result.is_err()); 335 | assert_eq!(result.unwrap_err().kind(), ErrorKind::Other); 336 | assert!(fs.is_file(&path)); 337 | } 338 | 339 | #[cfg(unix)] 340 | fn remove_dir_all_removes_dir_and_contents_if_descendant_not_writable< 341 | T: FileSystem + UnixFileSystem, 342 | >( 343 | fs: &T, 344 | parent: &Path, 345 | ) { 346 | let mode = 0o555; 347 | 348 | let path = parent.join("dir"); 349 | let child = path.join("child"); 350 | 351 | fs.create_dir(&path).unwrap(); 352 | fs.create_dir(&child).unwrap(); 353 | 354 | fs.set_mode(&child, mode).unwrap(); 355 | 356 | let result = fs.remove_dir_all(&path); 357 | 358 | assert!(result.is_ok()); 359 | assert!(!fs.is_dir(&path)); 360 | assert!(!fs.is_dir(&child)); 361 | } 362 | 363 | #[cfg(unix)] 364 | fn remove_dir_all_removes_dir_and_contents_if_descendant_not_executable< 365 | T: FileSystem + UnixFileSystem, 366 | >( 367 | fs: &T, 368 | parent: &Path, 369 | ) { 370 | let mode = 0o666; 371 | 372 | let path = parent.join("dir"); 373 | let child = path.join("child"); 374 | 375 | fs.create_dir(&path).unwrap(); 376 | fs.create_dir(&child).unwrap(); 377 | 378 | fs.set_mode(&child, mode).unwrap(); 379 | 380 | let result = fs.remove_dir_all(&path); 381 | 382 | assert!(result.is_ok()); 383 | assert!(!fs.is_dir(&path)); 384 | assert!(!fs.is_dir(&child)); 385 | } 386 | 387 | #[cfg(unix)] 388 | fn remove_dir_all_fails_if_descendant_not_readable( 389 | fs: &T, 390 | parent: &Path, 391 | ) { 392 | let mode = 0o333; 393 | 394 | let path = parent.join("dir"); 395 | let child = path.join("child"); 396 | 397 | fs.create_dir(&path).unwrap(); 398 | fs.create_dir(&child).unwrap(); 399 | 400 | fs.set_mode(&child, mode).unwrap(); 401 | 402 | let result = fs.remove_dir_all(&path); 403 | 404 | assert!(result.is_err()); 405 | assert_eq!(result.unwrap_err().kind(), ErrorKind::PermissionDenied); 406 | assert!(fs.is_dir(&path)); 407 | assert!(fs.is_dir(&child)); 408 | } 409 | 410 | fn read_dir_returns_dir_entries(fs: &T, parent: &Path) { 411 | let file1 = parent.join("file1"); 412 | let file2 = parent.join("file2"); 413 | let dir1 = parent.join("dir1"); 414 | let dir2 = parent.join("dir2"); 415 | let file3 = dir1.join("file3"); 416 | let file4 = dir2.join("file4"); 417 | 418 | fs.create_file(&file1, "").unwrap(); 419 | fs.create_file(&file2, "").unwrap(); 420 | fs.create_dir(&dir1).unwrap(); 421 | fs.create_dir(&dir2).unwrap(); 422 | fs.create_file(&file3, "").unwrap(); 423 | fs.create_file(&file4, "").unwrap(); 424 | 425 | let result = fs.read_dir(parent); 426 | 427 | assert!(result.is_ok()); 428 | 429 | let mut entries: Vec = result.unwrap().map(|e| e.unwrap().path()).collect(); 430 | let expected_paths = &mut [file1, file2, dir1, dir2]; 431 | 432 | entries.sort(); 433 | expected_paths.sort(); 434 | 435 | assert_eq!(&entries, expected_paths); 436 | } 437 | 438 | fn read_dir_fails_if_node_does_not_exist(fs: &T, parent: &Path) { 439 | let path = parent.join("does_not_exist"); 440 | let result = fs.read_dir(&path); 441 | 442 | assert!(result.is_err()); 443 | 444 | match result { 445 | Ok(_) => panic!("should be an err"), 446 | Err(err) => assert_eq!(err.kind(), ErrorKind::NotFound), 447 | } 448 | } 449 | 450 | fn read_dir_fails_if_node_is_a_file(fs: &T, parent: &Path) { 451 | let path = parent.join("file"); 452 | 453 | fs.create_file(&path, "").unwrap(); 454 | 455 | let result = fs.read_dir(&path); 456 | 457 | assert!(result.is_err()); 458 | match result { 459 | Ok(_) => panic!("should be an err"), 460 | Err(err) => assert_eq!(err.kind(), ErrorKind::Other), 461 | } 462 | } 463 | 464 | fn write_file_writes_to_new_file(fs: &T, parent: &Path) { 465 | let path = parent.join("new_file"); 466 | let result = fs.write_file(&path, "new contents"); 467 | 468 | assert!(result.is_ok()); 469 | 470 | let contents = String::from_utf8(fs.read_file(path).unwrap()).unwrap(); 471 | 472 | assert_eq!(&contents, "new contents"); 473 | } 474 | 475 | fn write_file_overwrites_contents_of_existing_file(fs: &T, parent: &Path) { 476 | let path = parent.join("test_file"); 477 | 478 | fs.write_file(&path, "old contents").unwrap(); 479 | 480 | let result = fs.write_file(&path, "new contents"); 481 | 482 | assert!(result.is_ok()); 483 | 484 | let contents = String::from_utf8(fs.read_file(path).unwrap()).unwrap(); 485 | 486 | assert_eq!(&contents, "new contents"); 487 | } 488 | 489 | fn write_file_fails_if_file_is_readonly(fs: &T, parent: &Path) { 490 | let path = parent.join("test_file"); 491 | 492 | fs.create_file(&path, "").unwrap(); 493 | fs.set_readonly(&path, true).unwrap(); 494 | 495 | let result = fs.write_file(&path, "test contents"); 496 | 497 | assert!(result.is_err()); 498 | assert_eq!(result.unwrap_err().kind(), ErrorKind::PermissionDenied); 499 | } 500 | 501 | fn write_file_fails_if_node_is_a_directory(fs: &T, parent: &Path) { 502 | let path = parent.join("test_dir"); 503 | 504 | fs.create_dir(&path).unwrap(); 505 | 506 | let result = fs.write_file(&path, "test contents"); 507 | 508 | assert!(result.is_err()); 509 | assert_eq!(result.unwrap_err().kind(), ErrorKind::Other); 510 | } 511 | 512 | fn overwrite_file_overwrites_contents_of_existing_file(fs: &T, parent: &Path) { 513 | let path = parent.join("test_file"); 514 | 515 | fs.write_file(&path, "old contents").unwrap(); 516 | 517 | let result = fs.overwrite_file(&path, "new contents"); 518 | 519 | assert!(result.is_ok()); 520 | 521 | let contents = String::from_utf8(fs.read_file(path).unwrap()).unwrap(); 522 | 523 | assert_eq!(&contents, "new contents"); 524 | } 525 | 526 | fn overwrite_file_fails_if_node_does_not_exist(fs: &T, parent: &Path) { 527 | let path = parent.join("new_file"); 528 | let result = fs.overwrite_file(&path, "new contents"); 529 | 530 | assert!(result.is_err()); 531 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 532 | } 533 | 534 | fn overwrite_file_fails_if_file_is_readonly(fs: &T, parent: &Path) { 535 | let path = parent.join("test_file"); 536 | 537 | fs.create_file(&path, "").unwrap(); 538 | fs.set_readonly(&path, true).unwrap(); 539 | 540 | let result = fs.overwrite_file(&path, "test contents"); 541 | 542 | assert!(result.is_err()); 543 | assert_eq!(result.unwrap_err().kind(), ErrorKind::PermissionDenied); 544 | } 545 | 546 | fn overwrite_file_fails_if_node_is_a_directory(fs: &T, parent: &Path) { 547 | let path = parent.join("test_dir"); 548 | 549 | fs.create_dir(&path).unwrap(); 550 | 551 | let result = fs.overwrite_file(&path, "test contents"); 552 | 553 | assert!(result.is_err()); 554 | assert_eq!(result.unwrap_err().kind(), ErrorKind::Other); 555 | } 556 | 557 | fn read_file_returns_contents_as_bytes(fs: &T, parent: &Path) { 558 | let path = parent.join("test.txt"); 559 | 560 | fs.write_file(&path, "test text").unwrap(); 561 | 562 | let result = fs.read_file(&path); 563 | 564 | assert!(result.is_ok()); 565 | assert_eq!(result.unwrap(), br"test text"); 566 | } 567 | 568 | fn read_file_fails_if_file_does_not_exist(fs: &T, parent: &Path) { 569 | let path = parent.join("test.txt"); 570 | let result = fs.read_file(&path); 571 | 572 | assert!(result.is_err()); 573 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 574 | } 575 | 576 | fn read_file_to_string_returns_contents_as_string(fs: &T, parent: &Path) { 577 | let path = parent.join("test.txt"); 578 | 579 | fs.write_file(&path, "test text").unwrap(); 580 | 581 | let result = fs.read_file_to_string(&path); 582 | 583 | assert!(result.is_ok()); 584 | assert_eq!(&result.unwrap(), "test text"); 585 | } 586 | 587 | fn read_file_to_string_fails_if_file_does_not_exist(fs: &T, parent: &Path) { 588 | let path = parent.join("test.txt"); 589 | let result = fs.read_file_to_string(&path); 590 | 591 | assert!(result.is_err()); 592 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 593 | } 594 | 595 | fn read_file_to_string_fails_if_contents_are_not_utf8(fs: &T, parent: &Path) { 596 | let path = parent.join("test.txt"); 597 | 598 | fs.write_file(&path, &[0, 159, 146, 150]).unwrap(); 599 | 600 | let result = fs.read_file_to_string(&path); 601 | 602 | assert!(result.is_err()); 603 | assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidData); 604 | } 605 | 606 | fn read_file_into_writes_bytes_to_buffer(fs: &T, parent: &Path) { 607 | let path = parent.join("test.txt"); 608 | let text = "test text"; 609 | 610 | fs.write_file(&path, text).unwrap(); 611 | let mut buf = Vec::new(); 612 | 613 | let result = fs.read_file_into(&path, &mut buf); 614 | 615 | assert!(result.is_ok()); 616 | assert_eq!(result.unwrap(), text.as_bytes().len()); 617 | assert_eq!(buf, br"test text"); 618 | } 619 | 620 | fn read_file_into_fails_if_file_does_not_exist(fs: &T, parent: &Path) { 621 | let path = parent.join("test.txt"); 622 | 623 | let result = fs.read_file_into(&path, &mut Vec::new()); 624 | 625 | assert!(result.is_err()); 626 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 627 | } 628 | 629 | fn create_file_writes_to_new_file(fs: &T, parent: &Path) { 630 | let path = parent.join("test_file"); 631 | let result = fs.create_file(&path, "new contents"); 632 | 633 | assert!(result.is_ok()); 634 | 635 | let contents = String::from_utf8(fs.read_file(path).unwrap()).unwrap(); 636 | 637 | assert_eq!(&contents, "new contents"); 638 | } 639 | 640 | fn create_file_fails_if_file_already_exists(fs: &T, parent: &Path) { 641 | let path = parent.join("test_file"); 642 | 643 | fs.create_file(&path, "contents").unwrap(); 644 | 645 | let result = fs.create_file(&path, "new contents"); 646 | 647 | assert!(result.is_err()); 648 | assert_eq!(result.unwrap_err().kind(), ErrorKind::AlreadyExists); 649 | } 650 | 651 | fn remove_file_removes_a_file(fs: &T, parent: &Path) { 652 | let path = parent.join("test_file"); 653 | 654 | fs.create_file(&path, "").unwrap(); 655 | 656 | let result = fs.remove_file(&path); 657 | 658 | assert!(result.is_ok()); 659 | 660 | let result = fs.read_file(&path); 661 | 662 | assert!(result.is_err()); 663 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 664 | } 665 | 666 | fn remove_file_fails_if_file_does_not_exist(fs: &T, parent: &Path) { 667 | let result = fs.remove_file(parent.join("does_not_exist")); 668 | 669 | assert!(result.is_err()); 670 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 671 | } 672 | 673 | fn remove_file_fails_if_node_is_a_directory(fs: &T, parent: &Path) { 674 | let path = parent.join("test_dir"); 675 | 676 | fs.create_dir(&path).unwrap(); 677 | 678 | let result = fs.remove_file(&path); 679 | 680 | assert!(result.is_err()); 681 | assert_eq!(result.unwrap_err().kind(), ErrorKind::Other); 682 | } 683 | 684 | fn copy_file_copies_a_file(fs: &T, parent: &Path) { 685 | let from = parent.join("from"); 686 | let to = parent.join("to"); 687 | 688 | fs.create_file(&from, "test").unwrap(); 689 | 690 | let result = fs.copy_file(&from, &to); 691 | 692 | assert!(result.is_ok()); 693 | 694 | let result = fs.read_file(&to); 695 | 696 | assert!(result.is_ok()); 697 | assert_eq!(&result.unwrap(), b"test"); 698 | } 699 | 700 | fn copy_file_overwrites_destination_file(fs: &T, parent: &Path) { 701 | let from = parent.join("from"); 702 | let to = parent.join("to"); 703 | 704 | fs.create_file(&from, "expected").unwrap(); 705 | fs.create_file(&to, "should be overwritten").unwrap(); 706 | 707 | let result = fs.copy_file(&from, &to); 708 | 709 | assert!(result.is_ok()); 710 | 711 | let result = fs.read_file(&to); 712 | 713 | assert!(result.is_ok()); 714 | assert_eq!(result.unwrap(), b"expected"); 715 | } 716 | 717 | fn copy_file_fails_if_original_file_does_not_exist(fs: &T, parent: &Path) { 718 | let from = parent.join("from"); 719 | let to = parent.join("to"); 720 | 721 | let result = fs.copy_file(&from, &to); 722 | 723 | assert!(result.is_err()); 724 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 725 | assert!(!fs.is_file(&to)); 726 | } 727 | 728 | fn copy_file_fails_if_destination_file_is_readonly(fs: &T, parent: &Path) { 729 | let from = parent.join("from"); 730 | let to = parent.join("to"); 731 | 732 | fs.create_file(&from, "test").unwrap(); 733 | fs.create_file(&to, "").unwrap(); 734 | fs.set_readonly(&to, true).unwrap(); 735 | 736 | let result = fs.copy_file(&from, &to); 737 | 738 | assert!(result.is_err()); 739 | assert_eq!(result.unwrap_err().kind(), ErrorKind::PermissionDenied); 740 | } 741 | 742 | fn copy_file_fails_if_original_node_is_directory(fs: &T, parent: &Path) { 743 | let from = parent.join("from"); 744 | let to = parent.join("to"); 745 | 746 | fs.create_dir(&from).unwrap(); 747 | 748 | let result = fs.copy_file(&from, &to); 749 | 750 | assert!(result.is_err()); 751 | assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidInput); 752 | } 753 | 754 | fn copy_file_fails_if_destination_node_is_directory(fs: &T, parent: &Path) { 755 | let from = parent.join("from"); 756 | let to = parent.join("to"); 757 | 758 | fs.create_file(&from, "").unwrap(); 759 | fs.create_dir(&to).unwrap(); 760 | 761 | let result = fs.copy_file(&from, &to); 762 | 763 | assert!(result.is_err()); 764 | assert_eq!(result.unwrap_err().kind(), ErrorKind::Other); 765 | } 766 | 767 | fn rename_renames_a_file(fs: &T, parent: &Path) { 768 | let from = parent.join("from"); 769 | let to = parent.join("to"); 770 | 771 | fs.create_file(&from, "contents").unwrap(); 772 | 773 | let result = fs.rename(&from, &to); 774 | 775 | assert!(result.is_ok()); 776 | assert!(!fs.is_file(&from)); 777 | 778 | let result = fs.read_file_to_string(&to); 779 | 780 | assert!(result.is_ok()); 781 | assert_eq!(result.unwrap(), "contents"); 782 | } 783 | 784 | fn rename_renames_a_directory(fs: &T, parent: &Path) { 785 | let from = parent.join("from"); 786 | let to = parent.join("to"); 787 | let child = from.join("child"); 788 | 789 | fs.create_dir(&from).unwrap(); 790 | fs.create_file(&child, "child").unwrap(); 791 | 792 | let result = fs.rename(&from, &to); 793 | 794 | assert!(result.is_ok()); 795 | assert!(!fs.is_dir(&from)); 796 | 797 | let result = fs.read_file_to_string(to.join("child")); 798 | 799 | assert!(result.is_ok()); 800 | assert_eq!(result.unwrap(), "child"); 801 | } 802 | 803 | fn rename_overwrites_destination_file(fs: &T, parent: &Path) { 804 | let from = parent.join("from"); 805 | let to = parent.join("to"); 806 | 807 | fs.create_file(&from, "from").unwrap(); 808 | fs.create_file(&to, "to").unwrap(); 809 | 810 | let result = fs.rename(&from, &to); 811 | 812 | assert!(result.is_ok()); 813 | assert!(!fs.is_file(&from)); 814 | 815 | let result = fs.read_file_to_string(&to); 816 | 817 | assert!(result.is_ok()); 818 | assert_eq!(result.unwrap(), "from"); 819 | } 820 | 821 | fn rename_overwrites_empty_destination_directory(fs: &T, parent: &Path) { 822 | let from = parent.join("from"); 823 | let to = parent.join("to"); 824 | let child = from.join("child"); 825 | 826 | fs.create_dir(&from).unwrap(); 827 | fs.create_dir(&to).unwrap(); 828 | fs.create_file(&child, "child").unwrap(); 829 | 830 | let result = fs.rename(&from, &to); 831 | 832 | assert!(result.is_ok(), "err: {:?}", result); 833 | assert!(!fs.is_dir(&from)); 834 | 835 | let result = fs.read_file_to_string(to.join("child")); 836 | 837 | assert!(result.is_ok()); 838 | assert_eq!(result.unwrap(), "child"); 839 | } 840 | 841 | fn rename_renames_all_descendants(fs: &T, parent: &Path) { 842 | let from = parent.join("from"); 843 | let to = parent.join("to"); 844 | let child_file = from.join("child_file"); 845 | let child_dir = from.join("child_dir"); 846 | let grandchild = child_dir.join("grandchild"); 847 | 848 | fs.create_dir(&from).unwrap(); 849 | fs.create_file(&child_file, "child_file").unwrap(); 850 | fs.create_dir(&child_dir).unwrap(); 851 | fs.create_file(&grandchild, "grandchild").unwrap(); 852 | 853 | let result = fs.rename(&from, &to); 854 | 855 | assert!(result.is_ok()); 856 | assert!(!fs.is_dir(&from)); 857 | 858 | let result = fs.read_file_to_string(to.join("child_file")); 859 | assert!(result.is_ok()); 860 | assert_eq!(result.unwrap(), "child_file"); 861 | 862 | let result = fs.read_file_to_string(to.join("child_dir").join("grandchild")); 863 | assert!(result.is_ok()); 864 | assert_eq!(result.unwrap(), "grandchild"); 865 | } 866 | 867 | fn rename_fails_if_original_path_does_not_exist(fs: &T, parent: &Path) { 868 | let from = parent.join("from"); 869 | let to = parent.join("to"); 870 | 871 | let result = fs.rename(&from, &to); 872 | 873 | assert!(result.is_err()); 874 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 875 | } 876 | 877 | fn rename_fails_if_original_and_destination_are_different_types( 878 | fs: &T, 879 | parent: &Path, 880 | ) { 881 | let file = parent.join("file"); 882 | let dir = parent.join("dir"); 883 | 884 | fs.create_file(&file, "").unwrap(); 885 | fs.create_dir(&dir).unwrap(); 886 | 887 | let result = fs.rename(&file, &dir); 888 | 889 | assert!(result.is_err()); 890 | assert_eq!(result.unwrap_err().kind(), ErrorKind::Other); 891 | 892 | let result = fs.rename(&dir, &file); 893 | 894 | assert!(result.is_err()); 895 | assert_eq!(result.unwrap_err().kind(), ErrorKind::Other); 896 | } 897 | 898 | fn rename_fails_if_destination_directory_is_not_empty(fs: &T, parent: &Path) { 899 | let from = parent.join("from"); 900 | let to = parent.join("to"); 901 | let child = to.join("child"); 902 | 903 | fs.create_dir(&from).unwrap(); 904 | fs.create_dir(&to).unwrap(); 905 | fs.create_file(&child, "child").unwrap(); 906 | 907 | let result = fs.rename(&from, &to); 908 | 909 | assert!(result.is_err()); 910 | } 911 | 912 | fn readonly_returns_write_permission(fs: &T, parent: &Path) { 913 | let path = parent.join("test_file"); 914 | 915 | fs.create_file(&path, "").unwrap(); 916 | 917 | let result = fs.readonly(&path); 918 | 919 | assert!(result.is_ok()); 920 | assert!(!result.unwrap()); 921 | 922 | fs.set_readonly(&path, true).unwrap(); 923 | 924 | let result = fs.readonly(&path); 925 | 926 | assert!(result.is_ok()); 927 | assert!(result.unwrap()); 928 | } 929 | 930 | fn readonly_fails_if_node_does_not_exist(fs: &T, parent: &Path) { 931 | let result = fs.readonly(parent.join("does_not_exist")); 932 | 933 | assert!(result.is_err()); 934 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 935 | } 936 | 937 | fn set_readonly_toggles_write_permission_of_file(fs: &T, parent: &Path) { 938 | let path = parent.join("test_file"); 939 | 940 | fs.create_file(&path, "").unwrap(); 941 | 942 | let result = fs.set_readonly(&path, true); 943 | 944 | assert!(result.is_ok()); 945 | assert!(fs.write_file(&path, "readonly").is_err()); 946 | 947 | let result = fs.set_readonly(&path, false); 948 | 949 | assert!(result.is_ok()); 950 | assert!(fs.write_file(&path, "no longer readonly").is_ok()); 951 | } 952 | 953 | fn set_readonly_toggles_write_permission_of_dir(fs: &T, parent: &Path) { 954 | let path = parent.join("test_dir"); 955 | 956 | fs.create_dir(&path).unwrap(); 957 | 958 | let result = fs.set_readonly(&path, true); 959 | 960 | assert!(result.is_ok()); 961 | assert!(fs.write_file(&path.join("file"), "").is_err()); 962 | 963 | let result = fs.set_readonly(&path, false); 964 | 965 | assert!(result.is_ok()); 966 | assert!(fs.write_file(&path.join("file"), "").is_ok()); 967 | } 968 | 969 | fn set_readonly_fails_if_node_does_not_exist(fs: &T, parent: &Path) { 970 | let result = fs.set_readonly(parent.join("does_not_exist"), true); 971 | 972 | assert!(result.is_err()); 973 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 974 | 975 | let result = fs.set_readonly(parent.join("does_not_exist"), true); 976 | 977 | assert!(result.is_err()); 978 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 979 | } 980 | 981 | fn len_returns_size_of_file(fs: &T, parent: &Path) { 982 | let path = parent.join("file"); 983 | let result = fs.create_file(&path, ""); 984 | 985 | assert!(result.is_ok()); 986 | 987 | let len = fs.len(&path); 988 | 989 | assert_eq!(len, 0); 990 | 991 | let result = fs.write_file(&path, "contents"); 992 | 993 | assert!(result.is_ok()); 994 | 995 | let len = fs.len(&path); 996 | 997 | assert_eq!(len, 8); 998 | } 999 | 1000 | fn len_returns_size_of_directory(fs: &T, parent: &Path) { 1001 | let path = parent.join("directory"); 1002 | let result = fs.create_dir(&path); 1003 | 1004 | assert!(result.is_ok()); 1005 | 1006 | let len = fs.len(&path); 1007 | 1008 | assert_ne!(len, 0); 1009 | } 1010 | 1011 | fn len_returns_0_if_node_does_not_exist(fs: &T, parent: &Path) { 1012 | let path = parent.join("does-not-exist"); 1013 | let len = fs.len(&path); 1014 | 1015 | assert_eq!(len, 0); 1016 | } 1017 | 1018 | #[cfg(unix)] 1019 | fn mode_returns_permissions(fs: &T, parent: &Path) { 1020 | let path = parent.join("file"); 1021 | 1022 | fs.create_file(&path, "").unwrap(); 1023 | fs.set_mode(&path, 0o644).unwrap(); 1024 | 1025 | let result = fs.mode(&path); 1026 | 1027 | assert!(result.is_ok()); 1028 | assert_eq!(result.unwrap() % 0o100_000, 0o644); 1029 | 1030 | fs.set_mode(&path, 0o600).unwrap(); 1031 | 1032 | let result = fs.mode(&path); 1033 | 1034 | assert!(result.is_ok()); 1035 | assert_eq!(result.unwrap() % 0o100_000, 0o600); 1036 | 1037 | fs.set_readonly(&path, true).unwrap(); 1038 | 1039 | let result = fs.mode(&path); 1040 | 1041 | assert!(result.is_ok()); 1042 | assert_eq!(result.unwrap() % 0o100_000, 0o400); 1043 | } 1044 | 1045 | #[cfg(unix)] 1046 | fn mode_fails_if_node_does_not_exist(fs: &T, parent: &Path) { 1047 | let result = fs.mode(parent.join("does_not_exist")); 1048 | 1049 | assert!(result.is_err()); 1050 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 1051 | } 1052 | 1053 | #[cfg(unix)] 1054 | fn set_mode_sets_permissions(fs: &T, parent: &Path) { 1055 | let path = parent.join("file"); 1056 | 1057 | fs.create_file(&path, "").unwrap(); 1058 | 1059 | let result = fs.set_mode(&path, 0o000); 1060 | 1061 | assert!(result.is_ok()); 1062 | 1063 | let readonly_result = fs.readonly(&path); 1064 | 1065 | assert!(readonly_result.is_ok()); 1066 | assert!(readonly_result.unwrap()); 1067 | 1068 | let read_result = fs.read_file(&path); 1069 | let write_result = fs.write_file(&path, "should not be allowed"); 1070 | 1071 | assert!(read_result.is_err()); 1072 | assert!(write_result.is_err()); 1073 | assert_eq!(read_result.unwrap_err().kind(), ErrorKind::PermissionDenied); 1074 | assert_eq!( 1075 | write_result.unwrap_err().kind(), 1076 | ErrorKind::PermissionDenied 1077 | ); 1078 | 1079 | let result = fs.set_mode(&path, 0o200); 1080 | 1081 | assert!(result.is_ok()); 1082 | 1083 | let read_result = fs.read_file(&path); 1084 | let write_result = fs.write_file(&path, "should be allowed"); 1085 | 1086 | assert!(read_result.is_err()); 1087 | assert!(write_result.is_ok()); 1088 | assert_eq!(read_result.unwrap_err().kind(), ErrorKind::PermissionDenied); 1089 | 1090 | let readonly_result = fs.readonly(&path); 1091 | 1092 | assert!(readonly_result.is_ok()); 1093 | assert!(!readonly_result.unwrap()); 1094 | 1095 | let result = fs.set_mode(&path, 0o644); 1096 | 1097 | assert!(result.is_ok()); 1098 | 1099 | let readonly_result = fs.readonly(&path); 1100 | 1101 | assert!(readonly_result.is_ok()); 1102 | assert!(!readonly_result.unwrap()); 1103 | } 1104 | 1105 | #[cfg(unix)] 1106 | fn set_mode_fails_if_node_does_not_exist(fs: &T, parent: &Path) { 1107 | let result = fs.set_mode(parent.join("does_not_exist"), 0o644); 1108 | 1109 | assert!(result.is_err()); 1110 | assert_eq!(result.unwrap_err().kind(), ErrorKind::NotFound); 1111 | } 1112 | 1113 | fn temp_dir_creates_tempdir(fs: &T, _: &Path) { 1114 | let path = { 1115 | let result = fs.temp_dir("test"); 1116 | 1117 | assert!(result.is_ok()); 1118 | 1119 | let temp_dir = result.unwrap(); 1120 | 1121 | assert!(fs.is_dir(temp_dir.path())); 1122 | 1123 | temp_dir.path().to_path_buf() 1124 | }; 1125 | 1126 | assert!(!fs.is_dir(&path)); 1127 | assert!(fs.is_dir(path.parent().unwrap())); 1128 | } 1129 | 1130 | fn temp_dir_creates_unique_dir(fs: &T, _: &Path) { 1131 | let first = fs.temp_dir("test").unwrap(); 1132 | let second = fs.temp_dir("test").unwrap(); 1133 | 1134 | assert_ne!(first.path(), second.path()); 1135 | } 1136 | --------------------------------------------------------------------------------