diff --git a/cli/args/config_file.rs b/cli/args/config_file.rs index 0d84de2e41..1546923767 100644 --- a/cli/args/config_file.rs +++ b/cli/args/config_file.rs @@ -18,6 +18,7 @@ use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; use deno_core::ModuleSpecifier; +use std::borrow::Cow; use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; @@ -483,10 +484,21 @@ pub struct ConfigFile { } impl ConfigFile { - pub fn discover(flags: &Flags) -> Result, AnyError> { + pub fn discover( + flags: &Flags, + cwd: &Path, + ) -> Result, AnyError> { match &flags.config_flag { ConfigFlag::Disabled => Ok(None), - ConfigFlag::Path(config_path) => Ok(Some(ConfigFile::read(config_path)?)), + ConfigFlag::Path(config_path) => { + let config_path = PathBuf::from(config_path); + let config_path = if config_path.is_absolute() { + config_path + } else { + cwd.join(config_path) + }; + Ok(Some(ConfigFile::read(&config_path)?)) + } ConfigFlag::Discover => { if let Some(config_path_args) = flags.config_path_args() { let mut checked = HashSet::new(); @@ -508,8 +520,7 @@ impl ConfigFile { } }; // From CWD walk up to root looking for deno.json or deno.jsonc - let cwd = std::env::current_dir()?; - Self::discover_from(&cwd, &mut checked) + Self::discover_from(cwd, &mut checked) } else { Ok(None) } @@ -524,6 +535,14 @@ impl ConfigFile { /// Filenames that Deno will recognize when discovering config. const CONFIG_FILE_NAMES: [&str; 2] = ["deno.json", "deno.jsonc"]; + // todo(dsherret): in the future, we should force all callers + // to provide a resolved path + let start = if start.is_absolute() { + Cow::Borrowed(start) + } else { + Cow::Owned(std::env::current_dir()?.join(start)) + }; + for ancestor in start.ancestors() { if checked.insert(ancestor.to_path_buf()) { for config_filename in CONFIG_FILE_NAMES { @@ -556,34 +575,29 @@ impl ConfigFile { Ok(None) } - pub fn read(path_ref: impl AsRef) -> Result { - let path = Path::new(path_ref.as_ref()); - let config_file = if path.is_absolute() { - path.to_path_buf() - } else { - std::env::current_dir()?.join(path_ref) - }; + pub fn read(config_path: &Path) -> Result { + debug_assert!(config_path.is_absolute()); // perf: Check if the config file exists before canonicalizing path. - if !config_file.exists() { + if !config_path.exists() { return Err( std::io::Error::new( std::io::ErrorKind::InvalidInput, format!( "Could not find the config file: {}", - config_file.to_string_lossy() + config_path.to_string_lossy() ), ) .into(), ); } - let config_path = canonicalize_path(&config_file).map_err(|_| { + let config_path = canonicalize_path(config_path).map_err(|_| { std::io::Error::new( std::io::ErrorKind::InvalidInput, format!( "Could not find the config file: {}", - config_file.to_string_lossy() + config_path.to_string_lossy() ), ) })?; @@ -990,25 +1004,17 @@ mod tests { use deno_core::serde_json::json; use pretty_assertions::assert_eq; - #[test] - fn read_config_file_relative() { - let config_file = - ConfigFile::read("tests/testdata/module_graph/tsconfig.json") - .expect("Failed to load config file"); - assert!(config_file.json.compiler_options.is_some()); - } - #[test] fn read_config_file_absolute() { let path = test_util::testdata_path().join("module_graph/tsconfig.json"); - let config_file = ConfigFile::read(path.to_str().unwrap()) - .expect("Failed to load config file"); + let config_file = ConfigFile::read(&path).unwrap(); assert!(config_file.json.compiler_options.is_some()); } #[test] fn include_config_path_on_error() { - let error = ConfigFile::read("404.json").err().unwrap(); + let path = test_util::testdata_path().join("404.json"); + let error = ConfigFile::read(&path).err().unwrap(); assert!(error.to_string().contains("404.json")); } diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 7cb2213e92..3d8a29fe70 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -51,7 +51,6 @@ use deno_runtime::permissions::PermissionsOptions; use once_cell::sync::Lazy; use std::collections::BTreeMap; use std::collections::HashMap; -use std::collections::HashSet; use std::env; use std::io::BufReader; use std::io::Cursor; @@ -393,38 +392,35 @@ fn discover_package_json( flags: &Flags, maybe_stop_at: Option, ) -> Result, AnyError> { - pub fn discover_from( + fn discover_from( start: &Path, - checked: &mut HashSet, maybe_stop_at: Option, ) -> Result, AnyError> { const PACKAGE_JSON_NAME: &str = "package.json"; for ancestor in start.ancestors() { - if checked.insert(ancestor.to_path_buf()) { - let path = ancestor.join(PACKAGE_JSON_NAME); + let path = ancestor.join(PACKAGE_JSON_NAME); - let source = match std::fs::read_to_string(&path) { - Ok(source) => source, - Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - if let Some(stop_at) = maybe_stop_at.as_ref() { - if ancestor == stop_at { - break; - } + let source = match std::fs::read_to_string(&path) { + Ok(source) => source, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + if let Some(stop_at) = maybe_stop_at.as_ref() { + if ancestor == stop_at { + break; } - continue; } - Err(err) => bail!( - "Error loading package.json at {}. {:#}", - path.display(), - err - ), - }; + continue; + } + Err(err) => bail!( + "Error loading package.json at {}. {:#}", + path.display(), + err + ), + }; - let package_json = PackageJson::load_from_string(path.clone(), source)?; - log::debug!("package.json file found at '{}'", path.display()); - return Ok(Some(package_json)); - } + let package_json = PackageJson::load_from_string(path.clone(), source)?; + log::debug!("package.json file found at '{}'", path.display()); + return Ok(Some(package_json)); } // No config file found. log::debug!("No package.json file found"); @@ -434,21 +430,17 @@ fn discover_package_json( // TODO(bartlomieju): discover for all subcommands, but print warnings that // `package.json` is ignored in bundle/compile/etc. - if let Some(package_json_arg) = flags.package_json_arg() { - return discover_from( - &package_json_arg, - &mut HashSet::new(), - maybe_stop_at, - ); - } else if let crate::args::DenoSubcommand::Task(TaskFlags { - cwd: Some(path), - .. + if let crate::args::DenoSubcommand::Task(TaskFlags { + cwd: Some(path), .. }) = &flags.subcommand { // attempt to resolve the config file from the task subcommand's // `--cwd` when specified let task_cwd = canonicalize_path(&PathBuf::from(path))?; - return discover_from(&task_cwd, &mut HashSet::new(), None); + return discover_from(&task_cwd, None); + } else if let Some(package_json_arg) = flags.package_json_arg() { + let package_json_arg = canonicalize_path(&package_json_arg)?; + return discover_from(&package_json_arg, maybe_stop_at); } log::debug!("No package.json file found"); @@ -542,9 +534,6 @@ pub fn get_root_cert_store( const RESOLUTION_STATE_ENV_VAR_NAME: &str = "DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE"; -static IS_NPM_MAIN: Lazy = - Lazy::new(|| std::env::var(RESOLUTION_STATE_ENV_VAR_NAME).is_ok()); - static NPM_PROCESS_STATE: Lazy> = Lazy::new(|| { let state = std::env::var(RESOLUTION_STATE_ENV_VAR_NAME).ok()?; let state: NpmProcessState = serde_json::from_str(&state).ok()?; @@ -568,6 +557,7 @@ pub struct CliOptions { // the source of the options is a detail the rest of the // application need not concern itself with, so keep these private flags: Flags, + maybe_node_modules_folder: Option, maybe_config_file: Option, maybe_package_json: Option, maybe_lockfile: Option>>, @@ -577,10 +567,11 @@ pub struct CliOptions { impl CliOptions { pub fn new( flags: Flags, + initial_cwd: PathBuf, maybe_config_file: Option, maybe_lockfile: Option, maybe_package_json: Option, - ) -> Self { + ) -> Result { if let Some(insecure_allowlist) = flags.unsafely_ignore_certificate_errors.as_ref() { @@ -596,18 +587,28 @@ impl CliOptions { } let maybe_lockfile = maybe_lockfile.map(|l| Arc::new(Mutex::new(l))); + let maybe_node_modules_folder = resolve_local_node_modules_folder( + &initial_cwd, + &flags, + maybe_config_file.as_ref(), + maybe_package_json.as_ref(), + ) + .with_context(|| "Resolving node_modules folder.")?; - Self { + Ok(Self { + flags, maybe_config_file, maybe_lockfile, maybe_package_json, - flags, + maybe_node_modules_folder, overrides: Default::default(), - } + }) } pub fn from_flags(flags: Flags) -> Result { - let maybe_config_file = ConfigFile::discover(&flags)?; + let initial_cwd = + std::env::current_dir().with_context(|| "Failed getting cwd.")?; + let maybe_config_file = ConfigFile::discover(&flags, &initial_cwd)?; let mut maybe_package_json = None; if let Some(config_file) = &maybe_config_file { @@ -626,12 +627,13 @@ impl CliOptions { } let maybe_lock_file = lockfile::discover(&flags, maybe_config_file.as_ref())?; - Ok(Self::new( + Self::new( flags, + initial_cwd, maybe_config_file, maybe_lock_file, maybe_package_json, - )) + ) } pub fn maybe_config_file_specifier(&self) -> Option { @@ -705,16 +707,8 @@ impl CliOptions { .map(Some) } - fn get_npm_process_state(&self) -> Option<&NpmProcessState> { - if !self.is_npm_main() { - return None; - } - - (*NPM_PROCESS_STATE).as_ref() - } - pub fn get_npm_resolution_snapshot(&self) -> Option { - if let Some(state) = self.get_npm_process_state() { + if let Some(state) = &*NPM_PROCESS_STATE { // TODO(bartlomieju): remove this clone return Some(state.snapshot.clone()); } @@ -727,7 +721,7 @@ impl CliOptions { // for functionality like child_process.fork. Users should NOT depend // on this functionality. pub fn is_npm_main(&self) -> bool { - *IS_NPM_MAIN + NPM_PROCESS_STATE.is_some() } /// Overrides the import map specifier to use. @@ -735,36 +729,19 @@ impl CliOptions { self.overrides.import_map_specifier = Some(path); } - pub fn node_modules_dir(&self) -> bool { - if let Some(node_modules_dir) = self.flags.node_modules_dir { - return node_modules_dir; - } - - if let Some(npm_process_state) = self.get_npm_process_state() { - return npm_process_state.local_node_modules_path.is_some(); - } - - self.maybe_package_json.is_some() + pub fn has_node_modules_dir(&self) -> bool { + self.maybe_node_modules_folder.is_some() } - /// Resolves the path to use for a local node_modules folder. - pub fn resolve_local_node_modules_folder( - &self, - ) -> Result, AnyError> { - let path = if !self.node_modules_dir() { - return Ok(None); - } else if let Some(state) = self.get_npm_process_state() { - return Ok(state.local_node_modules_path.as_ref().map(PathBuf::from)); - } else if let Some(config_path) = self - .maybe_config_file + pub fn node_modules_dir_path(&self) -> Option { + self.maybe_node_modules_folder.clone() + } + + pub fn node_modules_dir_specifier(&self) -> Option { + self + .maybe_node_modules_folder .as_ref() - .and_then(|c| c.specifier.to_file_path().ok()) - { - config_path.parent().unwrap().join("node_modules") - } else { - std::env::current_dir()?.join("node_modules") - }; - Ok(Some(canonicalize_path_maybe_not_exists(&path)?)) + .map(|path| ModuleSpecifier::from_directory_path(path).unwrap()) } pub fn resolve_root_cert_store(&self) -> Result { @@ -1081,6 +1058,33 @@ impl CliOptions { } } +/// Resolves the path to use for a local node_modules folder. +fn resolve_local_node_modules_folder( + cwd: &Path, + flags: &Flags, + maybe_config_file: Option<&ConfigFile>, + maybe_package_json: Option<&PackageJson>, +) -> Result, AnyError> { + let path = if flags.node_modules_dir == Some(false) { + return Ok(None); + } else if let Some(state) = &*NPM_PROCESS_STATE { + return Ok(state.local_node_modules_path.as_ref().map(PathBuf::from)); + } else if let Some(package_json_path) = maybe_package_json.map(|c| &c.path) { + // always auto-discover the local_node_modules_folder when a package.json exists + package_json_path.parent().unwrap().join("node_modules") + } else if flags.node_modules_dir.is_none() { + return Ok(None); + } else if let Some(config_path) = maybe_config_file + .as_ref() + .and_then(|c| c.specifier.to_file_path().ok()) + { + config_path.parent().unwrap().join("node_modules") + } else { + cwd.join("node_modules") + }; + Ok(Some(canonicalize_path_maybe_not_exists(&path)?)) +} + fn resolve_import_map_specifier( maybe_import_map_path: Option<&str>, maybe_config_file: Option<&ConfigFile>, diff --git a/cli/graph_util.rs b/cli/graph_util.rs index b5726b9435..148ab1cee6 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -14,7 +14,6 @@ use crate::resolver::CliGraphResolver; use crate::tools::check; use deno_core::anyhow::bail; -use deno_core::anyhow::Context; use deno_core::error::custom_error; use deno_core::error::AnyError; use deno_core::ModuleSpecifier; @@ -153,10 +152,7 @@ pub async fn create_graph_and_maybe_check( ps.file_fetcher.clone(), PermissionsContainer::allow_all(), PermissionsContainer::allow_all(), - ps.options - .resolve_local_node_modules_folder() - .with_context(|| "Resolving local node_modules folder.")? - .map(|path| ModuleSpecifier::from_file_path(path).unwrap()), + ps.options.node_modules_dir_specifier(), ); let maybe_imports = ps.options.to_maybe_imports()?; let maybe_package_json_deps = ps.options.maybe_package_json_deps()?; diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index d056afbc04..808a98a2cc 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -2,6 +2,7 @@ use deno_ast::MediaType; use deno_core::anyhow::anyhow; +use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::resolve_url; use deno_core::serde_json; @@ -168,7 +169,7 @@ impl LanguageServer { .map(|d| (d.specifier().clone(), d)) .collect::>(); let ps = ProcState::from_options(Arc::new(cli_options)).await?; - let mut inner_loader = ps.create_graph_loader()?; + let mut inner_loader = ps.create_graph_loader(); let mut loader = crate::lsp::documents::OpenDocumentsGraphLoader { inner_loader: &mut inner_loader, open_docs: &open_docs, @@ -191,11 +192,23 @@ impl LanguageServer { match params.map(serde_json::from_value) { Some(Ok(params)) => { // do as much as possible in a read, then do a write outside - let result = { + let maybe_cache_result = { let inner = self.0.read().await; // ensure dropped - inner.prepare_cache(params)? + match inner.prepare_cache(params) { + Ok(maybe_cache_result) => maybe_cache_result, + Err(err) => { + self + .0 + .read() + .await + .client + .show_message(MessageType::WARNING, err) + .await; + return Err(LspError::internal_error()); + } + } }; - if let Some(result) = result { + if let Some(result) = maybe_cache_result { let cli_options = result.cli_options; let roots = result.roots; let open_docs = result.open_docs; @@ -2993,7 +3006,7 @@ impl Inner { fn prepare_cache( &self, params: lsp_custom::CacheParams, - ) -> LspResult> { + ) -> Result, AnyError> { let referrer = self.url_map.normalize_url(¶ms.referrer.uri); if !self.is_diagnosable(&referrer) { return Ok(None); @@ -3021,12 +3034,13 @@ impl Inner { unstable: true, ..Default::default() }, + std::env::current_dir().with_context(|| "Failed getting cwd.")?, self.maybe_config_file.clone(), // TODO(#16510): add support for lockfile None, // TODO(bartlomieju): handle package.json dependencies here None, - ); + )?; cli_options.set_import_map_specifier(self.maybe_import_map_uri.clone()); let open_docs = self.documents.documents(true, true); diff --git a/cli/proc_state.rs b/cli/proc_state.rs index 9f1e7320c9..2070b14c67 100644 --- a/cli/proc_state.rs +++ b/cli/proc_state.rs @@ -224,9 +224,7 @@ impl ProcState { let npm_resolver = NpmPackageResolver::new_with_maybe_lockfile( npm_cache.clone(), api, - cli_options - .resolve_local_node_modules_folder() - .with_context(|| "Resolving local node_modules folder.")?, + cli_options.node_modules_dir_path(), cli_options.get_npm_resolution_snapshot(), lockfile.as_ref().cloned(), ) @@ -329,11 +327,7 @@ impl ProcState { self.file_fetcher.clone(), root_permissions, dynamic_permissions, - self - .options - .resolve_local_node_modules_folder() - .with_context(|| "Resolving local node_modules folder.")? - .map(|path| ModuleSpecifier::from_file_path(path).unwrap()), + self.options.node_modules_dir_specifier(), ); let maybe_imports = self.options.to_maybe_imports()?; let graph_resolver = self.resolver.as_graph_resolver(); @@ -632,25 +626,21 @@ impl ProcState { } /// Creates the default loader used for creating a graph. - pub fn create_graph_loader(&self) -> Result { - Ok(cache::FetchCacher::new( + pub fn create_graph_loader(&self) -> cache::FetchCacher { + cache::FetchCacher::new( self.emit_cache.clone(), self.file_fetcher.clone(), PermissionsContainer::allow_all(), PermissionsContainer::allow_all(), - self - .options - .resolve_local_node_modules_folder() - .with_context(|| "Resolving local node_modules folder.")? - .map(|path| ModuleSpecifier::from_file_path(path).unwrap()), - )) + self.options.node_modules_dir_specifier(), + ) } pub async fn create_graph( &self, roots: Vec, ) -> Result { - let mut cache = self.create_graph_loader()?; + let mut cache = self.create_graph_loader(); self.create_graph_with_loader(roots, &mut cache).await } diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index f30e7ce698..7dbf9c74c0 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -2753,6 +2753,8 @@ itest!(package_json_auto_discovered_for_local_script_log { args: "run -L debug -A no_deno_json/main.ts", output: "run/with_package_json/no_deno_json/main.out", maybe_cwd: Some("run/with_package_json/"), + // prevent creating a node_modules dir in the code directory + copy_temp_dir: Some("run/with_package_json/"), envs: env_vars_for_npm_tests_no_sync_download(), http_server: true, }); @@ -2764,16 +2766,29 @@ itest!( args: "run -L debug with_stop/some/nested/dir/main.ts", output: "run/with_package_json/with_stop/main.out", maybe_cwd: Some("run/with_package_json/"), + copy_temp_dir: Some("run/with_package_json/"), envs: env_vars_for_npm_tests_no_sync_download(), http_server: true, exit_code: 1, } ); +itest!( + package_json_auto_discovered_node_modules_relative_package_json { + args: "run -A main.js", + output: "run/with_package_json/no_deno_json/sub_dir/main.out", + maybe_cwd: Some("run/with_package_json/no_deno_json/sub_dir"), + copy_temp_dir: Some("run/with_package_json/"), + envs: env_vars_for_npm_tests_no_sync_download(), + http_server: true, + } +); + itest!(package_json_auto_discovered_for_npm_binary { args: "run -L debug -A npm:@denotest/bin/cli-esm this is a test", output: "run/with_package_json/npm_binary/main.out", maybe_cwd: Some("run/with_package_json/npm_binary/"), + copy_temp_dir: Some("run/with_package_json/"), envs: env_vars_for_npm_tests_no_sync_download(), http_server: true, }); diff --git a/cli/tests/testdata/run/with_package_json/no_deno_json/sub_dir/main.js b/cli/tests/testdata/run/with_package_json/no_deno_json/sub_dir/main.js new file mode 100644 index 0000000000..2976532117 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/no_deno_json/sub_dir/main.js @@ -0,0 +1,2 @@ +console.log(Deno.cwd()); +console.log(Deno.statSync("../node_modules")); diff --git a/cli/tests/testdata/run/with_package_json/no_deno_json/sub_dir/main.out b/cli/tests/testdata/run/with_package_json/no_deno_json/sub_dir/main.out new file mode 100644 index 0000000000..0ec7919604 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/no_deno_json/sub_dir/main.out @@ -0,0 +1,7 @@ +Download http://[WILDCARD] +[WILDCARD]sub_dir +{ + [WILDCARD] + isDirectory: true, + [WILDCARD] +} diff --git a/cli/tools/info.rs b/cli/tools/info.rs index d120cb89f8..8a7f4b6b98 100644 --- a/cli/tools/info.rs +++ b/cli/tools/info.rs @@ -34,7 +34,7 @@ pub async fn info(flags: Flags, info_flags: InfoFlags) -> Result<(), AnyError> { let ps = ProcState::build(flags).await?; if let Some(specifier) = info_flags.file { let specifier = resolve_url_or_path(&specifier)?; - let mut loader = ps.create_graph_loader()?; + let mut loader = ps.create_graph_loader(); loader.enable_loading_cache_info(); // for displaying the cache information let graph = ps .create_graph_with_loader(vec![specifier], &mut loader) diff --git a/cli/tools/repl/session.rs b/cli/tools/repl/session.rs index cb3862a631..e9ddd09b1a 100644 --- a/cli/tools/repl/session.rs +++ b/cli/tools/repl/session.rs @@ -463,7 +463,7 @@ impl ReplSession { if !self.has_initialized_node_runtime { deno_node::initialize_runtime( &mut self.worker.js_runtime, - self.proc_state.options.node_modules_dir(), + self.proc_state.options.has_node_modules_dir(), ) .await?; self.has_initialized_node_runtime = true; diff --git a/cli/worker.rs b/cli/worker.rs index f8a9f54bb2..0002161029 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -304,7 +304,7 @@ impl CliMainWorker { async fn initialize_main_module_for_node(&mut self) -> Result<(), AnyError> { deno_node::initialize_runtime( &mut self.worker.js_runtime, - self.ps.options.node_modules_dir(), + self.ps.options.has_node_modules_dir(), ) .await?; if let DenoSubcommand::Run(flags) = self.ps.options.sub_command() { @@ -631,7 +631,7 @@ fn create_web_worker_pre_execute_module_callback( if ps.npm_resolver.has_packages() { deno_node::initialize_runtime( &mut worker.js_runtime, - ps.options.node_modules_dir(), + ps.options.has_node_modules_dir(), ) .await?; } diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index 555c26ffef..25faa1633e 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -31,6 +31,7 @@ use std::mem::replace; use std::net::SocketAddr; use std::ops::Deref; use std::ops::DerefMut; +use std::path::Path; use std::path::PathBuf; use std::pin::Pin; use std::process::Child; @@ -1923,12 +1924,17 @@ pub struct CheckOutputIntegrationTest<'a> { pub envs: Vec<(String, String)>, pub env_clear: bool, pub temp_cwd: bool, - // Relative to "testdata" directory + /// Copies the files at the specified directory in the "testdata" directory + /// to the temp folder and runs the test from there. This is useful when + /// the test creates files in the testdata directory (ex. a node_modules folder) + pub copy_temp_dir: Option<&'a str>, + /// Relative to "testdata" directory pub maybe_cwd: Option<&'a str>, } impl<'a> CheckOutputIntegrationTest<'a> { pub fn run(&self) { + let deno_dir = new_deno_dir(); // keep this alive for the test let args = if self.args_vec.is_empty() { std::borrow::Cow::Owned(self.args.split_whitespace().collect::>()) } else { @@ -1938,7 +1944,15 @@ impl<'a> CheckOutputIntegrationTest<'a> { ); std::borrow::Cow::Borrowed(&self.args_vec) }; - let testdata_dir = testdata_path(); + let testdata_dir = if let Some(temp_copy_dir) = &self.copy_temp_dir { + let test_data_path = testdata_path().join(temp_copy_dir); + let temp_copy_dir = deno_dir.path().join(temp_copy_dir); + std::fs::create_dir_all(&temp_copy_dir).unwrap(); + copy_dir_recursive(&test_data_path, &temp_copy_dir).unwrap(); + deno_dir.path().to_owned() + } else { + testdata_path() + }; let args = args .iter() .map(|arg| arg.replace("$TESTDATA", &testdata_dir.to_string_lossy())) @@ -1953,7 +1967,6 @@ impl<'a> CheckOutputIntegrationTest<'a> { }; let (mut reader, writer) = pipe().unwrap(); - let deno_dir = new_deno_dir(); // keep this alive for the test let mut command = deno_cmd_with_deno_dir(&deno_dir); let cwd = if self.temp_cwd { deno_dir.path().to_owned() @@ -2328,6 +2341,37 @@ pub fn parse_max_mem(output: &str) -> Option { None } +/// Copies a directory to another directory. +/// +/// Note: Does not handle symlinks. +pub fn copy_dir_recursive(from: &Path, to: &Path) -> Result<(), anyhow::Error> { + use anyhow::Context; + + std::fs::create_dir_all(to) + .with_context(|| format!("Creating {}", to.display()))?; + let read_dir = std::fs::read_dir(from) + .with_context(|| format!("Reading {}", from.display()))?; + + for entry in read_dir { + let entry = entry?; + let file_type = entry.file_type()?; + let new_from = from.join(entry.file_name()); + let new_to = to.join(entry.file_name()); + + if file_type.is_dir() { + copy_dir_recursive(&new_from, &new_to).with_context(|| { + format!("Dir {} to {}", new_from.display(), new_to.display()) + })?; + } else if file_type.is_file() { + std::fs::copy(&new_from, &new_to).with_context(|| { + format!("Copying {} to {}", new_from.display(), new_to.display()) + })?; + } + } + + Ok(()) +} + #[cfg(test)] mod tests { use super::*;