diff options
Diffstat (limited to 'test_util/src/fs.rs')
-rw-r--r-- | test_util/src/fs.rs | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/test_util/src/fs.rs b/test_util/src/fs.rs new file mode 100644 index 000000000..12750ffa4 --- /dev/null +++ b/test_util/src/fs.rs @@ -0,0 +1,394 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use std::borrow::Cow; +use std::ffi::OsStr; +use std::fs; +use std::path::Path; +use std::path::PathBuf; +use std::process::Command; +use std::sync::Arc; + +use anyhow::Context; +use lsp_types::Url; + +/// Represents a path on the file system, which can be used +/// to perform specific actions. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PathRef(PathBuf); + +impl AsRef<Path> for PathRef { + fn as_ref(&self) -> &Path { + self.as_path() + } +} + +impl AsRef<OsStr> for PathRef { + fn as_ref(&self) -> &OsStr { + self.as_path().as_ref() + } +} + +impl std::fmt::Display for PathRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_path().display()) + } +} + +impl PathRef { + pub fn new(path: impl AsRef<Path>) -> Self { + Self(path.as_ref().to_path_buf()) + } + + pub fn parent(&self) -> PathRef { + PathRef(self.as_path().parent().unwrap().to_path_buf()) + } + + pub fn uri(&self) -> Url { + Url::from_directory_path(self.as_path()).unwrap() + } + + pub fn as_path(&self) -> &Path { + self.0.as_path() + } + + pub fn to_path_buf(&self) -> PathBuf { + self.0.to_path_buf() + } + + pub fn to_string_lossy(&self) -> Cow<str> { + self.0.to_string_lossy() + } + + pub fn exists(&self) -> bool { + self.0.exists() + } + + pub fn try_exists(&self) -> std::io::Result<bool> { + self.0.try_exists() + } + + pub fn is_dir(&self) -> bool { + self.0.is_dir() + } + + pub fn is_file(&self) -> bool { + self.0.is_file() + } + + pub fn join(&self, path: impl AsRef<Path>) -> PathRef { + PathRef(self.as_path().join(path)) + } + + pub fn with_extension(&self, ext: impl AsRef<OsStr>) -> PathRef { + PathRef(self.as_path().with_extension(ext)) + } + + pub fn canonicalize(&self) -> PathRef { + PathRef(strip_unc_prefix(self.as_path().canonicalize().unwrap())) + } + + pub fn create_dir_all(&self) { + fs::create_dir_all(self).unwrap(); + } + + pub fn remove_file(&self) { + fs::remove_file(self).unwrap(); + } + + pub fn remove_dir_all(&self) { + fs::remove_dir_all(self).unwrap(); + } + + pub fn read_to_string(&self) -> String { + self.read_to_string_if_exists().unwrap() + } + + pub fn read_to_string_if_exists(&self) -> Result<String, anyhow::Error> { + fs::read_to_string(self) + .with_context(|| format!("Could not find file: {}", self)) + } + + pub fn rename(&self, to: impl AsRef<Path>) { + fs::rename(self, self.join(to)).unwrap(); + } + + pub fn write(&self, text: impl AsRef<str>) { + fs::write(self, text.as_ref()).unwrap(); + } + + pub fn symlink_dir( + &self, + oldpath: impl AsRef<Path>, + newpath: impl AsRef<Path>, + ) { + #[cfg(unix)] + { + use std::os::unix::fs::symlink; + symlink(self.as_path().join(oldpath), self.as_path().join(newpath)) + .unwrap(); + } + #[cfg(not(unix))] + { + use std::os::windows::fs::symlink_dir; + symlink_dir(self.as_path().join(oldpath), self.as_path().join(newpath)) + .unwrap(); + } + } + + pub fn symlink_file( + &self, + oldpath: impl AsRef<Path>, + newpath: impl AsRef<Path>, + ) { + #[cfg(unix)] + { + use std::os::unix::fs::symlink; + symlink(self.as_path().join(oldpath), self.as_path().join(newpath)) + .unwrap(); + } + #[cfg(not(unix))] + { + use std::os::windows::fs::symlink_file; + symlink_file(self.as_path().join(oldpath), self.as_path().join(newpath)) + .unwrap(); + } + } + + pub fn read_dir(&self) -> fs::ReadDir { + fs::read_dir(self.as_path()) + .with_context(|| format!("Reading {}", self.as_path().display())) + .unwrap() + } + + pub fn copy(&self, to: &impl AsRef<Path>) { + std::fs::copy(self.as_path(), to) + .with_context(|| format!("Copying {} to {}", self, to.as_ref().display())) + .unwrap(); + } + + /// Copies this directory to another directory. + /// + /// Note: Does not handle symlinks. + pub fn copy_to_recursive(&self, to: &PathRef) { + to.create_dir_all(); + let read_dir = self.read_dir(); + + for entry in read_dir { + let entry = entry.unwrap(); + let file_type = entry.file_type().unwrap(); + let new_from = self.join(entry.file_name()); + let new_to = to.join(entry.file_name()); + + if file_type.is_dir() { + new_from.copy_to_recursive(&new_to); + } else if file_type.is_file() { + new_from.copy(&new_to); + } + } + } + + pub fn make_dir_readonly(&self) { + self.create_dir_all(); + if cfg!(windows) { + Command::new("attrib").arg("+r").arg(self).output().unwrap(); + } else if cfg!(unix) { + Command::new("chmod").arg("555").arg(self).output().unwrap(); + } + } +} + +#[cfg(not(windows))] +#[inline] +fn strip_unc_prefix(path: PathBuf) -> PathBuf { + path +} + +/// Strips the unc prefix (ex. \\?\) from Windows paths. +/// +/// Lifted from deno_core for use in the tests. +#[cfg(windows)] +fn strip_unc_prefix(path: PathBuf) -> PathBuf { + use std::path::Component; + use std::path::Prefix; + + let mut components = path.components(); + match components.next() { + Some(Component::Prefix(prefix)) => { + match prefix.kind() { + // \\?\device + Prefix::Verbatim(device) => { + let mut path = PathBuf::new(); + path.push(format!(r"\\{}\", device.to_string_lossy())); + path.extend(components.filter(|c| !matches!(c, Component::RootDir))); + path + } + // \\?\c:\path + Prefix::VerbatimDisk(_) => { + let mut path = PathBuf::new(); + path.push(prefix.as_os_str().to_string_lossy().replace(r"\\?\", "")); + path.extend(components); + path + } + // \\?\UNC\hostname\share_name\path + Prefix::VerbatimUNC(hostname, share_name) => { + let mut path = PathBuf::new(); + path.push(format!( + r"\\{}\{}\", + hostname.to_string_lossy(), + share_name.to_string_lossy() + )); + path.extend(components.filter(|c| !matches!(c, Component::RootDir))); + path + } + _ => path, + } + } + _ => path, + } +} + +enum TempDirInner { + TempDir { + path_ref: PathRef, + // kept alive for the duration of the temp dir + _dir: tempfile::TempDir, + }, + Path(PathRef), + Symlinked { + symlink: Arc<TempDirInner>, + target: Arc<TempDirInner>, + }, +} + +impl TempDirInner { + pub fn path(&self) -> &PathRef { + match self { + Self::Path(path_ref) => path_ref, + Self::TempDir { path_ref, .. } => path_ref, + Self::Symlinked { symlink, .. } => symlink.path(), + } + } + + pub fn target_path(&self) -> &PathRef { + match self { + TempDirInner::Symlinked { target, .. } => target.target_path(), + _ => self.path(), + } + } +} + +impl Drop for TempDirInner { + fn drop(&mut self) { + if let Self::Path(path) = self { + _ = fs::remove_dir_all(path); + } + } +} + +/// For creating temporary directories in tests. +/// +/// This was done because `tempfiles::TempDir` was very slow on Windows. +/// +/// Note: Do not use this in actual code as this does not protect against +/// "insecure temporary file" security vulnerabilities. +#[derive(Clone)] +pub struct TempDir(Arc<TempDirInner>); + +impl Default for TempDir { + fn default() -> Self { + Self::new() + } +} + +impl TempDir { + pub fn new() -> Self { + Self::new_inner(&std::env::temp_dir(), None) + } + + pub fn new_with_prefix(prefix: &str) -> Self { + Self::new_inner(&std::env::temp_dir(), Some(prefix)) + } + + pub fn new_with_path(path: &Path) -> Self { + Self(Arc::new(TempDirInner::Path(PathRef(path.to_path_buf())))) + } + + pub fn new_symlinked(target: TempDir) -> Self { + let target_path = target.path(); + let path = target_path.parent().join(format!( + "{}_symlinked", + target_path.as_path().file_name().unwrap().to_str().unwrap() + )); + target.symlink_dir(target.path(), &path); + TempDir(Arc::new(TempDirInner::Symlinked { + target: target.0, + symlink: Self::new_with_path(path.as_path()).0, + })) + } + + /// Create a new temporary directory with the given prefix as part of its name, if specified. + fn new_inner(parent_dir: &Path, prefix: Option<&str>) -> Self { + let mut builder = tempfile::Builder::new(); + builder.prefix(prefix.unwrap_or("deno-cli-test")); + let dir = builder + .tempdir_in(parent_dir) + .expect("Failed to create a temporary directory"); + Self(Arc::new(TempDirInner::TempDir { + path_ref: PathRef(dir.path().to_path_buf()), + _dir: dir, + })) + } + + pub fn uri(&self) -> Url { + Url::from_directory_path(self.path()).unwrap() + } + + pub fn path(&self) -> &PathRef { + self.0.path() + } + + /// The resolved final target path if this is a symlink. + pub fn target_path(&self) -> &PathRef { + self.0.target_path() + } + + pub fn create_dir_all(&self, path: impl AsRef<Path>) { + self.target_path().join(path).create_dir_all() + } + + pub fn remove_file(&self, path: impl AsRef<Path>) { + self.target_path().join(path).remove_file() + } + + pub fn remove_dir_all(&self, path: impl AsRef<Path>) { + self.target_path().join(path).remove_dir_all() + } + + pub fn read_to_string(&self, path: impl AsRef<Path>) -> String { + self.target_path().join(path).read_to_string() + } + + pub fn rename(&self, from: impl AsRef<Path>, to: impl AsRef<Path>) { + self.target_path().join(from).rename(to) + } + + pub fn write(&self, path: impl AsRef<Path>, text: impl AsRef<str>) { + self.target_path().join(path).write(text) + } + + pub fn symlink_dir( + &self, + oldpath: impl AsRef<Path>, + newpath: impl AsRef<Path>, + ) { + self.target_path().symlink_dir(oldpath, newpath) + } + + pub fn symlink_file( + &self, + oldpath: impl AsRef<Path>, + newpath: impl AsRef<Path>, + ) { + self.target_path().symlink_file(oldpath, newpath) + } +} |