2024-01-01 14:58:21 -05:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
2023-06-10 11:09:45 -04:00
|
|
|
|
2024-01-22 16:31:12 -05:00
|
|
|
use pretty_assertions::assert_eq;
|
2023-06-10 11:09:45 -04:00
|
|
|
use std::borrow::Cow;
|
2024-03-13 12:21:13 -04:00
|
|
|
use std::collections::HashSet;
|
2023-06-10 11:09:45 -04:00
|
|
|
use std::ffi::OsStr;
|
|
|
|
use std::fs;
|
2024-02-20 16:29:57 -05:00
|
|
|
use std::fs::OpenOptions;
|
|
|
|
use std::io::Write;
|
2023-06-10 11:09:45 -04:00
|
|
|
use std::path::Path;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use std::process::Command;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
use anyhow::Context;
|
|
|
|
use lsp_types::Url;
|
2023-07-26 17:23:07 -04:00
|
|
|
use serde::de::DeserializeOwned;
|
|
|
|
use serde::Serialize;
|
2023-06-10 11:09:45 -04:00
|
|
|
|
2023-06-26 15:53:17 -04:00
|
|
|
use crate::assertions::assert_wildcard_match;
|
2023-11-17 22:46:15 -05:00
|
|
|
use crate::testdata_path;
|
2023-06-26 15:53:17 -04:00
|
|
|
|
2023-06-10 11:09:45 -04:00
|
|
|
/// Represents a path on the file system, which can be used
|
|
|
|
/// to perform specific actions.
|
2024-03-13 12:21:13 -04:00
|
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
2023-06-10 11:09:45 -04:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
|
2023-08-02 16:57:25 -04:00
|
|
|
pub fn uri_dir(&self) -> Url {
|
2023-06-10 11:09:45 -04:00
|
|
|
Url::from_directory_path(self.as_path()).unwrap()
|
|
|
|
}
|
|
|
|
|
2023-08-02 16:57:25 -04:00
|
|
|
pub fn uri_file(&self) -> Url {
|
|
|
|
Url::from_file_path(self.as_path()).unwrap()
|
|
|
|
}
|
|
|
|
|
2023-06-10 11:09:45 -04:00
|
|
|
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)
|
2023-06-26 15:53:17 -04:00
|
|
|
.with_context(|| format!("Could not read file: {}", self))
|
2023-06-10 11:09:45 -04:00
|
|
|
}
|
|
|
|
|
2024-01-31 22:15:22 -05:00
|
|
|
pub fn read_to_bytes_if_exists(&self) -> Result<Vec<u8>, anyhow::Error> {
|
|
|
|
fs::read(self).with_context(|| format!("Could not read file: {}", self))
|
|
|
|
}
|
|
|
|
|
2024-03-13 12:21:13 -04:00
|
|
|
#[track_caller]
|
2023-07-26 17:23:07 -04:00
|
|
|
pub fn read_json<TValue: DeserializeOwned>(&self) -> TValue {
|
2024-03-13 12:21:13 -04:00
|
|
|
serde_json::from_str(&self.read_to_string())
|
|
|
|
.with_context(|| format!("Failed deserializing: {}", self))
|
|
|
|
.unwrap()
|
2023-07-26 17:23:07 -04:00
|
|
|
}
|
|
|
|
|
2024-03-13 12:21:13 -04:00
|
|
|
#[track_caller]
|
2023-08-01 20:49:09 -04:00
|
|
|
pub fn read_json_value(&self) -> serde_json::Value {
|
2024-03-13 12:21:13 -04:00
|
|
|
serde_json::from_str(&self.read_to_string())
|
|
|
|
.with_context(|| format!("Failed deserializing: {}", self))
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[track_caller]
|
|
|
|
pub fn read_jsonc_value(&self) -> serde_json::Value {
|
|
|
|
jsonc_parser::parse_to_serde_value(
|
|
|
|
&self.read_to_string(),
|
|
|
|
&Default::default(),
|
|
|
|
)
|
|
|
|
.with_context(|| format!("Failed to parse {}", self))
|
|
|
|
.unwrap()
|
|
|
|
.expect("Found no value.")
|
2023-08-01 20:49:09 -04:00
|
|
|
}
|
|
|
|
|
2023-06-10 11:09:45 -04:00
|
|
|
pub fn rename(&self, to: impl AsRef<Path>) {
|
|
|
|
fs::rename(self, self.join(to)).unwrap();
|
|
|
|
}
|
|
|
|
|
2024-02-20 16:29:57 -05:00
|
|
|
pub fn append(&self, text: impl AsRef<str>) {
|
|
|
|
let mut file = OpenOptions::new().append(true).open(self).unwrap();
|
|
|
|
file.write_all(text.as_ref().as_bytes()).unwrap();
|
|
|
|
}
|
|
|
|
|
2023-06-10 11:09:45 -04:00
|
|
|
pub fn write(&self, text: impl AsRef<str>) {
|
|
|
|
fs::write(self, text.as_ref()).unwrap();
|
|
|
|
}
|
|
|
|
|
2023-07-26 17:23:07 -04:00
|
|
|
pub fn write_json<TValue: Serialize>(&self, value: &TValue) {
|
|
|
|
let text = serde_json::to_string_pretty(value).unwrap();
|
|
|
|
self.write(text);
|
|
|
|
}
|
|
|
|
|
2023-06-10 11:09:45 -04:00
|
|
|
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) {
|
2024-03-13 12:21:13 -04:00
|
|
|
self.copy_to_recursive_with_exclusions(to, &HashSet::new())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn copy_to_recursive_with_exclusions(
|
|
|
|
&self,
|
|
|
|
to: &PathRef,
|
|
|
|
file_exclusions: &HashSet<PathRef>,
|
|
|
|
) {
|
2023-06-10 11:09:45 -04:00
|
|
|
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);
|
2024-03-13 12:21:13 -04:00
|
|
|
} else if file_type.is_file() && !file_exclusions.contains(&new_from) {
|
2023-06-10 11:09:45 -04:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
2023-06-26 15:53:17 -04:00
|
|
|
|
2024-01-22 16:31:12 -05:00
|
|
|
#[track_caller]
|
2023-06-26 15:53:17 -04:00
|
|
|
pub fn assert_matches_file(&self, wildcard_file: impl AsRef<Path>) -> &Self {
|
2023-11-17 22:46:15 -05:00
|
|
|
let wildcard_file = testdata_path().join(wildcard_file);
|
2023-06-26 15:53:17 -04:00
|
|
|
println!("output path {}", wildcard_file);
|
|
|
|
let expected_text = wildcard_file.read_to_string();
|
|
|
|
self.assert_matches_text(&expected_text)
|
|
|
|
}
|
|
|
|
|
2024-01-22 16:31:12 -05:00
|
|
|
#[track_caller]
|
2023-06-26 15:53:17 -04:00
|
|
|
pub fn assert_matches_text(&self, wildcard_text: impl AsRef<str>) -> &Self {
|
|
|
|
let actual = self.read_to_string();
|
|
|
|
assert_wildcard_match(&actual, wildcard_text.as_ref());
|
|
|
|
self
|
|
|
|
}
|
2024-01-22 16:31:12 -05:00
|
|
|
|
|
|
|
#[track_caller]
|
|
|
|
pub fn assert_matches_json(&self, expected: serde_json::Value) {
|
|
|
|
let actual_json = self.read_json_value();
|
|
|
|
if actual_json != expected {
|
|
|
|
let actual_text = serde_json::to_string_pretty(&actual_json).unwrap();
|
|
|
|
let expected_text = serde_json::to_string_pretty(&expected).unwrap();
|
|
|
|
assert_eq!(actual_text, expected_text);
|
|
|
|
}
|
|
|
|
}
|
2023-06-10 11:09:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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))
|
|
|
|
}
|
|
|
|
|
2023-07-21 09:12:26 -04:00
|
|
|
pub fn new_in(parent_dir: &Path) -> Self {
|
|
|
|
Self::new_inner(parent_dir, None)
|
|
|
|
}
|
|
|
|
|
2023-06-10 11:09:45 -04:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|