From 669fe775df5b4a1a780f33d42cb4e98dd526a7b3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 22 Feb 2022 12:43:56 -0800 Subject: [PATCH] Normalize paths passed to the FakeFs Co-Authored-By: Nathan Sobo --- crates/project/src/fs.rs | 78 +++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/crates/project/src/fs.rs b/crates/project/src/fs.rs index 8fbdb3220e0575a2c4e3977a0a5c2198921e9e35..7f89c29c8384e33d3817e180c42c288cdbda22a2 100644 --- a/crates/project/src/fs.rs +++ b/crates/project/src/fs.rs @@ -5,7 +5,7 @@ use smol::io::{AsyncReadExt, AsyncWriteExt}; use std::{ io, os::unix::fs::MetadataExt, - path::{Path, PathBuf}, + path::{Component, Path, PathBuf}, pin::Pin, time::{Duration, SystemTime}, }; @@ -379,6 +379,7 @@ impl Fs for FakeFs { async fn create_dir(&self, path: &Path) -> Result<()> { self.executor.simulate_random_delay().await; let state = &mut *self.state.lock().await; + let path = normalize_path(path); let mut ancestor_path = PathBuf::new(); let mut created_dir_paths = Vec::new(); for component in path.components() { @@ -415,8 +416,9 @@ impl Fs for FakeFs { async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> { self.executor.simulate_random_delay().await; let mut state = self.state.lock().await; - state.validate_path(path)?; - if let Some(entry) = state.entries.get_mut(path) { + let path = normalize_path(path); + state.validate_path(&path)?; + if let Some(entry) = state.entries.get_mut(&path) { if entry.metadata.is_dir || entry.metadata.is_symlink { return Err(anyhow!( "cannot create file because {:?} is a dir or a symlink", @@ -430,7 +432,7 @@ impl Fs for FakeFs { } else if !options.ignore_if_exists { return Err(anyhow!( "cannot create file because {:?} already exists", - path + &path )); } } else { @@ -453,11 +455,14 @@ impl Fs for FakeFs { } async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> { + let source = normalize_path(source); + let target = normalize_path(target); + let mut state = self.state.lock().await; - state.validate_path(source)?; - state.validate_path(target)?; + state.validate_path(&source)?; + state.validate_path(&target)?; - if !options.overwrite && state.entries.contains_key(target) { + if !options.overwrite && state.entries.contains_key(&target) { if options.ignore_if_exists { return Ok(()); } else { @@ -467,7 +472,7 @@ impl Fs for FakeFs { let mut removed = Vec::new(); state.entries.retain(|path, entry| { - if let Ok(relative_path) = path.strip_prefix(source) { + if let Ok(relative_path) = path.strip_prefix(&source) { removed.push((relative_path.to_path_buf(), entry.clone())); false } else { @@ -485,9 +490,10 @@ impl Fs for FakeFs { } async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> { + let path = normalize_path(path); let mut state = self.state.lock().await; - state.validate_path(path)?; - if let Some(entry) = state.entries.get(path) { + state.validate_path(&path)?; + if let Some(entry) = state.entries.get(&path) { if !entry.metadata.is_dir { return Err(anyhow!("cannot remove {path:?} because it is not a dir")); } @@ -513,14 +519,15 @@ impl Fs for FakeFs { } async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> { + let path = normalize_path(path); let mut state = self.state.lock().await; - state.validate_path(path)?; - if let Some(entry) = state.entries.get(path) { + state.validate_path(&path)?; + if let Some(entry) = state.entries.get(&path) { if entry.metadata.is_dir { return Err(anyhow!("cannot remove {path:?} because it is not a file")); } - state.entries.remove(path); + state.entries.remove(&path); state.emit_event(&[path]).await; } else if !options.ignore_if_not_exists { return Err(anyhow!("{path:?} does not exist")); @@ -529,11 +536,12 @@ impl Fs for FakeFs { } async fn load(&self, path: &Path) -> Result { + let path = normalize_path(path); self.executor.simulate_random_delay().await; let state = self.state.lock().await; let text = state .entries - .get(path) + .get(&path) .and_then(|e| e.content.as_ref()) .ok_or_else(|| anyhow!("file {:?} does not exist", path))?; Ok(text.clone()) @@ -542,8 +550,9 @@ impl Fs for FakeFs { async fn save(&self, path: &Path, text: &Rope) -> Result<()> { self.executor.simulate_random_delay().await; let mut state = self.state.lock().await; - state.validate_path(path)?; - if let Some(entry) = state.entries.get_mut(path) { + let path = normalize_path(path); + state.validate_path(&path)?; + if let Some(entry) = state.entries.get_mut(&path) { if entry.metadata.is_dir { Err(anyhow!("cannot overwrite a directory with a file")) } else { @@ -572,22 +581,24 @@ impl Fs for FakeFs { async fn canonicalize(&self, path: &Path) -> Result { self.executor.simulate_random_delay().await; - Ok(path.to_path_buf()) + Ok(normalize_path(path)) } async fn is_file(&self, path: &Path) -> bool { + let path = normalize_path(path); self.executor.simulate_random_delay().await; let state = self.state.lock().await; state .entries - .get(path) + .get(&path) .map_or(false, |entry| !entry.metadata.is_dir) } async fn metadata(&self, path: &Path) -> Result> { self.executor.simulate_random_delay().await; let state = self.state.lock().await; - Ok(state.entries.get(path).map(|entry| entry.metadata.clone())) + let path = normalize_path(path); + Ok(state.entries.get(&path).map(|entry| entry.metadata.clone())) } async fn read_dir( @@ -597,7 +608,7 @@ impl Fs for FakeFs { use futures::{future, stream}; self.executor.simulate_random_delay().await; let state = self.state.lock().await; - let abs_path = abs_path.to_path_buf(); + let abs_path = normalize_path(abs_path); Ok(Box::pin(stream::iter(state.entries.clone()).filter_map( move |(child_path, _)| { future::ready(if child_path.parent() == Some(&abs_path) { @@ -633,3 +644,30 @@ impl Fs for FakeFs { self } } + +pub fn normalize_path(path: &Path) -> PathBuf { + let mut components = path.components().peekable(); + let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + components.next(); + PathBuf::from(c.as_os_str()) + } else { + PathBuf::new() + }; + + for component in components { + match component { + Component::Prefix(..) => unreachable!(), + Component::RootDir => { + ret.push(component.as_os_str()); + } + Component::CurDir => {} + Component::ParentDir => { + ret.pop(); + } + Component::Normal(c) => { + ret.push(c); + } + } + } + ret +}