From 315dcaaa14811fc5fa8dc4e28ddc744b6ff67b09 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Thu, 21 Mar 2024 10:07:18 -0700 Subject: [PATCH 01/18] feat(cli): v8 code cache --- cli/args/flags.rs | 10 ++ cli/args/mod.rs | 4 + cli/cache/caches.rs | 15 +++ cli/cache/code_cache.rs | 196 +++++++++++++++++++++++++++++++++ cli/cache/deno_dir.rs | 6 + cli/cache/mod.rs | 2 + cli/factory.rs | 21 ++++ cli/module_loader.rs | 131 +++++++++++++++++++--- cli/standalone/mod.rs | 2 + cli/worker.rs | 5 + runtime/code_cache.rs | 29 +++++ runtime/lib.rs | 1 + runtime/worker.rs | 23 ++++ tests/integration/run_tests.rs | 50 +++++++++ 14 files changed, 481 insertions(+), 14 deletions(-) create mode 100644 cli/cache/code_cache.rs create mode 100644 runtime/code_cache.rs diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 283ebc9a31ca0..0904e9391c0df 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -486,6 +486,7 @@ pub struct Flags { pub unstable_config: UnstableConfig, pub unsafely_ignore_certificate_errors: Option>, pub v8_flags: Vec, + pub no_code_cache: bool, } fn join_paths(allowlist: &[String], d: &str) -> String { @@ -932,6 +933,8 @@ pub fn flags_from_vec(args: Vec) -> clap::error::Result { flags.unstable_config.sloppy_imports = matches.get_flag("unstable-sloppy-imports"); + flags.no_code_cache = matches.get_flag("no-code-cache"); + if matches.get_flag("quiet") { flags.log_level = Some(Level::Error); } else if let Some(log_level) = matches.get_one::("log-level") { @@ -1066,6 +1069,13 @@ fn clap_root() -> Command { .value_parser(FalseyValueParser::new()) .action(ArgAction::SetTrue) .global(true), + ) + .arg( + Arg::new("no-code-cache") + .long("no-code-cache") + .help("Disable V8 code cache feature") + .action(ArgAction::SetTrue) + .global(true), ); for (flag_name, help, _) in crate::UNSTABLE_GRANULAR_FLAGS { diff --git a/cli/args/mod.rs b/cli/args/mod.rs index cb4473ca2600a..35f4052c2a767 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -1608,6 +1608,10 @@ impl CliOptions { &self.flags.v8_flags } + pub fn no_code_cache(&self) -> bool { + self.flags.no_code_cache + } + pub fn watch_paths(&self) -> Vec { let mut full_paths = Vec::new(); if let DenoSubcommand::Run(RunFlags { diff --git a/cli/cache/caches.rs b/cli/cache/caches.rs index dc97f02d559e5..1be14b53bbc22 100644 --- a/cli/cache/caches.rs +++ b/cli/cache/caches.rs @@ -8,6 +8,7 @@ use once_cell::sync::OnceCell; use super::cache_db::CacheDB; use super::cache_db::CacheDBConfiguration; use super::check::TYPE_CHECK_CACHE_DB; +use super::code_cache::CODE_CACHE_DB; use super::deno_dir::DenoDirProvider; use super::fast_check::FAST_CHECK_CACHE_DB; use super::incremental::INCREMENTAL_CACHE_DB; @@ -22,6 +23,7 @@ pub struct Caches { fast_check_db: OnceCell, node_analysis_db: OnceCell, type_checking_cache_db: OnceCell, + code_cache_db: OnceCell, } impl Caches { @@ -34,6 +36,7 @@ impl Caches { fast_check_db: Default::default(), node_analysis_db: Default::default(), type_checking_cache_db: Default::default(), + code_cache_db: Default::default(), } } @@ -124,4 +127,16 @@ impl Caches { .map(|dir| dir.type_checking_cache_db_file_path()), ) } + + pub fn code_cache_db(&self) -> CacheDB { + Self::make_db( + &self.code_cache_db, + &CODE_CACHE_DB, + self + .dir_provider + .get_or_create() + .ok() + .map(|dir| dir.code_cache_db_file_path()), + ) + } } diff --git a/cli/cache/code_cache.rs b/cli/cache/code_cache.rs new file mode 100644 index 0000000000000..02db4e4a21b91 --- /dev/null +++ b/cli/cache/code_cache.rs @@ -0,0 +1,196 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_runtime::code_cache; +use deno_runtime::deno_webstorage::rusqlite::params; + +use super::cache_db::CacheDB; +use super::cache_db::CacheDBConfiguration; +use super::cache_db::CacheFailure; + +pub static CODE_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration { + table_initializer: "CREATE TABLE IF NOT EXISTS codecache ( + specifier TEXT NOT NULL, + type TEXT NOT NULL, + data BLOB NOT NULL, + PRIMARY KEY (specifier, type) + );", + on_version_change: "DELETE FROM codecache;", + preheat_queries: &[], + on_failure: CacheFailure::Blackhole, +}; + +#[derive(Clone)] +pub struct CodeCache { + inner: CodeCacheInner, +} + +impl CodeCache { + pub fn new(db: CacheDB) -> Self { + Self { + inner: CodeCacheInner::new(db), + } + } + + fn ensure_ok(res: Result) -> T { + match res { + Ok(x) => x, + Err(err) => { + // TODO(mmastrac): This behavior was inherited from before the refactoring but it probably makes sense to move it into the cache + // at some point. + // should never error here, but if it ever does don't fail + if cfg!(debug_assertions) { + panic!("Error using code cache: {err:#}"); + } else { + log::debug!("Error using code cache: {:#}", err); + } + T::default() + } + } + } + + pub fn get_sync( + &self, + specifier: &str, + code_cache_type: code_cache::CodeCacheType, + ) -> Option> { + Self::ensure_ok(self.inner.get_sync(specifier, code_cache_type)) + } + + pub fn set_sync( + &self, + specifier: &str, + code_cache_type: code_cache::CodeCacheType, + data: &[u8], + ) { + Self::ensure_ok(self.inner.set_sync(specifier, code_cache_type, data)); + } +} + +impl code_cache::CodeCache for CodeCache { + fn get_sync( + &self, + specifier: &str, + code_cache_type: code_cache::CodeCacheType, + ) -> Option> { + self.get_sync(specifier, code_cache_type) + } + + fn set_sync( + &self, + specifier: &str, + code_cache_type: code_cache::CodeCacheType, + data: &[u8], + ) { + self.set_sync(specifier, code_cache_type, data); + } +} + +#[derive(Clone)] +struct CodeCacheInner { + conn: CacheDB, +} + +impl CodeCacheInner { + pub fn new(conn: CacheDB) -> Self { + Self { conn } + } + + pub fn get_sync( + &self, + specifier: &str, + code_cache_type: code_cache::CodeCacheType, + ) -> Result>, AnyError> { + let query = " + SELECT + data + FROM + codecache + WHERE + specifier=?1 AND type=?2 + LIMIT 1"; + self.conn.query_row( + query, + params![specifier, code_cache_type.as_str()], + |row| { + let value: Vec = row.get(0)?; + Ok(value) + }, + ) + } + + pub fn set_sync( + &self, + specifier: &str, + code_cache_type: code_cache::CodeCacheType, + data: &[u8], + ) -> Result<(), AnyError> { + let sql = " + INSERT OR REPLACE INTO + codecache (specifier, type, data) + VALUES + (?1, ?2, ?3)"; + self + .conn + .execute(sql, params![specifier, code_cache_type.as_str(), data])?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + pub fn end_to_end() { + let conn = CacheDB::in_memory(&CODE_CACHE_DB, "1.0.0"); + let cache = CodeCacheInner::new(conn); + + assert!(cache + .get_sync("file:///foo/bar.js", code_cache::CodeCacheType::EsModule) + .unwrap() + .is_none()); + let data_esm = vec![1, 2, 3]; + cache + .set_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::EsModule, + &data_esm, + ) + .unwrap(); + assert_eq!( + cache + .get_sync("file:///foo/bar.js", code_cache::CodeCacheType::EsModule) + .unwrap() + .unwrap(), + data_esm + ); + + assert!(cache + .get_sync("file:///foo/bar.js", code_cache::CodeCacheType::Script) + .unwrap() + .is_none()); + let data_script = vec![1, 2, 3]; + cache + .set_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::Script, + &data_script, + ) + .unwrap(); + assert_eq!( + cache + .get_sync("file:///foo/bar.js", code_cache::CodeCacheType::Script) + .unwrap() + .unwrap(), + data_script + ); + assert_eq!( + cache + .get_sync("file:///foo/bar.js", code_cache::CodeCacheType::EsModule) + .unwrap() + .unwrap(), + data_esm + ); + } +} diff --git a/cli/cache/deno_dir.rs b/cli/cache/deno_dir.rs index ee8c35684e6db..6b5f3aa460c69 100644 --- a/cli/cache/deno_dir.rs +++ b/cli/cache/deno_dir.rs @@ -142,6 +142,12 @@ impl DenoDir { self.root.join("npm") } + /// Path for the V8 code cache. + pub fn code_cache_db_file_path(&self) -> PathBuf { + // bump this version name to invalidate the entire cache + self.root.join("code_cache_v1") + } + /// Path used for the REPL history file. /// Can be overridden or disabled by setting `DENO_REPL_HISTORY` environment variable. pub fn repl_history_file_path(&self) -> Option { diff --git a/cli/cache/mod.rs b/cli/cache/mod.rs index 229a9cb544ced..a511792132e9c 100644 --- a/cli/cache/mod.rs +++ b/cli/cache/mod.rs @@ -25,6 +25,7 @@ use std::time::SystemTime; mod cache_db; mod caches; mod check; +mod code_cache; mod common; mod deno_dir; mod disk_cache; @@ -37,6 +38,7 @@ mod parsed_source; pub use caches::Caches; pub use check::TypeCheckCache; +pub use code_cache::CodeCache; pub use common::FastInsecureHasher; pub use deno_dir::DenoDir; pub use deno_dir::DenoDirProvider; diff --git a/cli/factory.rs b/cli/factory.rs index a2755c1290e6d..decf1ccd2cc36 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -9,6 +9,7 @@ use crate::args::PackageJsonDepsProvider; use crate::args::StorageKeyResolver; use crate::args::TsConfigType; use crate::cache::Caches; +use crate::cache::CodeCache; use crate::cache::DenoDir; use crate::cache::DenoDirProvider; use crate::cache::EmitCache; @@ -178,6 +179,7 @@ struct CliFactoryServices { cjs_resolutions: Deferred>, cli_node_resolver: Deferred>, feature_checker: Deferred>, + code_cache: Deferred>, } pub struct CliFactory { @@ -226,6 +228,9 @@ impl CliFactory { _ = caches.fast_check_db(); _ = caches.type_checking_cache_db(); } + if !self.options.no_code_cache() { + _ = caches.code_cache_db(); + } } _ => {} } @@ -534,6 +539,12 @@ impl CliFactory { }) } + pub fn code_cache(&self) -> Result<&Arc, AnyError> { + self.services.code_cache.get_or_try_init(|| { + Ok(Arc::new(CodeCache::new(self.caches()?.code_cache_db()))) + }) + } + pub fn parsed_source_cache(&self) -> &Arc { self .services @@ -782,6 +793,11 @@ impl CliFactory { fs.clone(), cli_node_resolver.clone(), ), + if self.options.no_code_cache() { + None + } else { + Some(self.code_cache()?.clone()) + }, )), self.root_cert_store_provider().clone(), self.fs().clone(), @@ -796,6 +812,11 @@ impl CliFactory { // self.options.disable_deprecated_api_warning, true, self.options.verbose_deprecated_api_warning, + if self.options.no_code_cache() { + None + } else { + Some(self.code_cache()?.clone()) + }, )) } diff --git a/cli/module_loader.rs b/cli/module_loader.rs index 940cfbd8ebd3b..ddbf5926d6c1a 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -4,6 +4,7 @@ use crate::args::jsr_url; use crate::args::CliOptions; use crate::args::DenoSubcommand; use crate::args::TsTypeLib; +use crate::cache::CodeCache; use crate::cache::ParsedSourceCache; use crate::emit::Emitter; use crate::graph_util::graph_lock_or_exit; @@ -30,10 +31,12 @@ use deno_core::error::custom_error; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::futures::future::FutureExt; +use deno_core::futures::future::LocalBoxFuture; use deno_core::futures::Future; use deno_core::parking_lot::Mutex; use deno_core::resolve_url; use deno_core::resolve_url_or_path; +use deno_core::unsync::spawn_blocking; use deno_core::ModuleCodeString; use deno_core::ModuleLoader; use deno_core::ModuleSource; @@ -50,11 +53,14 @@ use deno_graph::JsonModule; use deno_graph::Module; use deno_graph::Resolution; use deno_lockfile::Lockfile; +use deno_runtime::code_cache; use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::permissions::PermissionsContainer; use deno_semver::npm::NpmPackageReqReference; use deno_terminal::colors; use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::HashMap; use std::pin::Pin; use std::rc::Rc; use std::str; @@ -311,6 +317,7 @@ struct SharedCliModuleLoaderState { resolver: Arc, node_resolver: Arc, npm_module_loader: NpmModuleLoader, + code_cache: Option>, } pub struct CliModuleLoaderFactory { @@ -328,6 +335,7 @@ impl CliModuleLoaderFactory { resolver: Arc, node_resolver: Arc, npm_module_loader: NpmModuleLoader, + code_cache: Option>, ) -> Self { Self { shared: Arc::new(SharedCliModuleLoaderState { @@ -348,6 +356,7 @@ impl CliModuleLoaderFactory { resolver, node_resolver, npm_module_loader, + code_cache, }), } } @@ -363,6 +372,7 @@ impl CliModuleLoaderFactory { root_permissions, dynamic_permissions, shared: self.shared.clone(), + pending_code_cache_futs: Rc::new(RefCell::new(HashMap::new())), }) } } @@ -399,6 +409,9 @@ impl ModuleLoaderFactory for CliModuleLoaderFactory { } } +type PendingCodeCacheFuture = + LocalBoxFuture<'static, Option>>; + struct CliModuleLoader { lib: TsTypeLib, /// The initial set of permissions used to resolve the static imports in the @@ -409,16 +422,17 @@ struct CliModuleLoader { /// "root permissions" for Web Worker. dynamic_permissions: PermissionsContainer, shared: Arc, + pending_code_cache_futs: Rc>>, } impl CliModuleLoader { - fn load_sync( + fn load( &self, specifier: &ModuleSpecifier, maybe_referrer: Option<&ModuleSpecifier>, is_dynamic: bool, requested_module_type: RequestedModuleType, - ) -> Result { + ) -> Result { let permissions = if is_dynamic { &self.dynamic_permissions } else { @@ -458,13 +472,57 @@ impl CliModuleLoader { return Err(generic_error("Attempted to load JSON module without specifying \"type\": \"json\" attribute in the import statement.")); } - Ok(ModuleSource::new_with_redirect( - module_type, - ModuleSourceCode::String(code), - specifier, - &code_source.found_url, - None, - )) + // Check if there is code cache available for this module. + let pending_code_cache_fut = if module_type == ModuleType::JavaScript { + self + .pending_code_cache_futs + .borrow_mut() + .remove(specifier.as_str()) + .or_else(|| { + self.shared.code_cache.as_ref().cloned().map(|cache| { + let specifier = specifier.clone(); + spawn_blocking(move || { + cache + .get_sync( + specifier.as_str(), + code_cache::CodeCacheType::EsModule, + ) + .map(Cow::from) + }) + .map(|r| r.ok().flatten()) + .boxed_local() + }) + }) + } else { + None + }; + + if let Some(fut) = pending_code_cache_fut { + let specifier = specifier.clone(); + Ok(deno_core::ModuleLoadResponse::Async( + async move { + let code_cache = fut.await; + Ok(ModuleSource::new_with_redirect( + module_type, + ModuleSourceCode::String(code), + &specifier, + &code_source.found_url, + code_cache, + )) + } + .boxed_local(), + )) + } else { + Ok(deno_core::ModuleLoadResponse::Sync(Ok( + ModuleSource::new_with_redirect( + module_type, + ModuleSourceCode::String(code), + specifier, + &code_source.found_url, + None, + ), + ))) + } } fn resolve_referrer( @@ -638,15 +696,15 @@ impl ModuleLoader for CliModuleLoader { is_dynamic: bool, requested_module_type: RequestedModuleType, ) -> deno_core::ModuleLoadResponse { - // NOTE: this block is async only because of `deno_core` interface - // requirements; module was already loaded when constructing module graph - // during call to `prepare_load` so we can load it synchronously. - deno_core::ModuleLoadResponse::Sync(self.load_sync( + match self.load( specifier, maybe_referrer, is_dynamic, requested_module_type, - )) + ) { + Ok(res) => res, + Err(err) => deno_core::ModuleLoadResponse::Sync(Err(err)), + } } fn prepare_load( @@ -671,6 +729,25 @@ impl ModuleLoader for CliModuleLoader { }; let lib = self.lib; + // Try to preload code cache for this module. + if let Some(cache) = self.shared.code_cache.as_ref().cloned() { + let specifier_clone = specifier.clone(); + let pending_code_cache_fut = spawn_blocking(move || { + cache + .get_sync( + specifier_clone.as_str(), + code_cache::CodeCacheType::EsModule, + ) + .map(Cow::from) + }) + .map(|r| r.ok().flatten()) + .boxed_local(); + self + .pending_code_cache_futs + .borrow_mut() + .insert(specifier.to_string(), pending_code_cache_fut); + } + async move { module_load_preparer .prepare_module_load(vec![specifier], is_dynamic, lib, root_permissions) @@ -678,6 +755,32 @@ impl ModuleLoader for CliModuleLoader { } .boxed_local() } + + fn code_cache_ready( + &self, + specifier: &ModuleSpecifier, + code_cache: &[u8], + ) -> Pin>> { + self + .shared + .code_cache + .as_ref() + .cloned() + .map(|cache| { + let specifier = specifier.clone(); + let code_cache = code_cache.to_vec(); + spawn_blocking(move || { + cache.set_sync( + specifier.as_str(), + code_cache::CodeCacheType::EsModule, + &code_cache, + ); + }) + .map(|_| {}) + .boxed_local() + }) + .unwrap_or_else(|| async {}.boxed_local()) + } } struct CliSourceMapGetter { diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index dde70f63a101f..a3ab34a580a1c 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -572,6 +572,8 @@ pub async fn run( // metadata.disable_deprecated_api_warning, true, false, + // Code cache is not supported for standalone binary yet. + None, ); v8_set_flags(construct_v8_flags(&[], &metadata.v8_flags, vec![])); diff --git a/cli/worker.rs b/cli/worker.rs index 40c3cfcc34c3e..2b3c98c848510 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -23,6 +23,7 @@ use deno_core::PollEventLoopOptions; use deno_core::SharedArrayBufferStore; use deno_core::SourceMapGetter; use deno_lockfile::Lockfile; +use deno_runtime::code_cache; use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel; use deno_runtime::deno_fs; use deno_runtime::deno_node; @@ -147,6 +148,7 @@ struct SharedWorkerState { enable_future_features: bool, disable_deprecated_api_warning: bool, verbose_deprecated_api_warning: bool, + code_cache: Option>, } impl SharedWorkerState { @@ -425,6 +427,7 @@ impl CliMainWorkerFactory { enable_future_features: bool, disable_deprecated_api_warning: bool, verbose_deprecated_api_warning: bool, + code_cache: Option>, ) -> Self { Self { shared: Arc::new(SharedWorkerState { @@ -448,6 +451,7 @@ impl CliMainWorkerFactory { enable_future_features, disable_deprecated_api_warning, verbose_deprecated_api_warning, + code_cache, }), } } @@ -642,6 +646,7 @@ impl CliMainWorkerFactory { stdio, feature_checker, skip_op_registration: shared.options.skip_op_registration, + code_cache: shared.code_cache.clone(), }; let mut worker = MainWorker::bootstrap_from_options( diff --git a/runtime/code_cache.rs b/runtime/code_cache.rs new file mode 100644 index 0000000000000..d8cb794d7fd97 --- /dev/null +++ b/runtime/code_cache.rs @@ -0,0 +1,29 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +pub enum CodeCacheType { + EsModule, + Script, +} + +impl CodeCacheType { + pub fn as_str(&self) -> &str { + match self { + Self::EsModule => "esmodule", + Self::Script => "script", + } + } +} + +pub trait CodeCache: Send + Sync { + fn get_sync( + &self, + specifier: &str, + code_cache_type: CodeCacheType, + ) -> Option>; + fn set_sync( + &self, + specifier: &str, + code_cache_type: CodeCacheType, + data: &[u8], + ); +} diff --git a/runtime/lib.rs b/runtime/lib.rs index 72fa1cef85938..f33e9b7e33857 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -26,6 +26,7 @@ pub use deno_webidl; pub use deno_websocket; pub use deno_webstorage; +pub mod code_cache; pub mod errors; pub mod fmt_errors; pub mod fs_util; diff --git a/runtime/worker.rs b/runtime/worker.rs index 2fb32c766bde3..3685093afde76 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -1,4 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::borrow::Cow; use std::collections::HashMap; use std::rc::Rc; use std::sync::atomic::AtomicBool; @@ -42,6 +43,8 @@ use deno_tls::RootCertStoreProvider; use deno_web::BlobStore; use log::debug; +use crate::code_cache::CodeCache; +use crate::code_cache::CodeCacheType; use crate::inspector_server::InspectorServer; use crate::ops; use crate::permissions::PermissionsContainer; @@ -189,6 +192,9 @@ pub struct WorkerOptions { pub compiled_wasm_module_store: Option, pub stdio: Stdio, pub feature_checker: Arc, + + /// V8 code cache for module and script source code. + pub code_cache: Option>, } impl Default for WorkerOptions { @@ -223,6 +229,7 @@ impl Default for WorkerOptions { bootstrap: Default::default(), stdio: Default::default(), feature_checker: Default::default(), + code_cache: Default::default(), } } } @@ -491,6 +498,22 @@ impl MainWorker { validate_import_attributes_cb: Some(Box::new( validate_import_attributes_callback, )), + enable_code_cache: options.code_cache.is_some(), + eval_context_code_cache_cbs: options.code_cache.map(|cache| { + let cache_clone = cache.clone(); + ( + Box::new(move |specifier: &str| { + Ok( + cache + .get_sync(specifier, CodeCacheType::Script) + .map(Cow::from), + ) + }) as Box _>, + Box::new(move |specifier: &str, data: &[u8]| { + cache_clone.set_sync(specifier, CodeCacheType::Script, data); + }) as Box, + ) + }), ..Default::default() }); diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs index 59163bfe81bdb..b37b878cceeed 100644 --- a/tests/integration/run_tests.rs +++ b/tests/integration/run_tests.rs @@ -5170,3 +5170,53 @@ fn run_etag_delete_source_cache() { "[WILDCARD]Cache body not found. Trying again without etag.[WILDCARD]", ); } + +#[test] +fn code_cache_test() { + let script = util::testdata_path().join("run/001_hello.js"); + let module_output_path = util::testdata_path().join("run/001_hello.js.out"); + let mut module_output_file = std::fs::File::open(module_output_path).unwrap(); + let mut module_output = String::new(); + module_output_file + .read_to_string(&mut module_output) + .unwrap(); + let prg = util::deno_exe_path(); + + { + let deno_dir = TempDir::new(); + let output = Command::new(prg.clone()) + .env("DENO_DIR", deno_dir.path()) + .current_dir(util::testdata_path()) + .arg("run") + .arg(script.to_string()) + .output() + .expect("Failed to spawn script"); + + let str_output = std::str::from_utf8(&output.stdout).unwrap(); + assert_eq!(module_output, str_output); + + // Check that the code cache database exists. + let code_cache_path = deno_dir.path().join("code_cache_v1"); + assert!(code_cache_path.exists()); + } + + // Rerun with --no-code-cache. + { + let deno_dir = TempDir::new(); + let output = Command::new(prg) + .env("DENO_DIR", deno_dir.path()) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--no-code-cache") + .arg(script.to_string()) + .output() + .expect("Failed to spawn script"); + + let str_output = std::str::from_utf8(&output.stdout).unwrap(); + assert_eq!(module_output, str_output); + + // Check that the code cache database exists. + let code_cache_path = deno_dir.path().join("code_cache_v1"); + assert!(!code_cache_path.exists()); + } +} From e27abeef65aa60626090cb6904e7c9da2720c6d4 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Sun, 31 Mar 2024 00:29:03 -0700 Subject: [PATCH 02/18] review comments --- cli/module_loader.rs | 134 +++++++++------------------------ cli/worker.rs | 2 +- runtime/worker.rs | 17 +++-- tests/integration/run_tests.rs | 52 +++++++++---- 4 files changed, 86 insertions(+), 119 deletions(-) diff --git a/cli/module_loader.rs b/cli/module_loader.rs index ddbf5926d6c1a..8171a85a5c630 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -31,12 +31,10 @@ use deno_core::error::custom_error; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::futures::future::FutureExt; -use deno_core::futures::future::LocalBoxFuture; use deno_core::futures::Future; use deno_core::parking_lot::Mutex; use deno_core::resolve_url; use deno_core::resolve_url_or_path; -use deno_core::unsync::spawn_blocking; use deno_core::ModuleCodeString; use deno_core::ModuleLoader; use deno_core::ModuleSource; @@ -59,8 +57,6 @@ use deno_runtime::permissions::PermissionsContainer; use deno_semver::npm::NpmPackageReqReference; use deno_terminal::colors; use std::borrow::Cow; -use std::cell::RefCell; -use std::collections::HashMap; use std::pin::Pin; use std::rc::Rc; use std::str; @@ -372,7 +368,6 @@ impl CliModuleLoaderFactory { root_permissions, dynamic_permissions, shared: self.shared.clone(), - pending_code_cache_futs: Rc::new(RefCell::new(HashMap::new())), }) } } @@ -409,9 +404,6 @@ impl ModuleLoaderFactory for CliModuleLoaderFactory { } } -type PendingCodeCacheFuture = - LocalBoxFuture<'static, Option>>; - struct CliModuleLoader { lib: TsTypeLib, /// The initial set of permissions used to resolve the static imports in the @@ -422,17 +414,16 @@ struct CliModuleLoader { /// "root permissions" for Web Worker. dynamic_permissions: PermissionsContainer, shared: Arc, - pending_code_cache_futs: Rc>>, } impl CliModuleLoader { - fn load( + fn load_sync( &self, specifier: &ModuleSpecifier, maybe_referrer: Option<&ModuleSpecifier>, is_dynamic: bool, requested_module_type: RequestedModuleType, - ) -> Result { + ) -> Result { let permissions = if is_dynamic { &self.dynamic_permissions } else { @@ -472,57 +463,29 @@ impl CliModuleLoader { return Err(generic_error("Attempted to load JSON module without specifying \"type\": \"json\" attribute in the import statement.")); } - // Check if there is code cache available for this module. - let pending_code_cache_fut = if module_type == ModuleType::JavaScript { - self - .pending_code_cache_futs - .borrow_mut() - .remove(specifier.as_str()) - .or_else(|| { - self.shared.code_cache.as_ref().cloned().map(|cache| { - let specifier = specifier.clone(); - spawn_blocking(move || { - cache - .get_sync( - specifier.as_str(), - code_cache::CodeCacheType::EsModule, - ) - .map(Cow::from) - }) - .map(|r| r.ok().flatten()) - .boxed_local() + let code_cache = if module_type == ModuleType::JavaScript { + self.shared.code_cache.as_ref().and_then(|cache| { + cache + .get_sync(specifier.as_str(), code_cache::CodeCacheType::EsModule) + .map(Cow::from) + .inspect(|_| { + log::debug!( + "v8 code cache hit for ES module: {}", + specifier.to_string() + ); }) - }) + }) } else { None }; - if let Some(fut) = pending_code_cache_fut { - let specifier = specifier.clone(); - Ok(deno_core::ModuleLoadResponse::Async( - async move { - let code_cache = fut.await; - Ok(ModuleSource::new_with_redirect( - module_type, - ModuleSourceCode::String(code), - &specifier, - &code_source.found_url, - code_cache, - )) - } - .boxed_local(), - )) - } else { - Ok(deno_core::ModuleLoadResponse::Sync(Ok( - ModuleSource::new_with_redirect( - module_type, - ModuleSourceCode::String(code), - specifier, - &code_source.found_url, - None, - ), - ))) - } + Ok(ModuleSource::new_with_redirect( + module_type, + ModuleSourceCode::String(code), + specifier, + &code_source.found_url, + code_cache, + )) } fn resolve_referrer( @@ -696,15 +659,15 @@ impl ModuleLoader for CliModuleLoader { is_dynamic: bool, requested_module_type: RequestedModuleType, ) -> deno_core::ModuleLoadResponse { - match self.load( + // NOTE: this block is async only because of `deno_core` interface + // requirements; module was already loaded when constructing module graph + // during call to `prepare_load` so we can load it synchronously. + deno_core::ModuleLoadResponse::Sync(self.load_sync( specifier, maybe_referrer, is_dynamic, requested_module_type, - ) { - Ok(res) => res, - Err(err) => deno_core::ModuleLoadResponse::Sync(Err(err)), - } + )) } fn prepare_load( @@ -729,25 +692,6 @@ impl ModuleLoader for CliModuleLoader { }; let lib = self.lib; - // Try to preload code cache for this module. - if let Some(cache) = self.shared.code_cache.as_ref().cloned() { - let specifier_clone = specifier.clone(); - let pending_code_cache_fut = spawn_blocking(move || { - cache - .get_sync( - specifier_clone.as_str(), - code_cache::CodeCacheType::EsModule, - ) - .map(Cow::from) - }) - .map(|r| r.ok().flatten()) - .boxed_local(); - self - .pending_code_cache_futs - .borrow_mut() - .insert(specifier.to_string(), pending_code_cache_fut); - } - async move { module_load_preparer .prepare_module_load(vec![specifier], is_dynamic, lib, root_permissions) @@ -761,25 +705,15 @@ impl ModuleLoader for CliModuleLoader { specifier: &ModuleSpecifier, code_cache: &[u8], ) -> Pin>> { - self - .shared - .code_cache - .as_ref() - .cloned() - .map(|cache| { - let specifier = specifier.clone(); - let code_cache = code_cache.to_vec(); - spawn_blocking(move || { - cache.set_sync( - specifier.as_str(), - code_cache::CodeCacheType::EsModule, - &code_cache, - ); - }) - .map(|_| {}) - .boxed_local() - }) - .unwrap_or_else(|| async {}.boxed_local()) + if let Some(cache) = self.shared.code_cache.as_ref() { + log::debug!("Updating v8 code cache for ES module: {}", specifier); + cache.set_sync( + specifier.as_str(), + code_cache::CodeCacheType::EsModule, + code_cache, + ); + } + async {}.boxed_local() } } diff --git a/cli/worker.rs b/cli/worker.rs index 2b3c98c848510..878fbf8e88304 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -646,7 +646,7 @@ impl CliMainWorkerFactory { stdio, feature_checker, skip_op_registration: shared.options.skip_op_registration, - code_cache: shared.code_cache.clone(), + v8_code_cache: shared.code_cache.clone(), }; let mut worker = MainWorker::bootstrap_from_options( diff --git a/runtime/worker.rs b/runtime/worker.rs index 3685093afde76..be85580ccecd4 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -194,7 +194,7 @@ pub struct WorkerOptions { pub feature_checker: Arc, /// V8 code cache for module and script source code. - pub code_cache: Option>, + pub v8_code_cache: Option>, } impl Default for WorkerOptions { @@ -229,7 +229,7 @@ impl Default for WorkerOptions { bootstrap: Default::default(), stdio: Default::default(), feature_checker: Default::default(), - code_cache: Default::default(), + v8_code_cache: Default::default(), } } } @@ -498,18 +498,25 @@ impl MainWorker { validate_import_attributes_cb: Some(Box::new( validate_import_attributes_callback, )), - enable_code_cache: options.code_cache.is_some(), - eval_context_code_cache_cbs: options.code_cache.map(|cache| { + enable_code_cache: options.v8_code_cache.is_some(), + eval_context_code_cache_cbs: options.v8_code_cache.map(|cache| { let cache_clone = cache.clone(); ( Box::new(move |specifier: &str| { Ok( cache .get_sync(specifier, CodeCacheType::Script) - .map(Cow::from), + .map(Cow::from) + .inspect(|_| { + log::debug!( + "v8 code cache hit for script: {}", + specifier.to_string() + ); + }), ) }) as Box _>, Box::new(move |specifier: &str, data: &[u8]| { + log::debug!("Updating code cache for script: {}", specifier); cache_clone.set_sync(specifier, CodeCacheType::Script, data); }) as Box, ) diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs index b37b878cceeed..4523aad8f952f 100644 --- a/tests/integration/run_tests.rs +++ b/tests/integration/run_tests.rs @@ -5174,49 +5174,75 @@ fn run_etag_delete_source_cache() { #[test] fn code_cache_test() { let script = util::testdata_path().join("run/001_hello.js"); - let module_output_path = util::testdata_path().join("run/001_hello.js.out"); - let mut module_output_file = std::fs::File::open(module_output_path).unwrap(); - let mut module_output = String::new(); - module_output_file - .read_to_string(&mut module_output) - .unwrap(); let prg = util::deno_exe_path(); + let deno_dir = TempDir::new(); + // First run with no prior cache. { - let deno_dir = TempDir::new(); let output = Command::new(prg.clone()) .env("DENO_DIR", deno_dir.path()) .current_dir(util::testdata_path()) .arg("run") + .arg("-Ldebug") .arg(script.to_string()) .output() .expect("Failed to spawn script"); let str_output = std::str::from_utf8(&output.stdout).unwrap(); - assert_eq!(module_output, str_output); + assert!(str_output.contains("Hello World")); + + let debug_output = std::str::from_utf8(&output.stderr).unwrap(); + // There should be no cache hits yet, and the cache should be created. + assert!(!debug_output.contains("v8 code cache hit")); + assert!(debug_output.contains( + format!("Updating v8 code cache for ES module: file://{}", script) + .as_str() + )); // Check that the code cache database exists. let code_cache_path = deno_dir.path().join("code_cache_v1"); assert!(code_cache_path.exists()); } + // 2nd run with cache. + { + let output = Command::new(prg.clone()) + .env("DENO_DIR", deno_dir.path()) + .current_dir(util::testdata_path()) + .arg("run") + .arg("-Ldebug") + .arg(script.to_string()) + .output() + .expect("Failed to spawn script"); + + let str_output = std::str::from_utf8(&output.stdout).unwrap(); + assert!(str_output.contains("Hello World")); + + let debug_output = std::str::from_utf8(&output.stderr).unwrap(); + // There should be a cache hit, and the cache should not be created. + assert!(debug_output.contains( + format!("v8 code cache hit for ES module: file://{}", script).as_str() + )); + assert!(!debug_output.contains("Updating v8 code cache")); + } + // Rerun with --no-code-cache. { - let deno_dir = TempDir::new(); let output = Command::new(prg) .env("DENO_DIR", deno_dir.path()) .current_dir(util::testdata_path()) .arg("run") + .arg("-Ldebug") .arg("--no-code-cache") .arg(script.to_string()) .output() .expect("Failed to spawn script"); let str_output = std::str::from_utf8(&output.stdout).unwrap(); - assert_eq!(module_output, str_output); + assert!(str_output.contains("Hello World")); - // Check that the code cache database exists. - let code_cache_path = deno_dir.path().join("code_cache_v1"); - assert!(!code_cache_path.exists()); + let debug_output = std::str::from_utf8(&output.stderr).unwrap(); + // There should be no cache used. + assert!(!debug_output.contains("v8 code cache")); } } From 0b117e36be3854d996a176a5a5b1ec013be5d4f7 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Sun, 31 Mar 2024 23:49:10 -0700 Subject: [PATCH 03/18] don't use code cache for coverage runs --- cli/args/flags.rs | 8 +++++++- cli/module_loader.rs | 4 ++-- runtime/worker.rs | 2 +- tests/integration/run_tests.rs | 10 +++++----- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index fe7e242580b52..fcf62de993557 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -3954,10 +3954,16 @@ fn test_parse(flags: &mut Flags, matches: &mut ArgMatches) { flags.log_level = Some(Level::Error); } + let coverage_dir = matches.remove_one::("coverage"); + if coverage_dir.is_some() { + // Don't use V8 code cache for code coverage runs. + flags.no_code_cache = true; + } + flags.subcommand = DenoSubcommand::Test(TestFlags { no_run, doc, - coverage_dir: matches.remove_one::("coverage"), + coverage_dir, fail_fast, files: FileFlags { include, ignore }, filter, diff --git a/cli/module_loader.rs b/cli/module_loader.rs index 8171a85a5c630..3fa81ce4d1822 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -470,7 +470,7 @@ impl CliModuleLoader { .map(Cow::from) .inspect(|_| { log::debug!( - "v8 code cache hit for ES module: {}", + "V8 code cache hit for ES module: {}", specifier.to_string() ); }) @@ -706,7 +706,7 @@ impl ModuleLoader for CliModuleLoader { code_cache: &[u8], ) -> Pin>> { if let Some(cache) = self.shared.code_cache.as_ref() { - log::debug!("Updating v8 code cache for ES module: {}", specifier); + log::debug!("Updating V8 code cache for ES module: {}", specifier); cache.set_sync( specifier.as_str(), code_cache::CodeCacheType::EsModule, diff --git a/runtime/worker.rs b/runtime/worker.rs index be85580ccecd4..193e25e8851a7 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -509,7 +509,7 @@ impl MainWorker { .map(Cow::from) .inspect(|_| { log::debug!( - "v8 code cache hit for script: {}", + "V8 code cache hit for script: {}", specifier.to_string() ); }), diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs index 4523aad8f952f..bcd8a6310ab81 100644 --- a/tests/integration/run_tests.rs +++ b/tests/integration/run_tests.rs @@ -5193,9 +5193,9 @@ fn code_cache_test() { let debug_output = std::str::from_utf8(&output.stderr).unwrap(); // There should be no cache hits yet, and the cache should be created. - assert!(!debug_output.contains("v8 code cache hit")); + assert!(!debug_output.contains("V8 code cache hit")); assert!(debug_output.contains( - format!("Updating v8 code cache for ES module: file://{}", script) + format!("Updating V8 code cache for ES module: file://{}", script) .as_str() )); @@ -5221,9 +5221,9 @@ fn code_cache_test() { let debug_output = std::str::from_utf8(&output.stderr).unwrap(); // There should be a cache hit, and the cache should not be created. assert!(debug_output.contains( - format!("v8 code cache hit for ES module: file://{}", script).as_str() + format!("V8 code cache hit for ES module: file://{}", script).as_str() )); - assert!(!debug_output.contains("Updating v8 code cache")); + assert!(!debug_output.contains("Updating V8 code cache")); } // Rerun with --no-code-cache. @@ -5243,6 +5243,6 @@ fn code_cache_test() { let debug_output = std::str::from_utf8(&output.stderr).unwrap(); // There should be no cache used. - assert!(!debug_output.contains("v8 code cache")); + assert!(!debug_output.contains("V8 code cache")); } } From 8d7d614c21f5ad6ebcbec51184dfcb5a4e09c46b Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Tue, 2 Apr 2024 10:16:38 -0700 Subject: [PATCH 04/18] fix tests --- cli/args/flags.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index fcf62de993557..26585ecbfead0 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -7494,6 +7494,7 @@ mod tests { no_prompt: true, no_npm: true, no_remote: true, + no_code_cache: true, location: Some(Url::parse("https://foo/").unwrap()), type_check_mode: TypeCheckMode::Local, allow_net: Some(vec![]), @@ -7873,6 +7874,7 @@ mod tests { }), type_check_mode: TypeCheckMode::Local, no_prompt: true, + no_code_cache: true, ..Flags::default() } ); From 6d43f0a48471ea26446ec9d6428bdcfa15ad3330 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Wed, 3 Apr 2024 14:56:04 -0700 Subject: [PATCH 05/18] include hash and timestamp into code cache --- cli/cache/cache_db.rs | 2 +- cli/cache/code_cache.rs | 191 ++++- cli/cache/module_info.rs | 17 + cli/factory.rs | 1 + cli/module_loader.rs | 62 +- cli/worker.rs | 2 + runtime/code_cache.rs | 4 + runtime/worker.rs | 56 +- tests/integration/run_tests.rs | 56 +- tests/unit/process_test.ts | 1306 ++++++++++++++++---------------- tests/util/std | 2 +- 11 files changed, 1002 insertions(+), 697 deletions(-) diff --git a/cli/cache/cache_db.rs b/cli/cache/cache_db.rs index 0613d52c66f44..e934b5eb93ae7 100644 --- a/cli/cache/cache_db.rs +++ b/cli/cache/cache_db.rs @@ -303,7 +303,7 @@ impl CacheDB { /// Query a row from the database with a mapping function. pub fn query_row( &self, - sql: &'static str, + sql: &str, params: impl Params, f: F, ) -> Result, AnyError> diff --git a/cli/cache/code_cache.rs b/cli/cache/code_cache.rs index 02db4e4a21b91..4a399f67881dd 100644 --- a/cli/cache/code_cache.rs +++ b/cli/cache/code_cache.rs @@ -2,6 +2,7 @@ use deno_core::error::AnyError; use deno_runtime::code_cache; +use deno_runtime::deno_webstorage::rusqlite; use deno_runtime::deno_webstorage::rusqlite::params; use super::cache_db::CacheDB; @@ -12,6 +13,8 @@ pub static CODE_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration { table_initializer: "CREATE TABLE IF NOT EXISTS codecache ( specifier TEXT NOT NULL, type TEXT NOT NULL, + source_hash TEXT, + source_timestamp INTEGER, data BLOB NOT NULL, PRIMARY KEY (specifier, type) );", @@ -53,17 +56,32 @@ impl CodeCache { &self, specifier: &str, code_cache_type: code_cache::CodeCacheType, + source_hash: Option<&str>, + source_timestamp: Option, ) -> Option> { - Self::ensure_ok(self.inner.get_sync(specifier, code_cache_type)) + Self::ensure_ok(self.inner.get_sync( + specifier, + code_cache_type, + source_hash, + source_timestamp, + )) } pub fn set_sync( &self, specifier: &str, code_cache_type: code_cache::CodeCacheType, + source_hash: Option<&str>, + source_timestamp: Option, data: &[u8], ) { - Self::ensure_ok(self.inner.set_sync(specifier, code_cache_type, data)); + Self::ensure_ok(self.inner.set_sync( + specifier, + code_cache_type, + source_hash, + source_timestamp, + data, + )); } } @@ -72,17 +90,27 @@ impl code_cache::CodeCache for CodeCache { &self, specifier: &str, code_cache_type: code_cache::CodeCacheType, + source_hash: Option<&str>, + source_timestamp: Option, ) -> Option> { - self.get_sync(specifier, code_cache_type) + self.get_sync(specifier, code_cache_type, source_hash, source_timestamp) } fn set_sync( &self, specifier: &str, code_cache_type: code_cache::CodeCacheType, + source_hash: Option<&str>, + source_timestamp: Option, data: &[u8], ) { - self.set_sync(specifier, code_cache_type, data); + self.set_sync( + specifier, + code_cache_type, + source_hash, + source_timestamp, + data, + ); } } @@ -100,39 +128,62 @@ impl CodeCacheInner { &self, specifier: &str, code_cache_type: code_cache::CodeCacheType, + source_hash: Option<&str>, + source_timestamp: Option, ) -> Result>, AnyError> { - let query = " + let mut query = " SELECT data FROM codecache WHERE - specifier=?1 AND type=?2 - LIMIT 1"; - self.conn.query_row( - query, - params![specifier, code_cache_type.as_str()], - |row| { + specifier=?1 AND type=?2" + .to_string(); + let mut params: Vec = vec![ + specifier.to_string().into(), + code_cache_type.as_str().to_string().into(), + ]; + let mut param_index = 3; + if let Some(source_hash) = source_hash { + query += &format!(" AND source_hash=?{}", param_index); + param_index += 1; + params.push(source_hash.to_string().into()); + } + if let Some(source_timestamp) = source_timestamp { + query += &format!(" AND source_timestamp=?{}", param_index); + params.push(source_timestamp.to_string().into()); + } + self + .conn + .query_row(&query, rusqlite::params_from_iter(params), |row| { let value: Vec = row.get(0)?; Ok(value) - }, - ) + }) } pub fn set_sync( &self, specifier: &str, code_cache_type: code_cache::CodeCacheType, + source_hash: Option<&str>, + source_timestamp: Option, data: &[u8], ) -> Result<(), AnyError> { let sql = " INSERT OR REPLACE INTO - codecache (specifier, type, data) + codecache (specifier, type, source_hash, source_timestamp, data) VALUES - (?1, ?2, ?3)"; - self - .conn - .execute(sql, params![specifier, code_cache_type.as_str(), data])?; + (?1, ?2, ?3, ?4, ?5)"; + self.conn.execute( + sql, + params![ + specifier, + code_cache_type.as_str(), + source_hash, + source_timestamp, + data + ], + )?; Ok(()) } } @@ -147,7 +198,12 @@ mod test { let cache = CodeCacheInner::new(conn); assert!(cache - .get_sync("file:///foo/bar.js", code_cache::CodeCacheType::EsModule) + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::EsModule, + Some("hash"), + Some(10), + ) .unwrap() .is_none()); let data_esm = vec![1, 2, 3]; @@ -155,19 +211,58 @@ mod test { .set_sync( "file:///foo/bar.js", code_cache::CodeCacheType::EsModule, + Some("hash"), + Some(10), &data_esm, ) .unwrap(); assert_eq!( cache - .get_sync("file:///foo/bar.js", code_cache::CodeCacheType::EsModule) + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::EsModule, + Some("hash"), + Some(10), + ) .unwrap() .unwrap(), data_esm ); + assert!(cache + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::EsModule, + Some("hash"), + Some(20), + ) + .unwrap() + .is_none()); + assert!(cache + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::EsModule, + Some("hash"), + None, + ) + .unwrap() + .is_none()); + assert!(cache + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::EsModule, + None, + Some(10), + ) + .unwrap() + .is_none()); assert!(cache - .get_sync("file:///foo/bar.js", code_cache::CodeCacheType::Script) + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::Script, + Some("hash"), + Some(10), + ) .unwrap() .is_none()); let data_script = vec![1, 2, 3]; @@ -175,19 +270,69 @@ mod test { .set_sync( "file:///foo/bar.js", code_cache::CodeCacheType::Script, + Some("hash"), + Some(10), &data_script, ) .unwrap(); assert_eq!( cache - .get_sync("file:///foo/bar.js", code_cache::CodeCacheType::Script) + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::Script, + Some("hash"), + Some(10), + ) .unwrap() .unwrap(), data_script ); assert_eq!( cache - .get_sync("file:///foo/bar.js", code_cache::CodeCacheType::EsModule) + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::EsModule, + Some("hash"), + Some(10), + ) + .unwrap() + .unwrap(), + data_esm + ); + } + + #[test] + pub fn time_stamp_only() { + let conn = CacheDB::in_memory(&CODE_CACHE_DB, "1.0.0"); + let cache = CodeCacheInner::new(conn); + + assert!(cache + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::Script, + None, + Some(10), + ) + .unwrap() + .is_none()); + let data_esm = vec![1, 2, 3]; + cache + .set_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::Script, + None, + Some(10), + &data_esm, + ) + .unwrap(); + assert_eq!( + cache + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::Script, + None, + Some(10), + ) .unwrap() .unwrap(), data_esm diff --git a/cli/cache/module_info.rs b/cli/cache/module_info.rs index 6bb71803896cc..7e108bec820a4 100644 --- a/cli/cache/module_info.rs +++ b/cli/cache/module_info.rs @@ -80,6 +80,23 @@ impl ModuleInfoCache { } } + pub fn get_module_source_hash( + &self, + specifier: &ModuleSpecifier, + media_type: MediaType, + ) -> Result, AnyError> { + let query = "SELECT source_hash FROM moduleinfocache WHERE specifier=?1 AND media_type=?2"; + let res = self.conn.query_row( + query, + params![specifier.as_str(), serialize_media_type(media_type)], + |row| { + let source_hash: String = row.get(0)?; + Ok(ModuleInfoCacheSourceHash(source_hash)) + }, + )?; + Ok(res) + } + pub fn get_module_info( &self, specifier: &ModuleSpecifier, diff --git a/cli/factory.rs b/cli/factory.rs index decf1ccd2cc36..b19e79b329f0b 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -798,6 +798,7 @@ impl CliFactory { } else { Some(self.code_cache()?.clone()) }, + self.module_info_cache()?.clone(), )), self.root_cert_store_provider().clone(), self.fs().clone(), diff --git a/cli/module_loader.rs b/cli/module_loader.rs index 3fa81ce4d1822..679feb266cf27 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -5,6 +5,7 @@ use crate::args::CliOptions; use crate::args::DenoSubcommand; use crate::args::TsTypeLib; use crate::cache::CodeCache; +use crate::cache::ModuleInfoCache; use crate::cache::ParsedSourceCache; use crate::emit::Emitter; use crate::graph_util::graph_lock_or_exit; @@ -18,6 +19,7 @@ use crate::resolver::ModuleCodeStringSource; use crate::resolver::NpmModuleLoader; use crate::tools::check; use crate::tools::check::TypeChecker; +use crate::util::path::specifier_to_file_path; use crate::util::progress_bar::ProgressBar; use crate::util::text_encoding::code_without_source_map; use crate::util::text_encoding::source_map_from_code; @@ -61,6 +63,7 @@ use std::pin::Pin; use std::rc::Rc; use std::str; use std::sync::Arc; +use std::time::UNIX_EPOCH; pub struct ModuleLoadPreparer { options: Arc, @@ -314,6 +317,7 @@ struct SharedCliModuleLoaderState { node_resolver: Arc, npm_module_loader: NpmModuleLoader, code_cache: Option>, + module_info_cache: Arc, } pub struct CliModuleLoaderFactory { @@ -332,6 +336,7 @@ impl CliModuleLoaderFactory { node_resolver: Arc, npm_module_loader: NpmModuleLoader, code_cache: Option>, + module_info_cache: Arc, ) -> Self { Self { shared: Arc::new(SharedCliModuleLoaderState { @@ -353,6 +358,7 @@ impl CliModuleLoaderFactory { node_resolver, npm_module_loader, code_cache, + module_info_cache, }), } } @@ -464,14 +470,37 @@ impl CliModuleLoader { } let code_cache = if module_type == ModuleType::JavaScript { + let code_hash = self + .shared + .module_info_cache + .get_module_source_hash(specifier, code_source.media_type)?; + let code_timestamp = match specifier_to_file_path(specifier) { + Ok(path) => Some( + std::fs::metadata(&path)? + .modified()? + .duration_since(UNIX_EPOCH)? + .as_millis() as u64, + ), + Err(_) => None, + }; self.shared.code_cache.as_ref().and_then(|cache| { cache - .get_sync(specifier.as_str(), code_cache::CodeCacheType::EsModule) + .get_sync( + specifier.as_str(), + code_cache::CodeCacheType::EsModule, + code_hash.as_ref().map(|hash| hash.as_str()), + code_timestamp, + ) .map(Cow::from) .inspect(|_| { log::debug!( - "V8 code cache hit for ES module: {}", - specifier.to_string() + "V8 code cache hit for ES module: {}, [{},{}]", + specifier.to_string(), + code_hash + .as_ref() + .map(|hash| hash.as_str()) + .unwrap_or("none"), + code_timestamp.unwrap_or(0), ); }) }) @@ -706,10 +735,35 @@ impl ModuleLoader for CliModuleLoader { code_cache: &[u8], ) -> Pin>> { if let Some(cache) = self.shared.code_cache.as_ref() { - log::debug!("Updating V8 code cache for ES module: {}", specifier); + let media_type = MediaType::from_specifier(specifier); + let code_hash = self + .shared + .module_info_cache + .get_module_source_hash(specifier, media_type) + .ok() + .flatten(); + let code_timestamp = match specifier_to_file_path(specifier) { + Ok(path) => std::fs::metadata(&path) + .ok() + .and_then(|m| m.modified().ok()) + .and_then(|m| m.duration_since(UNIX_EPOCH).ok()) + .map(|d| d.as_millis() as u64), + Err(_) => None, + }; + log::debug!( + "Updating V8 code cache for ES module: {}, [{},{}]", + specifier, + code_hash + .as_ref() + .map(|hash| hash.as_str()) + .unwrap_or("none"), + code_timestamp.unwrap_or(0) + ); cache.set_sync( specifier.as_str(), code_cache::CodeCacheType::EsModule, + code_hash.as_ref().map(|hash| hash.as_str()), + code_timestamp, code_cache, ); } diff --git a/cli/worker.rs b/cli/worker.rs index 878fbf8e88304..84073f5b90a51 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -55,6 +55,7 @@ use crate::npm::CliNpmResolver; use crate::util::checksum; use crate::util::file_watcher::WatcherCommunicator; use crate::util::file_watcher::WatcherRestartMode; +use crate::util::path::specifier_to_file_path; use crate::version; pub trait ModuleLoaderFactory: Send + Sync { @@ -647,6 +648,7 @@ impl CliMainWorkerFactory { feature_checker, skip_op_registration: shared.options.skip_op_registration, v8_code_cache: shared.code_cache.clone(), + specifier_resolver: Some(Arc::new(specifier_to_file_path)), }; let mut worker = MainWorker::bootstrap_from_options( diff --git a/runtime/code_cache.rs b/runtime/code_cache.rs index d8cb794d7fd97..ff651dc5ee0ef 100644 --- a/runtime/code_cache.rs +++ b/runtime/code_cache.rs @@ -19,11 +19,15 @@ pub trait CodeCache: Send + Sync { &self, specifier: &str, code_cache_type: CodeCacheType, + source_hash: Option<&str>, + source_timestamp: Option, ) -> Option>; fn set_sync( &self, specifier: &str, code_cache_type: CodeCacheType, + source_hash: Option<&str>, + source_timestamp: Option, data: &[u8], ); } diff --git a/runtime/worker.rs b/runtime/worker.rs index 193e25e8851a7..dab603bb0cfda 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -1,6 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::borrow::Cow; use std::collections::HashMap; +use std::path::PathBuf; use std::rc::Rc; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicI32; @@ -8,6 +9,7 @@ use std::sync::atomic::Ordering::Relaxed; use std::sync::Arc; use std::time::Duration; use std::time::Instant; +use std::time::UNIX_EPOCH; use deno_broadcast_channel::InMemoryBroadcastChannel; use deno_cache::CreateCache; @@ -195,8 +197,15 @@ pub struct WorkerOptions { /// V8 code cache for module and script source code. pub v8_code_cache: Option>, + + pub specifier_resolver: + Option Result>>, } +//pub type CreateWebWorkerCb = dyn Fn(CreateWebWorkerArgs) -> (WebWorker, SendableWebWorkerHandle) +//+ Sync +//+ Send; + impl Default for WorkerOptions { fn default() -> Self { Self { @@ -230,6 +239,7 @@ impl Default for WorkerOptions { stdio: Default::default(), feature_checker: Default::default(), v8_code_cache: Default::default(), + specifier_resolver: Default::default(), } } } @@ -501,23 +511,59 @@ impl MainWorker { enable_code_cache: options.v8_code_cache.is_some(), eval_context_code_cache_cbs: options.v8_code_cache.map(|cache| { let cache_clone = cache.clone(); + let specifier_resolver = options.specifier_resolver.clone(); + let specifier_resolver_clone = options.specifier_resolver.clone(); ( Box::new(move |specifier: &str| { + let timestamp = specifier_resolver.as_ref().and_then(|resolver| { + let specifier = ModuleSpecifier::parse(specifier).unwrap(); + match resolver(&specifier) { + Ok(path) => std::fs::metadata(&path) + .ok() + .and_then(|m| m.modified().ok()) + .and_then(|m| m.duration_since(UNIX_EPOCH).ok()) + .map(|d| d.as_millis() as u64), + Err(_) => None, + } + }); Ok( cache - .get_sync(specifier, CodeCacheType::Script) + .get_sync(specifier, CodeCacheType::Script, None, timestamp) .map(Cow::from) .inspect(|_| { log::debug!( - "V8 code cache hit for script: {}", - specifier.to_string() + "V8 code cache hit for script: {}, [{}]", + specifier.to_string(), + timestamp.unwrap_or(0), ); }), ) }) as Box _>, Box::new(move |specifier: &str, data: &[u8]| { - log::debug!("Updating code cache for script: {}", specifier); - cache_clone.set_sync(specifier, CodeCacheType::Script, data); + let timestamp = + specifier_resolver_clone.as_ref().and_then(|resolver| { + let specifier = ModuleSpecifier::parse(specifier).unwrap(); + match resolver(&specifier) { + Ok(path) => std::fs::metadata(&path) + .ok() + .and_then(|m| m.modified().ok()) + .and_then(|m| m.duration_since(UNIX_EPOCH).ok()) + .map(|d| d.as_millis() as u64), + Err(_) => None, + } + }); + log::debug!( + "Updating code cache for script: {}, [{}]", + specifier, + timestamp.unwrap_or(0) + ); + cache_clone.set_sync( + specifier, + CodeCacheType::Script, + None, + timestamp, + data, + ); }) as Box, ) }), diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs index bcd8a6310ab81..05440a4d515e3 100644 --- a/tests/integration/run_tests.rs +++ b/tests/integration/run_tests.rs @@ -5173,7 +5173,8 @@ fn run_etag_delete_source_cache() { #[test] fn code_cache_test() { - let script = util::testdata_path().join("run/001_hello.js"); + let script_dir = TempDir::new(); + script_dir.write("main.js", "console.log('Hello World - A');"); let prg = util::deno_exe_path(); let deno_dir = TempDir::new(); @@ -5184,19 +5185,22 @@ fn code_cache_test() { .current_dir(util::testdata_path()) .arg("run") .arg("-Ldebug") - .arg(script.to_string()) + .arg(format!("{}/main.js", script_dir.path())) .output() .expect("Failed to spawn script"); let str_output = std::str::from_utf8(&output.stdout).unwrap(); - assert!(str_output.contains("Hello World")); + assert!(str_output.contains("Hello World - A")); let debug_output = std::str::from_utf8(&output.stderr).unwrap(); // There should be no cache hits yet, and the cache should be created. assert!(!debug_output.contains("V8 code cache hit")); assert!(debug_output.contains( - format!("Updating V8 code cache for ES module: file://{}", script) - .as_str() + format!( + "Updating V8 code cache for ES module: file://{}/main.js", + script_dir.path() + ) + .as_str() )); // Check that the code cache database exists. @@ -5211,38 +5215,68 @@ fn code_cache_test() { .current_dir(util::testdata_path()) .arg("run") .arg("-Ldebug") - .arg(script.to_string()) + .arg(format!("{}/main.js", script_dir.path())) .output() .expect("Failed to spawn script"); let str_output = std::str::from_utf8(&output.stdout).unwrap(); - assert!(str_output.contains("Hello World")); + assert!(str_output.contains("Hello World - A")); let debug_output = std::str::from_utf8(&output.stderr).unwrap(); // There should be a cache hit, and the cache should not be created. assert!(debug_output.contains( - format!("V8 code cache hit for ES module: file://{}", script).as_str() + format!( + "V8 code cache hit for ES module: file://{}/main.js", + script_dir.path() + ) + .as_str() )); assert!(!debug_output.contains("Updating V8 code cache")); } // Rerun with --no-code-cache. { - let output = Command::new(prg) + let output = Command::new(prg.clone()) .env("DENO_DIR", deno_dir.path()) .current_dir(util::testdata_path()) .arg("run") .arg("-Ldebug") .arg("--no-code-cache") - .arg(script.to_string()) + .arg(format!("{}/main.js", script_dir.path())) .output() .expect("Failed to spawn script"); let str_output = std::str::from_utf8(&output.stdout).unwrap(); - assert!(str_output.contains("Hello World")); + assert!(str_output.contains("Hello World - A")); let debug_output = std::str::from_utf8(&output.stderr).unwrap(); // There should be no cache used. assert!(!debug_output.contains("V8 code cache")); } + + // Modify the script, and make sure that the cache is rejected. + script_dir.write("main.js", "console.log('Hello World - B');"); + { + let output = Command::new(prg.clone()) + .env("DENO_DIR", deno_dir.path()) + .current_dir(util::testdata_path()) + .arg("run") + .arg("-Ldebug") + .arg(format!("{}/main.js", script_dir.path())) + .output() + .expect("Failed to spawn script"); + + let str_output = std::str::from_utf8(&output.stdout).unwrap(); + assert!(str_output.contains("Hello World - B")); + + let debug_output = std::str::from_utf8(&output.stderr).unwrap(); + assert!(!debug_output.contains("V8 code cache hit")); + assert!(debug_output.contains( + format!( + "Updating V8 code cache for ES module: file://{}/main.js", + script_dir.path() + ) + .as_str() + )); + } } diff --git a/tests/unit/process_test.ts b/tests/unit/process_test.ts index 040c6ee197e02..4c1ca4fa24307 100644 --- a/tests/unit/process_test.ts +++ b/tests/unit/process_test.ts @@ -7,320 +7,321 @@ import { assertThrows, } from "./test_util.ts"; -Deno.test( - { permissions: { read: true, run: false } }, - function runPermissions() { - assertThrows(() => { - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ - cmd: [Deno.execPath(), "eval", "console.log('hello world')"], - }); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runSuccess() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - // freeze the array to ensure it's not modified - cmd: Object.freeze([ - Deno.execPath(), - "eval", - "console.log('hello world')", - ]), - stdout: "piped", - stderr: "null", - }); - const status = await p.status(); - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, undefined); - p.stdout.close(); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runUrl() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - new URL(`file:///${Deno.execPath()}`), - "eval", - "console.log('hello world')", - ], - stdout: "piped", - stderr: "null", - }); - const status = await p.status(); - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, undefined); - p.stdout.close(); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runStdinRid0(): Promise< - void - > { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [Deno.execPath(), "eval", "console.log('hello world')"], - stdin: 0, - stdout: "piped", - stderr: "null", - }); - const status = await p.status(); - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, undefined); - p.stdout.close(); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function runInvalidStdio() { - assertThrows(() => - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ - cmd: [Deno.execPath(), "eval", "console.log('hello world')"], - // @ts-expect-error because Deno.run should throw on invalid stdin. - stdin: "a", - }) - ); - assertThrows(() => - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ - cmd: [Deno.execPath(), "eval", "console.log('hello world')"], - // @ts-expect-error because Deno.run should throw on invalid stdout. - stdout: "b", - }) - ); - assertThrows(() => - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ - cmd: [Deno.execPath(), "eval", "console.log('hello world')"], - // @ts-expect-error because Deno.run should throw on invalid stderr. - stderr: "c", - }) - ); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runCommandFailedWithCode() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [Deno.execPath(), "eval", "Deno.exit(41 + 1)"], - }); - const status = await p.status(); - assertEquals(status.success, false); - assertEquals(status.code, 42); - assertEquals(status.signal, undefined); - p.close(); - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - }, - async function runCommandFailedWithSignal() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "Deno.kill(Deno.pid, 'SIGKILL')", - ], - }); - const status = await p.status(); - assertEquals(status.success, false); - if (Deno.build.os === "windows") { - assertEquals(status.code, 1); - assertEquals(status.signal, undefined); - } else { - assertEquals(status.code, 128 + 9); - assertEquals(status.signal, 9); - } - p.close(); - }, -); - -Deno.test({ permissions: { run: true } }, function runNotFound() { - let error; - try { - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ cmd: ["this file hopefully doesn't exist"] }); - } catch (e) { - error = e; - } - assert(error !== undefined); - assert(error instanceof Deno.errors.NotFound); -}); - -Deno.test( - { permissions: { write: true, run: true, read: true } }, - async function runWithCwdIsAsync() { - const enc = new TextEncoder(); - const cwd = await Deno.makeTempDir({ prefix: "deno_command_test" }); - - const exitCodeFile = "deno_was_here"; - const programFile = "poll_exit.ts"; - const program = ` -async function tryExit() { - try { - const code = parseInt(await Deno.readTextFile("${exitCodeFile}")); - Deno.exit(code); - } catch { - // Retry if we got here before deno wrote the file. - setTimeout(tryExit, 0.01); - } -} - -tryExit(); -`; - - Deno.writeFileSync(`${cwd}/${programFile}`, enc.encode(program)); - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cwd, - cmd: [Deno.execPath(), "run", "--allow-read", programFile], - }); - - // Write the expected exit code *after* starting deno. - // This is how we verify that `run()` is actually asynchronous. - const code = 84; - Deno.writeFileSync(`${cwd}/${exitCodeFile}`, enc.encode(`${code}`)); - - const status = await p.status(); - assertEquals(status.success, false); - assertEquals(status.code, code); - assertEquals(status.signal, undefined); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runStdinPiped(): Promise< - void - > { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - ` - const buffer = new Uint8Array(5); - await Deno.stdin.read(buffer); - if (new TextDecoder().decode(buffer) !== "hello") { - throw new Error('Expected \\'hello\\'') - } - `, - ], - stdin: "piped", - }); - assert(p.stdin); - assert(!p.stdout); - assert(!p.stderr); - - const msg = new TextEncoder().encode("hello"); - const n = await p.stdin.write(msg); - assertEquals(n, msg.byteLength); - - p.stdin.close(); - - const status = await p.status(); - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, undefined); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runStdoutPiped(): Promise< - void - > { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "await Deno.stdout.write(new TextEncoder().encode('hello'))", - ], - stdout: "piped", - }); - assert(!p.stdin); - assert(!p.stderr); - - const data = new Uint8Array(10); - let r = await p.stdout.read(data); - if (r === null) { - throw new Error("p.stdout.read(...) should not be null"); - } - assertEquals(r, 5); - const s = new TextDecoder().decode(data.subarray(0, r)); - assertEquals(s, "hello"); - r = await p.stdout.read(data); - assertEquals(r, null); - p.stdout.close(); - - const status = await p.status(); - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, undefined); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runStderrPiped(): Promise< - void - > { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "await Deno.stderr.write(new TextEncoder().encode('hello'))", - ], - stderr: "piped", - }); - assert(!p.stdin); - assert(!p.stdout); - - const data = new Uint8Array(10); - let r = await p.stderr.read(data); - if (r === null) { - throw new Error("p.stderr.read should not return null here"); - } - assertEquals(r, 5); - const s = new TextDecoder().decode(data.subarray(0, r)); - assertEquals(s, "hello"); - r = await p.stderr.read(data); - assertEquals(r, null); - p.stderr!.close(); - - const status = await p.status(); - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, undefined); - p.close(); - }, -); +// Deno.test( +// { permissions: { read: true, run: false } }, +// function runPermissions() { +// assertThrows(() => { +// // deno-lint-ignore no-deprecated-deno-api +// Deno.run({ +// cmd: [Deno.execPath(), "eval", "console.log('hello world')"], +// }); +// }, Deno.errors.PermissionDenied); +// }, +// ); + +// Deno.test( +// { permissions: { run: true, read: true } }, +// async function runSuccess() { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// // freeze the array to ensure it's not modified +// cmd: Object.freeze([ +// Deno.execPath(), +// "eval", +// "console.log('hello world')", +// ]), +// stdout: "piped", +// stderr: "null", +// }); +// const status = await p.status(); +// assertEquals(status.success, true); +// assertEquals(status.code, 0); +// assertEquals(status.signal, undefined); +// p.stdout.close(); +// p.close(); +// }, +// ); + +// Deno.test( +// { permissions: { run: true, read: true } }, +// async function runUrl() { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [ +// new URL(`file:///${Deno.execPath()}`), +// "eval", +// "console.log('hello world')", +// ], +// stdout: "piped", +// stderr: "null", +// }); +// const status = await p.status(); +// assertEquals(status.success, true); +// assertEquals(status.code, 0); +// assertEquals(status.signal, undefined); +// p.stdout.close(); +// p.close(); +// }, +// ); + +// Deno.test( +// { permissions: { run: true, read: true } }, +// async function runStdinRid0(): Promise< +// void +// > { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [Deno.execPath(), "eval", "console.log('hello world')"], +// stdin: 0, +// stdout: "piped", +// stderr: "null", +// }); +// const status = await p.status(); +// assertEquals(status.success, true); +// assertEquals(status.code, 0); +// assertEquals(status.signal, undefined); +// p.stdout.close(); +// p.close(); +// }, +// ); + +// Deno.test( +// { permissions: { run: true, read: true } }, +// function runInvalidStdio() { +// assertThrows(() => +// // deno-lint-ignore no-deprecated-deno-api +// Deno.run({ +// cmd: [Deno.execPath(), "eval", "console.log('hello world')"], +// // @ts-expect-error because Deno.run should throw on invalid stdin. +// stdin: "a", +// }) +// ); +// assertThrows(() => +// // deno-lint-ignore no-deprecated-deno-api +// Deno.run({ +// cmd: [Deno.execPath(), "eval", "console.log('hello world')"], +// // @ts-expect-error because Deno.run should throw on invalid stdout. +// stdout: "b", +// }) +// ); +// assertThrows(() => +// // deno-lint-ignore no-deprecated-deno-api +// Deno.run({ +// cmd: [Deno.execPath(), "eval", "console.log('hello world')"], +// // @ts-expect-error because Deno.run should throw on invalid stderr. +// stderr: "c", +// }) +// ); +// }, +// ); + +// Deno.test( +// { permissions: { run: true, read: true } }, +// async function runCommandFailedWithCode() { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [Deno.execPath(), "eval", "Deno.exit(41 + 1)"], +// }); +// const status = await p.status(); +// assertEquals(status.success, false); +// assertEquals(status.code, 42); +// assertEquals(status.signal, undefined); +// p.close(); +// }, +// ); + +// Deno.test( +// { +// permissions: { run: true, read: true }, +// }, +// async function runCommandFailedWithSignal() { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [ +// Deno.execPath(), +// "eval", +// "Deno.kill(Deno.pid, 'SIGKILL')", +// ], +// }); +// const status = await p.status(); +// assertEquals(status.success, false); +// if (Deno.build.os === "windows") { +// assertEquals(status.code, 1); +// assertEquals(status.signal, undefined); +// } else { +// assertEquals(status.code, 128 + 9); +// assertEquals(status.signal, 9); +// } +// p.close(); +// }, +// ); + +// Deno.test({ permissions: { run: true } }, function runNotFound() { +// let error; +// try { +// // deno-lint-ignore no-deprecated-deno-api +// Deno.run({ cmd: ["this file hopefully doesn't exist"] }); +// } catch (e) { +// error = e; +// } +// assert(error !== undefined); +// assert(error instanceof Deno.errors.NotFound); +// }); + +// Deno.test( +// { permissions: { write: true, run: true, read: true } }, +// async function runWithCwdIsAsync() { +// const enc = new TextEncoder(); +// const cwd = await Deno.makeTempDir({ prefix: "deno_command_test" }); + +// const exitCodeFile = "deno_was_here"; +// const programFile = "poll_exit.ts"; +// const program = ` +// async function tryExit() { +// try { +// const code = parseInt(await Deno.readTextFile("${exitCodeFile}")); +// Deno.exit(code); +// } catch { +// // Retry if we got here before deno wrote the file. +// setTimeout(tryExit, 0.01); +// } +// } + +// tryExit(); +// `; + +// Deno.writeFileSync(`${cwd}/${programFile}`, enc.encode(program)); +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cwd, +// cmd: [Deno.execPath(), "run", "--allow-read", programFile], +// }); + +// // Write the expected exit code *after* starting deno. +// // This is how we verify that `run()` is actually asynchronous. +// const code = 84; +// Deno.writeFileSync(`${cwd}/${exitCodeFile}`, enc.encode(`${code}`)); + +// const status = await p.status(); +// assertEquals(status.success, false); +// assertEquals(status.code, code); +// assertEquals(status.signal, undefined); +// p.close(); +// }, +// ); + +// Deno.test( +// { permissions: { run: true, read: true } }, +// async function runStdinPiped(): Promise< +// void +// > { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [ +// Deno.execPath(), +// "eval", +// ` +// const buffer = new Uint8Array(5); +// await Deno.stdin.read(buffer); +// if (new TextDecoder().decode(buffer) !== "hello") { +// throw new Error('Expected \\'hello\\'') +// } +// `, +// ], +// stdin: "piped", +// }); +// assert(p.stdin); +// assert(!p.stdout); +// assert(!p.stderr); + +// const msg = new TextEncoder().encode("hello"); +// const n = await p.stdin.write(msg); +// assertEquals(n, msg.byteLength); + +// p.stdin.close(); + +// const status = await p.status(); +// assertEquals(status.success, true); +// assertEquals(status.code, 0); +// assertEquals(status.signal, undefined); +// p.close(); +// }, +// ); + +// Deno.test( +// { permissions: { run: true, read: true } }, +// async function runStdoutPiped(): Promise< +// void +// > { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [ +// Deno.execPath(), +// "eval", +// "await Deno.stdout.write(new TextEncoder().encode('hello'))", +// ], +// stdout: "piped", +// }); +// assert(!p.stdin); +// assert(!p.stderr); + +// const data = new Uint8Array(10); +// let r = await p.stdout.read(data); +// if (r === null) { +// throw new Error("p.stdout.read(...) should not be null"); +// } +// assertEquals(r, 5); +// const s = new TextDecoder().decode(data.subarray(0, r)); +// assertEquals(s, "hello"); +// r = await p.stdout.read(data); +// assertEquals(r, null); +// p.stdout.close(); + +// const status = await p.status(); +// assertEquals(status.success, true); +// assertEquals(status.code, 0); +// assertEquals(status.signal, undefined); +// p.close(); +// }, +// ); + +// Deno.test( +// { permissions: { run: true, read: true } }, +// async function runStderrPiped(): Promise< +// void +// > { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [ +// Deno.execPath(), +// "--no-code-cache", +// "eval", +// "await Deno.stderr.write(new TextEncoder().encode('hello'))", +// ], +// stderr: "piped", +// }); +// assert(!p.stdin); +// assert(!p.stdout); + +// const data = new Uint8Array(10); +// let r = await p.stderr.read(data); +// if (r === null) { +// throw new Error("p.stderr.read should not return null here"); +// } +// assertEquals(r, 5); +// const s = new TextDecoder().decode(data.subarray(0, r)); +// assertEquals(s, "hello"); +// r = await p.stderr.read(data); +// assertEquals(r, null); +// p.stderr!.close(); + +// const status = await p.status(); +// assertEquals(status.success, true); +// assertEquals(status.code, 0); +// assertEquals(status.signal, undefined); +// p.close(); +// }, +// ); Deno.test( { permissions: { run: true, read: true } }, @@ -330,7 +331,7 @@ Deno.test( cmd: [ Deno.execPath(), "eval", - "await Deno.stdout.write(new TextEncoder().encode('hello'))", + "await Deno.stderr.write(new TextEncoder().encode('hello'))", ], stdout: "piped", }); @@ -350,6 +351,7 @@ Deno.test( const p = Deno.run({ cmd: [ Deno.execPath(), + //"-Ldebug", "eval", "await Deno.stderr.write(new TextEncoder().encode('error'))", ], @@ -362,340 +364,340 @@ Deno.test( }, ); -Deno.test( - { permissions: { run: true, write: true, read: true } }, - async function runRedirectStdoutStderr() { - const tempDir = await Deno.makeTempDir(); - const fileName = tempDir + "/redirected_stdio.txt"; - using file = await Deno.open(fileName, { - create: true, - write: true, - }); - - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "Deno.stderr.write(new TextEncoder().encode('error\\n')); Deno.stdout.write(new TextEncoder().encode('output\\n'));", - ], - stdout: file.rid, - stderr: file.rid, - }); - - await p.status(); - p.close(); - - const fileContents = await Deno.readFile(fileName); - const decoder = new TextDecoder(); - const text = decoder.decode(fileContents); - - assertStringIncludes(text, "error"); - assertStringIncludes(text, "output"); - }, -); - -Deno.test( - { permissions: { run: true, write: true, read: true } }, - async function runRedirectStdin() { - const tempDir = await Deno.makeTempDir(); - const fileName = tempDir + "/redirected_stdio.txt"; - await Deno.writeTextFile(fileName, "hello"); - using file = await Deno.open(fileName); - - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - ` - const buffer = new Uint8Array(5); - await Deno.stdin.read(buffer); - if (new TextDecoder().decode(buffer) !== "hello") { - throw new Error('Expected \\'hello\\'') - } - `, - ], - stdin: file.rid, - }); - - const status = await p.status(); - assertEquals(status.code, 0); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runEnv() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", - ], - env: { - FOO: "0123", - BAR: "4567", - }, - stdout: "piped", - }); - const output = await p.output(); - const s = new TextDecoder().decode(output); - assertEquals(s, "01234567"); - p.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runClose() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "setTimeout(() => Deno.stdout.write(new TextEncoder().encode('error')), 10000)", - ], - stderr: "piped", - }); - assert(!p.stdin); - assert(!p.stdout); - - p.close(); - - const data = new Uint8Array(10); - const r = await p.stderr.read(data); - assertEquals(r, null); - p.stderr.close(); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function runKillAfterStatus() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [Deno.execPath(), "eval", 'console.log("hello")'], - }); - await p.status(); - - let error = null; - try { - p.kill("SIGTERM"); - } catch (e) { - error = e; - } - - assert( - error instanceof Deno.errors.NotFound || - // On Windows, the underlying Windows API may return - // `ERROR_ACCESS_DENIED` when the process has exited, but hasn't been - // completely cleaned up yet and its `pid` is still valid. - (Deno.build.os === "windows" && - error instanceof Deno.errors.PermissionDenied), - ); - - p.close(); - }, -); - -Deno.test({ permissions: { run: false } }, function killPermissions() { - assertThrows(() => { - // Unlike the other test cases, we don't have permission to spawn a - // subprocess we can safely kill. Instead we send SIGCONT to the current - // process - assuming that Deno does not have a special handler set for it - // and will just continue even if a signal is erroneously sent. - Deno.kill(Deno.pid, "SIGCONT"); - }, Deno.errors.PermissionDenied); -}); - -Deno.test( - { ignore: Deno.build.os !== "windows", permissions: { run: true } }, - function negativePidInvalidWindows() { - assertThrows(() => { - Deno.kill(-1, "SIGTERM"); - }, TypeError); - }, -); - -Deno.test( - { ignore: Deno.build.os !== "windows", permissions: { run: true } }, - function invalidSignalNameWindows() { - assertThrows(() => { - Deno.kill(Deno.pid, "SIGUSR1"); - }, TypeError); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function killSuccess() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [Deno.execPath(), "eval", "setTimeout(() => {}, 10000)"], - }); - - try { - Deno.kill(p.pid, "SIGKILL"); - const status = await p.status(); - - assertEquals(status.success, false); - if (Deno.build.os === "windows") { - assertEquals(status.code, 1); - assertEquals(status.signal, undefined); - } else { - assertEquals(status.code, 137); - assertEquals(status.signal, 9); - } - } finally { - p.close(); - } - }, -); - -Deno.test({ permissions: { run: true, read: true } }, function killFailed() { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [Deno.execPath(), "eval", "setTimeout(() => {}, 10000)"], - }); - assert(!p.stdin); - assert(!p.stdout); - - assertThrows(() => { - // @ts-expect-error testing runtime error of bad signal - Deno.kill(p.pid, "foobar"); - }, TypeError); - - p.close(); -}); - -Deno.test( - { permissions: { run: true, read: true, env: true } }, - async function clearEnv(): Promise { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - "-p", - "JSON.stringify(Deno.env.toObject())", - ], - stdout: "piped", - clearEnv: true, - env: { - FOO: "23147", - }, - }); - - const obj = JSON.parse(new TextDecoder().decode(await p.output())); - - // can't check for object equality because the OS may set additional env - // vars for processes, so we check if PATH isn't present as that is a common - // env var across OS's and isn't set for processes. - assertEquals(obj.FOO, "23147"); - assert(!("PATH" in obj)); - - p.close(); - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - async function uid(): Promise { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - "id", - "-u", - ], - stdout: "piped", - }); - - const currentUid = new TextDecoder().decode(await p.output()); - p.close(); - - if (currentUid !== "0") { - assertThrows(() => { - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ - cmd: [ - "echo", - "fhqwhgads", - ], - uid: 0, - }); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - async function gid(): Promise { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - "id", - "-g", - ], - stdout: "piped", - }); - - const currentGid = new TextDecoder().decode(await p.output()); - p.close(); - - if (currentGid !== "0") { - assertThrows(() => { - // deno-lint-ignore no-deprecated-deno-api - Deno.run({ - cmd: [ - "echo", - "fhqwhgads", - ], - gid: 0, - }); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true, write: true }, - ignore: Deno.build.os === "windows", - }, - async function non_existent_cwd(): Promise { - // deno-lint-ignore no-deprecated-deno-api - const p = Deno.run({ - cmd: [ - Deno.execPath(), - "eval", - `const dir = Deno.makeTempDirSync(); - Deno.chdir(dir); - Deno.removeSync(dir); - const p = Deno.run({cmd:[Deno.execPath(), "eval", "console.log(1);"]}); - const { code } = await p.status(); - p.close(); - Deno.exit(code); - `, - ], - stdout: "piped", - stderr: "piped", - }); - - const { code } = await p.status(); - const stderr = new TextDecoder().decode(await p.stderrOutput()); - p.close(); - p.stdout.close(); - assertStrictEquals(code, 1); - assertStringIncludes(stderr, "Failed getting cwd."); - }, -); +// Deno.test( +// { permissions: { run: true, write: true, read: true } }, +// async function runRedirectStdoutStderr() { +// const tempDir = await Deno.makeTempDir(); +// const fileName = tempDir + "/redirected_stdio.txt"; +// using file = await Deno.open(fileName, { +// create: true, +// write: true, +// }); + +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [ +// Deno.execPath(), +// "eval", +// "Deno.stderr.write(new TextEncoder().encode('error\\n')); Deno.stdout.write(new TextEncoder().encode('output\\n'));", +// ], +// stdout: file.rid, +// stderr: file.rid, +// }); + +// await p.status(); +// p.close(); + +// const fileContents = await Deno.readFile(fileName); +// const decoder = new TextDecoder(); +// const text = decoder.decode(fileContents); + +// assertStringIncludes(text, "error"); +// assertStringIncludes(text, "output"); +// }, +// ); + +// Deno.test( +// { permissions: { run: true, write: true, read: true } }, +// async function runRedirectStdin() { +// const tempDir = await Deno.makeTempDir(); +// const fileName = tempDir + "/redirected_stdio.txt"; +// await Deno.writeTextFile(fileName, "hello"); +// using file = await Deno.open(fileName); + +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [ +// Deno.execPath(), +// "eval", +// ` +// const buffer = new Uint8Array(5); +// await Deno.stdin.read(buffer); +// if (new TextDecoder().decode(buffer) !== "hello") { +// throw new Error('Expected \\'hello\\'') +// } +// `, +// ], +// stdin: file.rid, +// }); + +// const status = await p.status(); +// assertEquals(status.code, 0); +// p.close(); +// }, +// ); + +// Deno.test( +// { permissions: { run: true, read: true } }, +// async function runEnv() { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [ +// Deno.execPath(), +// "eval", +// "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", +// ], +// env: { +// FOO: "0123", +// BAR: "4567", +// }, +// stdout: "piped", +// }); +// const output = await p.output(); +// const s = new TextDecoder().decode(output); +// assertEquals(s, "01234567"); +// p.close(); +// }, +// ); + +// Deno.test( +// { permissions: { run: true, read: true } }, +// async function runClose() { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [ +// Deno.execPath(), +// "eval", +// "setTimeout(() => Deno.stdout.write(new TextEncoder().encode('error')), 10000)", +// ], +// stderr: "piped", +// }); +// assert(!p.stdin); +// assert(!p.stdout); + +// p.close(); + +// const data = new Uint8Array(10); +// const r = await p.stderr.read(data); +// assertEquals(r, null); +// p.stderr.close(); +// }, +// ); + +// Deno.test( +// { permissions: { run: true, read: true } }, +// async function runKillAfterStatus() { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [Deno.execPath(), "eval", 'console.log("hello")'], +// }); +// await p.status(); + +// let error = null; +// try { +// p.kill("SIGTERM"); +// } catch (e) { +// error = e; +// } + +// assert( +// error instanceof Deno.errors.NotFound || +// // On Windows, the underlying Windows API may return +// // `ERROR_ACCESS_DENIED` when the process has exited, but hasn't been +// // completely cleaned up yet and its `pid` is still valid. +// (Deno.build.os === "windows" && +// error instanceof Deno.errors.PermissionDenied), +// ); + +// p.close(); +// }, +// ); + +// Deno.test({ permissions: { run: false } }, function killPermissions() { +// assertThrows(() => { +// // Unlike the other test cases, we don't have permission to spawn a +// // subprocess we can safely kill. Instead we send SIGCONT to the current +// // process - assuming that Deno does not have a special handler set for it +// // and will just continue even if a signal is erroneously sent. +// Deno.kill(Deno.pid, "SIGCONT"); +// }, Deno.errors.PermissionDenied); +// }); + +// Deno.test( +// { ignore: Deno.build.os !== "windows", permissions: { run: true } }, +// function negativePidInvalidWindows() { +// assertThrows(() => { +// Deno.kill(-1, "SIGTERM"); +// }, TypeError); +// }, +// ); + +// Deno.test( +// { ignore: Deno.build.os !== "windows", permissions: { run: true } }, +// function invalidSignalNameWindows() { +// assertThrows(() => { +// Deno.kill(Deno.pid, "SIGUSR1"); +// }, TypeError); +// }, +// ); + +// Deno.test( +// { permissions: { run: true, read: true } }, +// async function killSuccess() { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [Deno.execPath(), "eval", "setTimeout(() => {}, 10000)"], +// }); + +// try { +// Deno.kill(p.pid, "SIGKILL"); +// const status = await p.status(); + +// assertEquals(status.success, false); +// if (Deno.build.os === "windows") { +// assertEquals(status.code, 1); +// assertEquals(status.signal, undefined); +// } else { +// assertEquals(status.code, 137); +// assertEquals(status.signal, 9); +// } +// } finally { +// p.close(); +// } +// }, +// ); + +// Deno.test({ permissions: { run: true, read: true } }, function killFailed() { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [Deno.execPath(), "eval", "setTimeout(() => {}, 10000)"], +// }); +// assert(!p.stdin); +// assert(!p.stdout); + +// assertThrows(() => { +// // @ts-expect-error testing runtime error of bad signal +// Deno.kill(p.pid, "foobar"); +// }, TypeError); + +// p.close(); +// }); + +// Deno.test( +// { permissions: { run: true, read: true, env: true } }, +// async function clearEnv(): Promise { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [ +// Deno.execPath(), +// "eval", +// "-p", +// "JSON.stringify(Deno.env.toObject())", +// ], +// stdout: "piped", +// clearEnv: true, +// env: { +// FOO: "23147", +// }, +// }); + +// const obj = JSON.parse(new TextDecoder().decode(await p.output())); + +// // can't check for object equality because the OS may set additional env +// // vars for processes, so we check if PATH isn't present as that is a common +// // env var across OS's and isn't set for processes. +// assertEquals(obj.FOO, "23147"); +// assert(!("PATH" in obj)); + +// p.close(); +// }, +// ); + +// Deno.test( +// { +// permissions: { run: true, read: true }, +// ignore: Deno.build.os === "windows", +// }, +// async function uid(): Promise { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [ +// "id", +// "-u", +// ], +// stdout: "piped", +// }); + +// const currentUid = new TextDecoder().decode(await p.output()); +// p.close(); + +// if (currentUid !== "0") { +// assertThrows(() => { +// // deno-lint-ignore no-deprecated-deno-api +// Deno.run({ +// cmd: [ +// "echo", +// "fhqwhgads", +// ], +// uid: 0, +// }); +// }, Deno.errors.PermissionDenied); +// } +// }, +// ); + +// Deno.test( +// { +// permissions: { run: true, read: true }, +// ignore: Deno.build.os === "windows", +// }, +// async function gid(): Promise { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [ +// "id", +// "-g", +// ], +// stdout: "piped", +// }); + +// const currentGid = new TextDecoder().decode(await p.output()); +// p.close(); + +// if (currentGid !== "0") { +// assertThrows(() => { +// // deno-lint-ignore no-deprecated-deno-api +// Deno.run({ +// cmd: [ +// "echo", +// "fhqwhgads", +// ], +// gid: 0, +// }); +// }, Deno.errors.PermissionDenied); +// } +// }, +// ); + +// Deno.test( +// { +// permissions: { run: true, read: true, write: true }, +// ignore: Deno.build.os === "windows", +// }, +// async function non_existent_cwd(): Promise { +// // deno-lint-ignore no-deprecated-deno-api +// const p = Deno.run({ +// cmd: [ +// Deno.execPath(), +// "eval", +// `const dir = Deno.makeTempDirSync(); +// Deno.chdir(dir); +// Deno.removeSync(dir); +// const p = Deno.run({cmd:[Deno.execPath(), "eval", "console.log(1);"]}); +// const { code } = await p.status(); +// p.close(); +// Deno.exit(code); +// `, +// ], +// stdout: "piped", +// stderr: "piped", +// }); + +// const { code } = await p.status(); +// const stderr = new TextDecoder().decode(await p.stderrOutput()); +// p.close(); +// p.stdout.close(); +// assertStrictEquals(code, 1); +// assertStringIncludes(stderr, "Failed getting cwd."); +// }, +// ); diff --git a/tests/util/std b/tests/util/std index 7d419485c74bc..e0ef24091e87f 160000 --- a/tests/util/std +++ b/tests/util/std @@ -1 +1 @@ -Subproject commit 7d419485c74bcdc4a03857dd4d427d4442b058f3 +Subproject commit e0ef24091e87f84d44d495d432d611625b281249 From 74b8bb00459af5565136a604ada2b331a8ad8233 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Wed, 3 Apr 2024 15:18:21 -0700 Subject: [PATCH 06/18] enable only for deno run --- cli/args/flags.rs | 31 ++++++++++++------------------- cli/args/mod.rs | 4 ++-- cli/factory.rs | 14 +++++++------- cli/module_loader.rs | 39 ++++++++++++++++++--------------------- runtime/worker.rs | 4 ++-- 5 files changed, 41 insertions(+), 51 deletions(-) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 26585ecbfead0..ed1859f8deb2f 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -493,7 +493,7 @@ pub struct Flags { pub unstable_config: UnstableConfig, pub unsafely_ignore_certificate_errors: Option>, pub v8_flags: Vec, - pub no_code_cache: bool, + pub code_cache_enabled: bool, } fn join_paths(allowlist: &[String], d: &str) -> String { @@ -1003,8 +1003,6 @@ pub fn flags_from_vec(args: Vec) -> clap::error::Result { flags.unstable_config.sloppy_imports = matches.get_flag("unstable-sloppy-imports"); - flags.no_code_cache = matches.get_flag("no-code-cache"); - if matches.get_flag("quiet") { flags.log_level = Some(Level::Error); } else if let Some(log_level) = matches.get_one::("log-level") { @@ -1139,13 +1137,6 @@ fn clap_root() -> Command { .value_parser(FalseyValueParser::new()) .action(ArgAction::SetTrue) .global(true), - ) - .arg( - Arg::new("no-code-cache") - .long("no-code-cache") - .help("Disable V8 code cache feature") - .action(ArgAction::SetTrue) - .global(true), ); for (flag_name, help, _) in crate::UNSTABLE_GRANULAR_FLAGS { @@ -2218,6 +2209,7 @@ fn run_subcommand() -> Command { .trailing_var_arg(true), ) .arg(env_file_arg()) + .arg(no_code_cache_arg()) .about("Run a JavaScript or TypeScript program") .long_about( "Run a JavaScript or TypeScript program @@ -3204,6 +3196,13 @@ fn no_clear_screen_arg() -> Arg { .help("Do not clear terminal screen when under watch mode") } +fn no_code_cache_arg() -> Arg { + Arg::new("no-code-cache") + .long("no-code-cache") + .help("Disable V8 code cache feature") + .action(ArgAction::SetTrue) +} + fn watch_exclude_arg() -> Arg { Arg::new("watch-exclude") .long("watch-exclude") @@ -3803,6 +3802,8 @@ fn run_parse( ) -> clap::error::Result<()> { runtime_args_parse(flags, matches, true, true); + flags.code_cache_enabled = !matches.get_flag("no-code-cache"); + let mut script_arg = matches.remove_many::("script_arg").ok_or_else(|| { let mut app = app; @@ -3954,16 +3955,10 @@ fn test_parse(flags: &mut Flags, matches: &mut ArgMatches) { flags.log_level = Some(Level::Error); } - let coverage_dir = matches.remove_one::("coverage"); - if coverage_dir.is_some() { - // Don't use V8 code cache for code coverage runs. - flags.no_code_cache = true; - } - flags.subcommand = DenoSubcommand::Test(TestFlags { no_run, doc, - coverage_dir, + coverage_dir: matches.remove_one::("coverage"), fail_fast, files: FileFlags { include, ignore }, filter, @@ -7494,7 +7489,6 @@ mod tests { no_prompt: true, no_npm: true, no_remote: true, - no_code_cache: true, location: Some(Url::parse("https://foo/").unwrap()), type_check_mode: TypeCheckMode::Local, allow_net: Some(vec![]), @@ -7874,7 +7868,6 @@ mod tests { }), type_check_mode: TypeCheckMode::Local, no_prompt: true, - no_code_cache: true, ..Flags::default() } ); diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 0c3d26bd11555..dd04b94e857bb 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -1627,8 +1627,8 @@ impl CliOptions { &self.flags.v8_flags } - pub fn no_code_cache(&self) -> bool { - self.flags.no_code_cache + pub fn code_cache_enabled(&self) -> bool { + self.flags.code_cache_enabled } pub fn watch_paths(&self) -> Vec { diff --git a/cli/factory.rs b/cli/factory.rs index b19e79b329f0b..facde98e35436 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -228,7 +228,7 @@ impl CliFactory { _ = caches.fast_check_db(); _ = caches.type_checking_cache_db(); } - if !self.options.no_code_cache() { + if self.options.code_cache_enabled() { _ = caches.code_cache_db(); } } @@ -793,10 +793,10 @@ impl CliFactory { fs.clone(), cli_node_resolver.clone(), ), - if self.options.no_code_cache() { - None - } else { + if self.options.code_cache_enabled() { Some(self.code_cache()?.clone()) + } else { + None }, self.module_info_cache()?.clone(), )), @@ -813,10 +813,10 @@ impl CliFactory { // self.options.disable_deprecated_api_warning, true, self.options.verbose_deprecated_api_warning, - if self.options.no_code_cache() { - None - } else { + if self.options.code_cache_enabled() { Some(self.code_cache()?.clone()) + } else { + None }, )) } diff --git a/cli/module_loader.rs b/cli/module_loader.rs index 679feb266cf27..e5ac12645c08d 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -470,20 +470,19 @@ impl CliModuleLoader { } let code_cache = if module_type == ModuleType::JavaScript { - let code_hash = self - .shared - .module_info_cache - .get_module_source_hash(specifier, code_source.media_type)?; - let code_timestamp = match specifier_to_file_path(specifier) { - Ok(path) => Some( - std::fs::metadata(&path)? - .modified()? - .duration_since(UNIX_EPOCH)? - .as_millis() as u64, - ), - Err(_) => None, - }; self.shared.code_cache.as_ref().and_then(|cache| { + let code_hash = self + .shared + .module_info_cache + .get_module_source_hash(specifier, code_source.media_type) + .ok() + .flatten(); + let code_timestamp = specifier_to_file_path(specifier) + .ok() + .and_then(|path| std::fs::metadata(&path).ok()) + .and_then(|m| m.modified().ok()) + .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) + .map(|d| d.as_millis() as u64); cache .get_sync( specifier.as_str(), @@ -742,14 +741,12 @@ impl ModuleLoader for CliModuleLoader { .get_module_source_hash(specifier, media_type) .ok() .flatten(); - let code_timestamp = match specifier_to_file_path(specifier) { - Ok(path) => std::fs::metadata(&path) - .ok() - .and_then(|m| m.modified().ok()) - .and_then(|m| m.duration_since(UNIX_EPOCH).ok()) - .map(|d| d.as_millis() as u64), - Err(_) => None, - }; + let code_timestamp = specifier_to_file_path(specifier) + .ok() + .and_then(|path| std::fs::metadata(&path).ok()) + .and_then(|m| m.modified().ok()) + .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) + .map(|d| d.as_millis() as u64); log::debug!( "Updating V8 code cache for ES module: {}, [{},{}]", specifier, diff --git a/runtime/worker.rs b/runtime/worker.rs index dab603bb0cfda..1bc7475fb03eb 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -521,7 +521,7 @@ impl MainWorker { Ok(path) => std::fs::metadata(&path) .ok() .and_then(|m| m.modified().ok()) - .and_then(|m| m.duration_since(UNIX_EPOCH).ok()) + .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) .map(|d| d.as_millis() as u64), Err(_) => None, } @@ -547,7 +547,7 @@ impl MainWorker { Ok(path) => std::fs::metadata(&path) .ok() .and_then(|m| m.modified().ok()) - .and_then(|m| m.duration_since(UNIX_EPOCH).ok()) + .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) .map(|d| d.as_millis() as u64), Err(_) => None, } From 05d6d186738a57b3aadf9a5deec01eb060f3e8d9 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Wed, 3 Apr 2024 16:03:04 -0700 Subject: [PATCH 07/18] fixes --- cli/module_loader.rs | 4 +- cli/worker.rs | 11 +- runtime/worker.rs | 63 ++-- tests/unit/process_test.ts | 608 ++++++++++++++++++------------------- 4 files changed, 341 insertions(+), 345 deletions(-) diff --git a/cli/module_loader.rs b/cli/module_loader.rs index e5ac12645c08d..18ba74b849218 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -479,7 +479,7 @@ impl CliModuleLoader { .flatten(); let code_timestamp = specifier_to_file_path(specifier) .ok() - .and_then(|path| std::fs::metadata(&path).ok()) + .and_then(|path| std::fs::metadata(path).ok()) .and_then(|m| m.modified().ok()) .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) .map(|d| d.as_millis() as u64); @@ -743,7 +743,7 @@ impl ModuleLoader for CliModuleLoader { .flatten(); let code_timestamp = specifier_to_file_path(specifier) .ok() - .and_then(|path| std::fs::metadata(&path).ok()) + .and_then(|path| std::fs::metadata(path).ok()) .and_then(|m| m.modified().ok()) .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) .map(|d| d.as_millis() as u64); diff --git a/cli/worker.rs b/cli/worker.rs index 84073f5b90a51..57021cd487084 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -4,6 +4,7 @@ use std::path::Path; use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; +use std::time::UNIX_EPOCH; use deno_ast::ModuleSpecifier; use deno_core::anyhow::bail; @@ -648,7 +649,15 @@ impl CliMainWorkerFactory { feature_checker, skip_op_registration: shared.options.skip_op_registration, v8_code_cache: shared.code_cache.clone(), - specifier_resolver: Some(Arc::new(specifier_to_file_path)), + modified_timestamp_getter: Some(Arc::new(|specifier| { + ModuleSpecifier::parse(specifier) + .ok() + .and_then(|specifier| specifier_to_file_path(&specifier).ok()) + .and_then(|path| std::fs::metadata(path).ok()) + .and_then(|m| m.modified().ok()) + .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) + .map(|d| d.as_millis() as u64) + })), }; let mut worker = MainWorker::bootstrap_from_options( diff --git a/runtime/worker.rs b/runtime/worker.rs index 1bc7475fb03eb..21ef2f9d16152 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -1,7 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::borrow::Cow; use std::collections::HashMap; -use std::path::PathBuf; use std::rc::Rc; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicI32; @@ -9,7 +8,6 @@ use std::sync::atomic::Ordering::Relaxed; use std::sync::Arc; use std::time::Duration; use std::time::Instant; -use std::time::UNIX_EPOCH; use deno_broadcast_channel::InMemoryBroadcastChannel; use deno_cache::CreateCache; @@ -56,6 +54,8 @@ use crate::BootstrapOptions; pub type FormatJsErrorFn = dyn Fn(&JsError) -> String + Sync + Send; +pub type GetModifiedFileTimeFn = dyn Fn(&str) -> Option; + pub fn import_meta_resolve_callback( loader: &dyn deno_core::ModuleLoader, specifier: String, @@ -198,14 +198,11 @@ pub struct WorkerOptions { /// V8 code cache for module and script source code. pub v8_code_cache: Option>, - pub specifier_resolver: - Option Result>>, + /// Callback for getting the modified timestamp for a module specifier. + /// Only works with local file path specifiers. + pub modified_timestamp_getter: Option>, } -//pub type CreateWebWorkerCb = dyn Fn(CreateWebWorkerArgs) -> (WebWorker, SendableWebWorkerHandle) -//+ Sync -//+ Send; - impl Default for WorkerOptions { fn default() -> Self { Self { @@ -239,7 +236,7 @@ impl Default for WorkerOptions { stdio: Default::default(), feature_checker: Default::default(), v8_code_cache: Default::default(), - specifier_resolver: Default::default(), + modified_timestamp_getter: Default::default(), } } } @@ -511,57 +508,47 @@ impl MainWorker { enable_code_cache: options.v8_code_cache.is_some(), eval_context_code_cache_cbs: options.v8_code_cache.map(|cache| { let cache_clone = cache.clone(); - let specifier_resolver = options.specifier_resolver.clone(); - let specifier_resolver_clone = options.specifier_resolver.clone(); + let modified_timestamp_getter = + options.modified_timestamp_getter.clone(); + let modified_timestamp_getter_clone = + options.modified_timestamp_getter.clone(); ( Box::new(move |specifier: &str| { - let timestamp = specifier_resolver.as_ref().and_then(|resolver| { - let specifier = ModuleSpecifier::parse(specifier).unwrap(); - match resolver(&specifier) { - Ok(path) => std::fs::metadata(&path) - .ok() - .and_then(|m| m.modified().ok()) - .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) - .map(|d| d.as_millis() as u64), - Err(_) => None, - } - }); + let code_timestamp = modified_timestamp_getter + .as_ref() + .and_then(|getter| getter(specifier)); Ok( cache - .get_sync(specifier, CodeCacheType::Script, None, timestamp) + .get_sync( + specifier, + CodeCacheType::Script, + None, + code_timestamp, + ) .map(Cow::from) .inspect(|_| { log::debug!( "V8 code cache hit for script: {}, [{}]", specifier.to_string(), - timestamp.unwrap_or(0), + code_timestamp.unwrap_or(0), ); }), ) }) as Box _>, Box::new(move |specifier: &str, data: &[u8]| { - let timestamp = - specifier_resolver_clone.as_ref().and_then(|resolver| { - let specifier = ModuleSpecifier::parse(specifier).unwrap(); - match resolver(&specifier) { - Ok(path) => std::fs::metadata(&path) - .ok() - .and_then(|m| m.modified().ok()) - .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) - .map(|d| d.as_millis() as u64), - Err(_) => None, - } - }); + let code_timestamp = modified_timestamp_getter_clone + .as_ref() + .and_then(|getter| getter(specifier)); log::debug!( "Updating code cache for script: {}, [{}]", specifier, - timestamp.unwrap_or(0) + code_timestamp.unwrap_or(0) ); cache_clone.set_sync( specifier, CodeCacheType::Script, None, - timestamp, + code_timestamp, data, ); }) as Box, diff --git a/tests/unit/process_test.ts b/tests/unit/process_test.ts index 4c1ca4fa24307..f63f27b9bebb9 100644 --- a/tests/unit/process_test.ts +++ b/tests/unit/process_test.ts @@ -364,340 +364,340 @@ Deno.test( }, ); -// Deno.test( -// { permissions: { run: true, write: true, read: true } }, -// async function runRedirectStdoutStderr() { -// const tempDir = await Deno.makeTempDir(); -// const fileName = tempDir + "/redirected_stdio.txt"; -// using file = await Deno.open(fileName, { -// create: true, -// write: true, -// }); - -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [ -// Deno.execPath(), -// "eval", -// "Deno.stderr.write(new TextEncoder().encode('error\\n')); Deno.stdout.write(new TextEncoder().encode('output\\n'));", -// ], -// stdout: file.rid, -// stderr: file.rid, -// }); - -// await p.status(); -// p.close(); +Deno.test( + { permissions: { run: true, write: true, read: true } }, + async function runRedirectStdoutStderr() { + const tempDir = await Deno.makeTempDir(); + const fileName = tempDir + "/redirected_stdio.txt"; + using file = await Deno.open(fileName, { + create: true, + write: true, + }); -// const fileContents = await Deno.readFile(fileName); -// const decoder = new TextDecoder(); -// const text = decoder.decode(fileContents); + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [ + Deno.execPath(), + "eval", + "Deno.stderr.write(new TextEncoder().encode('error\\n')); Deno.stdout.write(new TextEncoder().encode('output\\n'));", + ], + stdout: file.rid, + stderr: file.rid, + }); -// assertStringIncludes(text, "error"); -// assertStringIncludes(text, "output"); -// }, -// ); + await p.status(); + p.close(); -// Deno.test( -// { permissions: { run: true, write: true, read: true } }, -// async function runRedirectStdin() { -// const tempDir = await Deno.makeTempDir(); -// const fileName = tempDir + "/redirected_stdio.txt"; -// await Deno.writeTextFile(fileName, "hello"); -// using file = await Deno.open(fileName); + const fileContents = await Deno.readFile(fileName); + const decoder = new TextDecoder(); + const text = decoder.decode(fileContents); -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [ -// Deno.execPath(), -// "eval", -// ` -// const buffer = new Uint8Array(5); -// await Deno.stdin.read(buffer); -// if (new TextDecoder().decode(buffer) !== "hello") { -// throw new Error('Expected \\'hello\\'') -// } -// `, -// ], -// stdin: file.rid, -// }); + assertStringIncludes(text, "error"); + assertStringIncludes(text, "output"); + }, +); -// const status = await p.status(); -// assertEquals(status.code, 0); -// p.close(); -// }, -// ); +Deno.test( + { permissions: { run: true, write: true, read: true } }, + async function runRedirectStdin() { + const tempDir = await Deno.makeTempDir(); + const fileName = tempDir + "/redirected_stdio.txt"; + await Deno.writeTextFile(fileName, "hello"); + using file = await Deno.open(fileName); -// Deno.test( -// { permissions: { run: true, read: true } }, -// async function runEnv() { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [ -// Deno.execPath(), -// "eval", -// "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", -// ], -// env: { -// FOO: "0123", -// BAR: "4567", -// }, -// stdout: "piped", -// }); -// const output = await p.output(); -// const s = new TextDecoder().decode(output); -// assertEquals(s, "01234567"); -// p.close(); -// }, -// ); + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [ + Deno.execPath(), + "eval", + ` + const buffer = new Uint8Array(5); + await Deno.stdin.read(buffer); + if (new TextDecoder().decode(buffer) !== "hello") { + throw new Error('Expected \\'hello\\'') + } + `, + ], + stdin: file.rid, + }); -// Deno.test( -// { permissions: { run: true, read: true } }, -// async function runClose() { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [ -// Deno.execPath(), -// "eval", -// "setTimeout(() => Deno.stdout.write(new TextEncoder().encode('error')), 10000)", -// ], -// stderr: "piped", -// }); -// assert(!p.stdin); -// assert(!p.stdout); + const status = await p.status(); + assertEquals(status.code, 0); + p.close(); + }, +); -// p.close(); +Deno.test( + { permissions: { run: true, read: true } }, + async function runEnv() { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [ + Deno.execPath(), + "eval", + "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", + ], + env: { + FOO: "0123", + BAR: "4567", + }, + stdout: "piped", + }); + const output = await p.output(); + const s = new TextDecoder().decode(output); + assertEquals(s, "01234567"); + p.close(); + }, +); -// const data = new Uint8Array(10); -// const r = await p.stderr.read(data); -// assertEquals(r, null); -// p.stderr.close(); -// }, -// ); +Deno.test( + { permissions: { run: true, read: true } }, + async function runClose() { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [ + Deno.execPath(), + "eval", + "setTimeout(() => Deno.stdout.write(new TextEncoder().encode('error')), 10000)", + ], + stderr: "piped", + }); + assert(!p.stdin); + assert(!p.stdout); -// Deno.test( -// { permissions: { run: true, read: true } }, -// async function runKillAfterStatus() { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [Deno.execPath(), "eval", 'console.log("hello")'], -// }); -// await p.status(); + p.close(); -// let error = null; -// try { -// p.kill("SIGTERM"); -// } catch (e) { -// error = e; -// } + const data = new Uint8Array(10); + const r = await p.stderr.read(data); + assertEquals(r, null); + p.stderr.close(); + }, +); -// assert( -// error instanceof Deno.errors.NotFound || -// // On Windows, the underlying Windows API may return -// // `ERROR_ACCESS_DENIED` when the process has exited, but hasn't been -// // completely cleaned up yet and its `pid` is still valid. -// (Deno.build.os === "windows" && -// error instanceof Deno.errors.PermissionDenied), -// ); +Deno.test( + { permissions: { run: true, read: true } }, + async function runKillAfterStatus() { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [Deno.execPath(), "eval", 'console.log("hello")'], + }); + await p.status(); + + let error = null; + try { + p.kill("SIGTERM"); + } catch (e) { + error = e; + } + + assert( + error instanceof Deno.errors.NotFound || + // On Windows, the underlying Windows API may return + // `ERROR_ACCESS_DENIED` when the process has exited, but hasn't been + // completely cleaned up yet and its `pid` is still valid. + (Deno.build.os === "windows" && + error instanceof Deno.errors.PermissionDenied), + ); -// p.close(); -// }, -// ); + p.close(); + }, +); -// Deno.test({ permissions: { run: false } }, function killPermissions() { -// assertThrows(() => { -// // Unlike the other test cases, we don't have permission to spawn a -// // subprocess we can safely kill. Instead we send SIGCONT to the current -// // process - assuming that Deno does not have a special handler set for it -// // and will just continue even if a signal is erroneously sent. -// Deno.kill(Deno.pid, "SIGCONT"); -// }, Deno.errors.PermissionDenied); -// }); +Deno.test({ permissions: { run: false } }, function killPermissions() { + assertThrows(() => { + // Unlike the other test cases, we don't have permission to spawn a + // subprocess we can safely kill. Instead we send SIGCONT to the current + // process - assuming that Deno does not have a special handler set for it + // and will just continue even if a signal is erroneously sent. + Deno.kill(Deno.pid, "SIGCONT"); + }, Deno.errors.PermissionDenied); +}); -// Deno.test( -// { ignore: Deno.build.os !== "windows", permissions: { run: true } }, -// function negativePidInvalidWindows() { -// assertThrows(() => { -// Deno.kill(-1, "SIGTERM"); -// }, TypeError); -// }, -// ); +Deno.test( + { ignore: Deno.build.os !== "windows", permissions: { run: true } }, + function negativePidInvalidWindows() { + assertThrows(() => { + Deno.kill(-1, "SIGTERM"); + }, TypeError); + }, +); -// Deno.test( -// { ignore: Deno.build.os !== "windows", permissions: { run: true } }, -// function invalidSignalNameWindows() { -// assertThrows(() => { -// Deno.kill(Deno.pid, "SIGUSR1"); -// }, TypeError); -// }, -// ); +Deno.test( + { ignore: Deno.build.os !== "windows", permissions: { run: true } }, + function invalidSignalNameWindows() { + assertThrows(() => { + Deno.kill(Deno.pid, "SIGUSR1"); + }, TypeError); + }, +); -// Deno.test( -// { permissions: { run: true, read: true } }, -// async function killSuccess() { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [Deno.execPath(), "eval", "setTimeout(() => {}, 10000)"], -// }); +Deno.test( + { permissions: { run: true, read: true } }, + async function killSuccess() { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [Deno.execPath(), "eval", "setTimeout(() => {}, 10000)"], + }); -// try { -// Deno.kill(p.pid, "SIGKILL"); -// const status = await p.status(); - -// assertEquals(status.success, false); -// if (Deno.build.os === "windows") { -// assertEquals(status.code, 1); -// assertEquals(status.signal, undefined); -// } else { -// assertEquals(status.code, 137); -// assertEquals(status.signal, 9); -// } -// } finally { -// p.close(); -// } -// }, -// ); + try { + Deno.kill(p.pid, "SIGKILL"); + const status = await p.status(); + + assertEquals(status.success, false); + if (Deno.build.os === "windows") { + assertEquals(status.code, 1); + assertEquals(status.signal, undefined); + } else { + assertEquals(status.code, 137); + assertEquals(status.signal, 9); + } + } finally { + p.close(); + } + }, +); -// Deno.test({ permissions: { run: true, read: true } }, function killFailed() { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [Deno.execPath(), "eval", "setTimeout(() => {}, 10000)"], -// }); -// assert(!p.stdin); -// assert(!p.stdout); +Deno.test({ permissions: { run: true, read: true } }, function killFailed() { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [Deno.execPath(), "eval", "setTimeout(() => {}, 10000)"], + }); + assert(!p.stdin); + assert(!p.stdout); -// assertThrows(() => { -// // @ts-expect-error testing runtime error of bad signal -// Deno.kill(p.pid, "foobar"); -// }, TypeError); + assertThrows(() => { + // @ts-expect-error testing runtime error of bad signal + Deno.kill(p.pid, "foobar"); + }, TypeError); -// p.close(); -// }); + p.close(); +}); -// Deno.test( -// { permissions: { run: true, read: true, env: true } }, -// async function clearEnv(): Promise { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [ -// Deno.execPath(), -// "eval", -// "-p", -// "JSON.stringify(Deno.env.toObject())", -// ], -// stdout: "piped", -// clearEnv: true, -// env: { -// FOO: "23147", -// }, -// }); +Deno.test( + { permissions: { run: true, read: true, env: true } }, + async function clearEnv(): Promise { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [ + Deno.execPath(), + "eval", + "-p", + "JSON.stringify(Deno.env.toObject())", + ], + stdout: "piped", + clearEnv: true, + env: { + FOO: "23147", + }, + }); -// const obj = JSON.parse(new TextDecoder().decode(await p.output())); + const obj = JSON.parse(new TextDecoder().decode(await p.output())); -// // can't check for object equality because the OS may set additional env -// // vars for processes, so we check if PATH isn't present as that is a common -// // env var across OS's and isn't set for processes. -// assertEquals(obj.FOO, "23147"); -// assert(!("PATH" in obj)); + // can't check for object equality because the OS may set additional env + // vars for processes, so we check if PATH isn't present as that is a common + // env var across OS's and isn't set for processes. + assertEquals(obj.FOO, "23147"); + assert(!("PATH" in obj)); -// p.close(); -// }, -// ); + p.close(); + }, +); -// Deno.test( -// { -// permissions: { run: true, read: true }, -// ignore: Deno.build.os === "windows", -// }, -// async function uid(): Promise { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [ -// "id", -// "-u", -// ], -// stdout: "piped", -// }); +Deno.test( + { + permissions: { run: true, read: true }, + ignore: Deno.build.os === "windows", + }, + async function uid(): Promise { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [ + "id", + "-u", + ], + stdout: "piped", + }); -// const currentUid = new TextDecoder().decode(await p.output()); -// p.close(); + const currentUid = new TextDecoder().decode(await p.output()); + p.close(); -// if (currentUid !== "0") { -// assertThrows(() => { -// // deno-lint-ignore no-deprecated-deno-api -// Deno.run({ -// cmd: [ -// "echo", -// "fhqwhgads", -// ], -// uid: 0, -// }); -// }, Deno.errors.PermissionDenied); -// } -// }, -// ); + if (currentUid !== "0") { + assertThrows(() => { + // deno-lint-ignore no-deprecated-deno-api + Deno.run({ + cmd: [ + "echo", + "fhqwhgads", + ], + uid: 0, + }); + }, Deno.errors.PermissionDenied); + } + }, +); -// Deno.test( -// { -// permissions: { run: true, read: true }, -// ignore: Deno.build.os === "windows", -// }, -// async function gid(): Promise { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [ -// "id", -// "-g", -// ], -// stdout: "piped", -// }); +Deno.test( + { + permissions: { run: true, read: true }, + ignore: Deno.build.os === "windows", + }, + async function gid(): Promise { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [ + "id", + "-g", + ], + stdout: "piped", + }); -// const currentGid = new TextDecoder().decode(await p.output()); -// p.close(); + const currentGid = new TextDecoder().decode(await p.output()); + p.close(); -// if (currentGid !== "0") { -// assertThrows(() => { -// // deno-lint-ignore no-deprecated-deno-api -// Deno.run({ -// cmd: [ -// "echo", -// "fhqwhgads", -// ], -// gid: 0, -// }); -// }, Deno.errors.PermissionDenied); -// } -// }, -// ); + if (currentGid !== "0") { + assertThrows(() => { + // deno-lint-ignore no-deprecated-deno-api + Deno.run({ + cmd: [ + "echo", + "fhqwhgads", + ], + gid: 0, + }); + }, Deno.errors.PermissionDenied); + } + }, +); -// Deno.test( -// { -// permissions: { run: true, read: true, write: true }, -// ignore: Deno.build.os === "windows", -// }, -// async function non_existent_cwd(): Promise { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [ -// Deno.execPath(), -// "eval", -// `const dir = Deno.makeTempDirSync(); -// Deno.chdir(dir); -// Deno.removeSync(dir); -// const p = Deno.run({cmd:[Deno.execPath(), "eval", "console.log(1);"]}); -// const { code } = await p.status(); -// p.close(); -// Deno.exit(code); -// `, -// ], -// stdout: "piped", -// stderr: "piped", -// }); +Deno.test( + { + permissions: { run: true, read: true, write: true }, + ignore: Deno.build.os === "windows", + }, + async function non_existent_cwd(): Promise { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [ + Deno.execPath(), + "eval", + `const dir = Deno.makeTempDirSync(); + Deno.chdir(dir); + Deno.removeSync(dir); + const p = Deno.run({cmd:[Deno.execPath(), "eval", "console.log(1);"]}); + const { code } = await p.status(); + p.close(); + Deno.exit(code); + `, + ], + stdout: "piped", + stderr: "piped", + }); -// const { code } = await p.status(); -// const stderr = new TextDecoder().decode(await p.stderrOutput()); -// p.close(); -// p.stdout.close(); -// assertStrictEquals(code, 1); -// assertStringIncludes(stderr, "Failed getting cwd."); -// }, -// ); + const { code } = await p.status(); + const stderr = new TextDecoder().decode(await p.stderrOutput()); + p.close(); + p.stdout.close(); + assertStrictEquals(code, 1); + assertStringIncludes(stderr, "Failed getting cwd."); + }, +); From ef2b573885c987f0f77244a102e17559dae72118 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Wed, 3 Apr 2024 18:41:07 -0700 Subject: [PATCH 08/18] more fixes --- tests/unit/process_test.ts | 632 ++++++++++++++++++------------------- tests/util/std | 2 +- 2 files changed, 316 insertions(+), 318 deletions(-) diff --git a/tests/unit/process_test.ts b/tests/unit/process_test.ts index f63f27b9bebb9..040c6ee197e02 100644 --- a/tests/unit/process_test.ts +++ b/tests/unit/process_test.ts @@ -7,325 +7,289 @@ import { assertThrows, } from "./test_util.ts"; -// Deno.test( -// { permissions: { read: true, run: false } }, -// function runPermissions() { -// assertThrows(() => { -// // deno-lint-ignore no-deprecated-deno-api -// Deno.run({ -// cmd: [Deno.execPath(), "eval", "console.log('hello world')"], -// }); -// }, Deno.errors.PermissionDenied); -// }, -// ); - -// Deno.test( -// { permissions: { run: true, read: true } }, -// async function runSuccess() { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// // freeze the array to ensure it's not modified -// cmd: Object.freeze([ -// Deno.execPath(), -// "eval", -// "console.log('hello world')", -// ]), -// stdout: "piped", -// stderr: "null", -// }); -// const status = await p.status(); -// assertEquals(status.success, true); -// assertEquals(status.code, 0); -// assertEquals(status.signal, undefined); -// p.stdout.close(); -// p.close(); -// }, -// ); - -// Deno.test( -// { permissions: { run: true, read: true } }, -// async function runUrl() { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [ -// new URL(`file:///${Deno.execPath()}`), -// "eval", -// "console.log('hello world')", -// ], -// stdout: "piped", -// stderr: "null", -// }); -// const status = await p.status(); -// assertEquals(status.success, true); -// assertEquals(status.code, 0); -// assertEquals(status.signal, undefined); -// p.stdout.close(); -// p.close(); -// }, -// ); - -// Deno.test( -// { permissions: { run: true, read: true } }, -// async function runStdinRid0(): Promise< -// void -// > { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [Deno.execPath(), "eval", "console.log('hello world')"], -// stdin: 0, -// stdout: "piped", -// stderr: "null", -// }); -// const status = await p.status(); -// assertEquals(status.success, true); -// assertEquals(status.code, 0); -// assertEquals(status.signal, undefined); -// p.stdout.close(); -// p.close(); -// }, -// ); - -// Deno.test( -// { permissions: { run: true, read: true } }, -// function runInvalidStdio() { -// assertThrows(() => -// // deno-lint-ignore no-deprecated-deno-api -// Deno.run({ -// cmd: [Deno.execPath(), "eval", "console.log('hello world')"], -// // @ts-expect-error because Deno.run should throw on invalid stdin. -// stdin: "a", -// }) -// ); -// assertThrows(() => -// // deno-lint-ignore no-deprecated-deno-api -// Deno.run({ -// cmd: [Deno.execPath(), "eval", "console.log('hello world')"], -// // @ts-expect-error because Deno.run should throw on invalid stdout. -// stdout: "b", -// }) -// ); -// assertThrows(() => -// // deno-lint-ignore no-deprecated-deno-api -// Deno.run({ -// cmd: [Deno.execPath(), "eval", "console.log('hello world')"], -// // @ts-expect-error because Deno.run should throw on invalid stderr. -// stderr: "c", -// }) -// ); -// }, -// ); - -// Deno.test( -// { permissions: { run: true, read: true } }, -// async function runCommandFailedWithCode() { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [Deno.execPath(), "eval", "Deno.exit(41 + 1)"], -// }); -// const status = await p.status(); -// assertEquals(status.success, false); -// assertEquals(status.code, 42); -// assertEquals(status.signal, undefined); -// p.close(); -// }, -// ); - -// Deno.test( -// { -// permissions: { run: true, read: true }, -// }, -// async function runCommandFailedWithSignal() { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [ -// Deno.execPath(), -// "eval", -// "Deno.kill(Deno.pid, 'SIGKILL')", -// ], -// }); -// const status = await p.status(); -// assertEquals(status.success, false); -// if (Deno.build.os === "windows") { -// assertEquals(status.code, 1); -// assertEquals(status.signal, undefined); -// } else { -// assertEquals(status.code, 128 + 9); -// assertEquals(status.signal, 9); -// } -// p.close(); -// }, -// ); - -// Deno.test({ permissions: { run: true } }, function runNotFound() { -// let error; -// try { -// // deno-lint-ignore no-deprecated-deno-api -// Deno.run({ cmd: ["this file hopefully doesn't exist"] }); -// } catch (e) { -// error = e; -// } -// assert(error !== undefined); -// assert(error instanceof Deno.errors.NotFound); -// }); - -// Deno.test( -// { permissions: { write: true, run: true, read: true } }, -// async function runWithCwdIsAsync() { -// const enc = new TextEncoder(); -// const cwd = await Deno.makeTempDir({ prefix: "deno_command_test" }); - -// const exitCodeFile = "deno_was_here"; -// const programFile = "poll_exit.ts"; -// const program = ` -// async function tryExit() { -// try { -// const code = parseInt(await Deno.readTextFile("${exitCodeFile}")); -// Deno.exit(code); -// } catch { -// // Retry if we got here before deno wrote the file. -// setTimeout(tryExit, 0.01); -// } -// } - -// tryExit(); -// `; - -// Deno.writeFileSync(`${cwd}/${programFile}`, enc.encode(program)); -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cwd, -// cmd: [Deno.execPath(), "run", "--allow-read", programFile], -// }); - -// // Write the expected exit code *after* starting deno. -// // This is how we verify that `run()` is actually asynchronous. -// const code = 84; -// Deno.writeFileSync(`${cwd}/${exitCodeFile}`, enc.encode(`${code}`)); - -// const status = await p.status(); -// assertEquals(status.success, false); -// assertEquals(status.code, code); -// assertEquals(status.signal, undefined); -// p.close(); -// }, -// ); - -// Deno.test( -// { permissions: { run: true, read: true } }, -// async function runStdinPiped(): Promise< -// void -// > { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [ -// Deno.execPath(), -// "eval", -// ` -// const buffer = new Uint8Array(5); -// await Deno.stdin.read(buffer); -// if (new TextDecoder().decode(buffer) !== "hello") { -// throw new Error('Expected \\'hello\\'') -// } -// `, -// ], -// stdin: "piped", -// }); -// assert(p.stdin); -// assert(!p.stdout); -// assert(!p.stderr); - -// const msg = new TextEncoder().encode("hello"); -// const n = await p.stdin.write(msg); -// assertEquals(n, msg.byteLength); - -// p.stdin.close(); - -// const status = await p.status(); -// assertEquals(status.success, true); -// assertEquals(status.code, 0); -// assertEquals(status.signal, undefined); -// p.close(); -// }, -// ); - -// Deno.test( -// { permissions: { run: true, read: true } }, -// async function runStdoutPiped(): Promise< -// void -// > { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [ -// Deno.execPath(), -// "eval", -// "await Deno.stdout.write(new TextEncoder().encode('hello'))", -// ], -// stdout: "piped", -// }); -// assert(!p.stdin); -// assert(!p.stderr); - -// const data = new Uint8Array(10); -// let r = await p.stdout.read(data); -// if (r === null) { -// throw new Error("p.stdout.read(...) should not be null"); -// } -// assertEquals(r, 5); -// const s = new TextDecoder().decode(data.subarray(0, r)); -// assertEquals(s, "hello"); -// r = await p.stdout.read(data); -// assertEquals(r, null); -// p.stdout.close(); - -// const status = await p.status(); -// assertEquals(status.success, true); -// assertEquals(status.code, 0); -// assertEquals(status.signal, undefined); -// p.close(); -// }, -// ); - -// Deno.test( -// { permissions: { run: true, read: true } }, -// async function runStderrPiped(): Promise< -// void -// > { -// // deno-lint-ignore no-deprecated-deno-api -// const p = Deno.run({ -// cmd: [ -// Deno.execPath(), -// "--no-code-cache", -// "eval", -// "await Deno.stderr.write(new TextEncoder().encode('hello'))", -// ], -// stderr: "piped", -// }); -// assert(!p.stdin); -// assert(!p.stdout); - -// const data = new Uint8Array(10); -// let r = await p.stderr.read(data); -// if (r === null) { -// throw new Error("p.stderr.read should not return null here"); -// } -// assertEquals(r, 5); -// const s = new TextDecoder().decode(data.subarray(0, r)); -// assertEquals(s, "hello"); -// r = await p.stderr.read(data); -// assertEquals(r, null); -// p.stderr!.close(); - -// const status = await p.status(); -// assertEquals(status.success, true); -// assertEquals(status.code, 0); -// assertEquals(status.signal, undefined); -// p.close(); -// }, -// ); +Deno.test( + { permissions: { read: true, run: false } }, + function runPermissions() { + assertThrows(() => { + // deno-lint-ignore no-deprecated-deno-api + Deno.run({ + cmd: [Deno.execPath(), "eval", "console.log('hello world')"], + }); + }, Deno.errors.PermissionDenied); + }, +); Deno.test( { permissions: { run: true, read: true } }, - async function runOutput() { + async function runSuccess() { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + // freeze the array to ensure it's not modified + cmd: Object.freeze([ + Deno.execPath(), + "eval", + "console.log('hello world')", + ]), + stdout: "piped", + stderr: "null", + }); + const status = await p.status(); + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, undefined); + p.stdout.close(); + p.close(); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function runUrl() { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [ + new URL(`file:///${Deno.execPath()}`), + "eval", + "console.log('hello world')", + ], + stdout: "piped", + stderr: "null", + }); + const status = await p.status(); + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, undefined); + p.stdout.close(); + p.close(); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function runStdinRid0(): Promise< + void + > { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [Deno.execPath(), "eval", "console.log('hello world')"], + stdin: 0, + stdout: "piped", + stderr: "null", + }); + const status = await p.status(); + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, undefined); + p.stdout.close(); + p.close(); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + function runInvalidStdio() { + assertThrows(() => + // deno-lint-ignore no-deprecated-deno-api + Deno.run({ + cmd: [Deno.execPath(), "eval", "console.log('hello world')"], + // @ts-expect-error because Deno.run should throw on invalid stdin. + stdin: "a", + }) + ); + assertThrows(() => + // deno-lint-ignore no-deprecated-deno-api + Deno.run({ + cmd: [Deno.execPath(), "eval", "console.log('hello world')"], + // @ts-expect-error because Deno.run should throw on invalid stdout. + stdout: "b", + }) + ); + assertThrows(() => + // deno-lint-ignore no-deprecated-deno-api + Deno.run({ + cmd: [Deno.execPath(), "eval", "console.log('hello world')"], + // @ts-expect-error because Deno.run should throw on invalid stderr. + stderr: "c", + }) + ); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function runCommandFailedWithCode() { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [Deno.execPath(), "eval", "Deno.exit(41 + 1)"], + }); + const status = await p.status(); + assertEquals(status.success, false); + assertEquals(status.code, 42); + assertEquals(status.signal, undefined); + p.close(); + }, +); + +Deno.test( + { + permissions: { run: true, read: true }, + }, + async function runCommandFailedWithSignal() { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [ + Deno.execPath(), + "eval", + "Deno.kill(Deno.pid, 'SIGKILL')", + ], + }); + const status = await p.status(); + assertEquals(status.success, false); + if (Deno.build.os === "windows") { + assertEquals(status.code, 1); + assertEquals(status.signal, undefined); + } else { + assertEquals(status.code, 128 + 9); + assertEquals(status.signal, 9); + } + p.close(); + }, +); + +Deno.test({ permissions: { run: true } }, function runNotFound() { + let error; + try { + // deno-lint-ignore no-deprecated-deno-api + Deno.run({ cmd: ["this file hopefully doesn't exist"] }); + } catch (e) { + error = e; + } + assert(error !== undefined); + assert(error instanceof Deno.errors.NotFound); +}); + +Deno.test( + { permissions: { write: true, run: true, read: true } }, + async function runWithCwdIsAsync() { + const enc = new TextEncoder(); + const cwd = await Deno.makeTempDir({ prefix: "deno_command_test" }); + + const exitCodeFile = "deno_was_here"; + const programFile = "poll_exit.ts"; + const program = ` +async function tryExit() { + try { + const code = parseInt(await Deno.readTextFile("${exitCodeFile}")); + Deno.exit(code); + } catch { + // Retry if we got here before deno wrote the file. + setTimeout(tryExit, 0.01); + } +} + +tryExit(); +`; + + Deno.writeFileSync(`${cwd}/${programFile}`, enc.encode(program)); + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cwd, + cmd: [Deno.execPath(), "run", "--allow-read", programFile], + }); + + // Write the expected exit code *after* starting deno. + // This is how we verify that `run()` is actually asynchronous. + const code = 84; + Deno.writeFileSync(`${cwd}/${exitCodeFile}`, enc.encode(`${code}`)); + + const status = await p.status(); + assertEquals(status.success, false); + assertEquals(status.code, code); + assertEquals(status.signal, undefined); + p.close(); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function runStdinPiped(): Promise< + void + > { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [ + Deno.execPath(), + "eval", + ` + const buffer = new Uint8Array(5); + await Deno.stdin.read(buffer); + if (new TextDecoder().decode(buffer) !== "hello") { + throw new Error('Expected \\'hello\\'') + } + `, + ], + stdin: "piped", + }); + assert(p.stdin); + assert(!p.stdout); + assert(!p.stderr); + + const msg = new TextEncoder().encode("hello"); + const n = await p.stdin.write(msg); + assertEquals(n, msg.byteLength); + + p.stdin.close(); + + const status = await p.status(); + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, undefined); + p.close(); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function runStdoutPiped(): Promise< + void + > { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [ + Deno.execPath(), + "eval", + "await Deno.stdout.write(new TextEncoder().encode('hello'))", + ], + stdout: "piped", + }); + assert(!p.stdin); + assert(!p.stderr); + + const data = new Uint8Array(10); + let r = await p.stdout.read(data); + if (r === null) { + throw new Error("p.stdout.read(...) should not be null"); + } + assertEquals(r, 5); + const s = new TextDecoder().decode(data.subarray(0, r)); + assertEquals(s, "hello"); + r = await p.stdout.read(data); + assertEquals(r, null); + p.stdout.close(); + + const status = await p.status(); + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, undefined); + p.close(); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function runStderrPiped(): Promise< + void + > { // deno-lint-ignore no-deprecated-deno-api const p = Deno.run({ cmd: [ @@ -333,6 +297,41 @@ Deno.test( "eval", "await Deno.stderr.write(new TextEncoder().encode('hello'))", ], + stderr: "piped", + }); + assert(!p.stdin); + assert(!p.stdout); + + const data = new Uint8Array(10); + let r = await p.stderr.read(data); + if (r === null) { + throw new Error("p.stderr.read should not return null here"); + } + assertEquals(r, 5); + const s = new TextDecoder().decode(data.subarray(0, r)); + assertEquals(s, "hello"); + r = await p.stderr.read(data); + assertEquals(r, null); + p.stderr!.close(); + + const status = await p.status(); + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, undefined); + p.close(); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function runOutput() { + // deno-lint-ignore no-deprecated-deno-api + const p = Deno.run({ + cmd: [ + Deno.execPath(), + "eval", + "await Deno.stdout.write(new TextEncoder().encode('hello'))", + ], stdout: "piped", }); const output = await p.output(); @@ -351,7 +350,6 @@ Deno.test( const p = Deno.run({ cmd: [ Deno.execPath(), - //"-Ldebug", "eval", "await Deno.stderr.write(new TextEncoder().encode('error'))", ], diff --git a/tests/util/std b/tests/util/std index e0ef24091e87f..7d419485c74bc 160000 --- a/tests/util/std +++ b/tests/util/std @@ -1 +1 @@ -Subproject commit e0ef24091e87f84d44d495d432d611625b281249 +Subproject commit 7d419485c74bcdc4a03857dd4d427d4442b058f3 From 372d2b799b3b90a67bb2d686f70cec22a406fe05 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Wed, 3 Apr 2024 19:20:17 -0700 Subject: [PATCH 09/18] fix args tests --- cli/args/flags.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index ed1859f8deb2f..f57164368d1cc 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -4444,6 +4444,7 @@ mod tests { ..Default::default() }, log_level: Some(Level::Error), + code_cache_enabled: true, ..Flags::default() } ); @@ -4515,6 +4516,7 @@ mod tests { "script.ts".to_string() )), reload: true, + code_cache_enabled: true, ..Flags::default() } ); @@ -4536,6 +4538,7 @@ mod tests { exclude: vec![], }), }), + code_cache_enabled: true, ..Flags::default() } ); @@ -4560,6 +4563,7 @@ mod tests { exclude: vec![], }), }), + code_cache_enabled: true, ..Flags::default() } ); @@ -4584,6 +4588,7 @@ mod tests { exclude: vec![], }), }), + code_cache_enabled: true, ..Flags::default() } ); @@ -4608,6 +4613,7 @@ mod tests { exclude: vec![], }), }), + code_cache_enabled: true, ..Flags::default() } ); @@ -4634,6 +4640,7 @@ mod tests { exclude: vec![], }), }), + code_cache_enabled: true, ..Flags::default() } ); @@ -4662,6 +4669,7 @@ mod tests { exclude: vec![], }), }), + code_cache_enabled: true, ..Flags::default() } ); @@ -4690,6 +4698,7 @@ mod tests { exclude: vec![String::from("foo")], }), }), + code_cache_enabled: true, ..Flags::default() } ); @@ -4714,6 +4723,7 @@ mod tests { exclude: vec![String::from("bar")], }), }), + code_cache_enabled: true, ..Flags::default() } ); @@ -4739,6 +4749,7 @@ mod tests { exclude: vec![String::from("foo"), String::from("bar")], }), }), + code_cache_enabled: true, ..Flags::default() } ); @@ -4764,6 +4775,7 @@ mod tests { exclude: vec![String::from("baz"), String::from("qux"),], }), }), + code_cache_enabled: true, ..Flags::default() } ); @@ -4781,6 +4793,7 @@ mod tests { "script.ts".to_string() )), allow_write: Some(vec![]), + code_cache_enabled: true, ..Flags::default() } ); @@ -4794,6 +4807,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Run(RunFlags::new_default("_".to_string())), v8_flags: svec!["--help"], + code_cache_enabled: true, ..Flags::default() } ); @@ -4811,6 +4825,7 @@ mod tests { "script.ts".to_string() )), v8_flags: svec!["--expose-gc", "--gc-stats=1"], + code_cache_enabled: true, ..Flags::default() } ); @@ -4864,6 +4879,7 @@ mod tests { )), argv: svec!["--title", "X"], allow_net: Some(vec![]), + code_cache_enabled: true, ..Flags::default() } ); @@ -4887,6 +4903,7 @@ mod tests { allow_write: Some(vec![]), allow_ffi: Some(vec![]), allow_hrtime: true, + code_cache_enabled: true, ..Flags::default() } ); @@ -4902,6 +4919,7 @@ mod tests { "gist.ts".to_string() )), allow_read: Some(vec![]), + code_cache_enabled: true, ..Flags::default() } ); @@ -4917,6 +4935,7 @@ mod tests { "gist.ts".to_string() )), deny_read: Some(vec![]), + code_cache_enabled: true, ..Flags::default() } ); @@ -4932,6 +4951,7 @@ mod tests { "gist.ts".to_string(), )), allow_hrtime: true, + code_cache_enabled: true, ..Flags::default() } ); @@ -4947,6 +4967,7 @@ mod tests { "gist.ts".to_string(), )), deny_hrtime: true, + code_cache_enabled: true, ..Flags::default() } ); @@ -4974,6 +4995,7 @@ mod tests { )), argv: svec!["--", "-D", "--allow-net"], allow_write: Some(vec![]), + code_cache_enabled: true, ..Flags::default() } ); @@ -5688,6 +5710,7 @@ mod tests { "script.ts".to_string(), )), config_flag: ConfigFlag::Path("tsconfig.json".to_owned()), + code_cache_enabled: true, ..Flags::default() } ); @@ -5982,6 +6005,7 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags::new_default( "script.ts".to_string(), )), + code_cache_enabled: true, ..Flags::default() } ); @@ -6006,6 +6030,7 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags::new_default( "script.ts".to_string(), )), + code_cache_enabled: true, ..Flags::default() } ); @@ -6030,6 +6055,7 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags::new_default( "script.ts".to_string(), )), + code_cache_enabled: true, ..Flags::default() } ); @@ -6054,6 +6080,7 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags::new_default( "script.ts".to_string(), )), + code_cache_enabled: true, ..Flags::default() } ); @@ -6074,6 +6101,7 @@ mod tests { "script.ts".to_string(), )), allow_net: Some(svec!["127.0.0.1"]), + code_cache_enabled: true, ..Flags::default() } ); @@ -6090,6 +6118,7 @@ mod tests { "script.ts".to_string(), )), deny_net: Some(svec!["127.0.0.1"]), + code_cache_enabled: true, ..Flags::default() } ); @@ -6106,6 +6135,7 @@ mod tests { "script.ts".to_string(), )), allow_env: Some(svec!["HOME"]), + code_cache_enabled: true, ..Flags::default() } ); @@ -6122,6 +6152,7 @@ mod tests { "script.ts".to_string(), )), deny_env: Some(svec!["HOME"]), + code_cache_enabled: true, ..Flags::default() } ); @@ -6142,6 +6173,7 @@ mod tests { "script.ts".to_string(), )), allow_env: Some(svec!["HOME", "PATH"]), + code_cache_enabled: true, ..Flags::default() } ); @@ -6158,6 +6190,7 @@ mod tests { "script.ts".to_string(), )), deny_env: Some(svec!["HOME", "PATH"]), + code_cache_enabled: true, ..Flags::default() } ); @@ -6199,6 +6232,7 @@ mod tests { "script.ts".to_string(), )), allow_sys: Some(vec![]), + code_cache_enabled: true, ..Flags::default() } ); @@ -6214,6 +6248,7 @@ mod tests { "script.ts".to_string(), )), deny_sys: Some(vec![]), + code_cache_enabled: true, ..Flags::default() } ); @@ -6230,6 +6265,7 @@ mod tests { "script.ts".to_string(), )), allow_sys: Some(svec!["hostname"]), + code_cache_enabled: true, ..Flags::default() } ); @@ -6246,6 +6282,7 @@ mod tests { "script.ts".to_string(), )), deny_sys: Some(svec!["hostname"]), + code_cache_enabled: true, ..Flags::default() } ); @@ -6266,6 +6303,7 @@ mod tests { "script.ts".to_string(), )), allow_sys: Some(svec!["hostname", "osRelease"]), + code_cache_enabled: true, ..Flags::default() } ); @@ -6286,6 +6324,7 @@ mod tests { "script.ts".to_string(), )), deny_sys: Some(svec!["hostname", "osRelease"]), + code_cache_enabled: true, ..Flags::default() } ); @@ -6593,6 +6632,7 @@ mod tests { "script.ts".to_string(), )), import_map_path: Some("import_map.json".to_owned()), + code_cache_enabled: true, ..Flags::default() } ); @@ -6674,6 +6714,22 @@ mod tests { "script.ts".to_string(), )), env_file: Some(".env".to_owned()), + code_cache_enabled: true, + ..Flags::default() + } + ); + } + + #[test] + fn run_no_code_cache() { + let r = + flags_from_vec(svec!["deno", "run", "--no-code-cache", "script.ts"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Run(RunFlags::new_default( + "script.ts".to_string(), + )), ..Flags::default() } ); @@ -6690,6 +6746,7 @@ mod tests { "script.ts".to_string(), )), env_file: Some(".another_env".to_owned()), + code_cache_enabled: true, ..Flags::default() } ); @@ -6721,6 +6778,7 @@ mod tests { )), seed: Some(250_u64), v8_flags: svec!["--random-seed=250"], + code_cache_enabled: true, ..Flags::default() } ); @@ -6744,6 +6802,7 @@ mod tests { )), seed: Some(250_u64), v8_flags: svec!["--expose-gc", "--random-seed=250"], + code_cache_enabled: true, ..Flags::default() } ); @@ -6875,6 +6934,7 @@ mod tests { "script.ts".to_string(), )), log_level: Some(Level::Debug), + code_cache_enabled: true, ..Flags::default() } ); @@ -6890,6 +6950,7 @@ mod tests { "script.ts".to_string(), )), log_level: Some(Level::Error), + code_cache_enabled: true, ..Flags::default() } ); @@ -6923,6 +6984,7 @@ mod tests { "script.ts".to_string(), )), argv: svec!["--allow-read", "--allow-net"], + code_cache_enabled: true, ..Flags::default() } ); @@ -6948,6 +7010,7 @@ mod tests { location: Some(Url::parse("https://foo/").unwrap()), allow_read: Some(vec![]), argv: svec!["--allow-net", "-r", "--help", "--foo", "bar"], + code_cache_enabled: true, ..Flags::default() } ); @@ -6960,6 +7023,7 @@ mod tests { "script.ts".to_string(), )), argv: svec!["foo", "bar"], + code_cache_enabled: true, ..Flags::default() } ); @@ -6971,6 +7035,7 @@ mod tests { "script.ts".to_string(), )), argv: svec!["-"], + code_cache_enabled: true, ..Flags::default() } ); @@ -6984,6 +7049,7 @@ mod tests { "script.ts".to_string(), )), argv: svec!["-", "foo", "bar"], + code_cache_enabled: true, ..Flags::default() } ); @@ -6999,6 +7065,7 @@ mod tests { "script.ts".to_string(), )), type_check_mode: TypeCheckMode::None, + code_cache_enabled: true, ..Flags::default() } ); @@ -7015,6 +7082,7 @@ mod tests { "script.ts".to_string(), )), type_check_mode: TypeCheckMode::Local, + code_cache_enabled: true, ..Flags::default() } ); @@ -7059,6 +7127,7 @@ mod tests { "script.ts".to_string(), )), unsafely_ignore_certificate_errors: Some(vec![]), + code_cache_enabled: true, ..Flags::default() } ); @@ -7086,6 +7155,7 @@ mod tests { "[::1]", "1.2.3.4" ]), + code_cache_enabled: true, ..Flags::default() } ); @@ -7129,6 +7199,7 @@ mod tests { "script.ts".to_string(), )), no_remote: true, + code_cache_enabled: true, ..Flags::default() } ); @@ -7144,6 +7215,7 @@ mod tests { "script.ts".to_string(), )), no_npm: true, + code_cache_enabled: true, ..Flags::default() } ); @@ -7160,6 +7232,7 @@ mod tests { "script.ts".to_string(), )), node_modules_dir: Some(true), + code_cache_enabled: true, ..Flags::default() } ); @@ -7177,6 +7250,7 @@ mod tests { "script.ts".to_string(), )), node_modules_dir: Some(false), + code_cache_enabled: true, ..Flags::default() } ); @@ -7192,6 +7266,7 @@ mod tests { "script.ts".to_string(), )), vendor: Some(true), + code_cache_enabled: true, ..Flags::default() } ); @@ -7204,6 +7279,7 @@ mod tests { "script.ts".to_string(), )), vendor: Some(false), + code_cache_enabled: true, ..Flags::default() } ); @@ -7219,6 +7295,7 @@ mod tests { "script.ts".to_string(), )), cached_only: true, + code_cache_enabled: true, ..Flags::default() } ); @@ -7247,6 +7324,7 @@ mod tests { "127.0.0.1:4545", "localhost:4545" ]), + code_cache_enabled: true, ..Flags::default() } ); @@ -7275,6 +7353,7 @@ mod tests { "127.0.0.1:4545", "localhost:4545" ]), + code_cache_enabled: true, ..Flags::default() } ); @@ -7306,6 +7385,7 @@ mod tests { "localhost:5678", "[::1]:8080" ]), + code_cache_enabled: true, ..Flags::default() } ); @@ -7337,6 +7417,7 @@ mod tests { "localhost:5678", "[::1]:8080" ]), + code_cache_enabled: true, ..Flags::default() } ); @@ -7359,6 +7440,7 @@ mod tests { )), lock_write: true, lock: Some(String::from("lock.json")), + code_cache_enabled: true, ..Flags::default() } ); @@ -7371,6 +7453,7 @@ mod tests { "script.ts".to_string(), )), no_lock: true, + code_cache_enabled: true, ..Flags::default() } ); @@ -7390,6 +7473,7 @@ mod tests { )), lock_write: true, lock: Some(String::from("./deno.lock")), + code_cache_enabled: true, ..Flags::default() } ); @@ -7410,6 +7494,7 @@ mod tests { )), lock_write: true, lock: Some(String::from("lock.json")), + code_cache_enabled: true, ..Flags::default() } ); @@ -7422,6 +7507,7 @@ mod tests { "script.ts".to_string(), )), lock_write: true, + code_cache_enabled: true, ..Flags::default() } ); @@ -7514,6 +7600,7 @@ mod tests { "script.ts".to_string(), )), ca_data: Some(CaData::File("example.crt".to_owned())), + code_cache_enabled: true, ..Flags::default() } ); @@ -7534,6 +7621,7 @@ mod tests { "script.ts".to_string(), )), enable_testing_features: true, + code_cache_enabled: true, ..Flags::default() } ); @@ -8212,6 +8300,7 @@ mod tests { "foo.js".to_string(), )), inspect: Some("127.0.0.1:9229".parse().unwrap()), + code_cache_enabled: true, ..Flags::default() } ); @@ -8227,6 +8316,7 @@ mod tests { "foo.js".to_string(), )), inspect_wait: Some("127.0.0.1:9229".parse().unwrap()), + code_cache_enabled: true, ..Flags::default() } ); @@ -8244,6 +8334,7 @@ mod tests { "foo.js".to_string(), )), inspect_wait: Some("127.0.0.1:3567".parse().unwrap()), + code_cache_enabled: true, ..Flags::default() } ); @@ -8770,6 +8861,7 @@ mod tests { "script.ts".to_string(), )), type_check_mode: TypeCheckMode::Local, + code_cache_enabled: true, ..Flags::default() } ); @@ -8782,6 +8874,7 @@ mod tests { "script.ts".to_string(), )), type_check_mode: TypeCheckMode::All, + code_cache_enabled: true, ..Flags::default() } ); @@ -8794,6 +8887,7 @@ mod tests { "script.ts".to_string(), )), type_check_mode: TypeCheckMode::None, + code_cache_enabled: true, ..Flags::default() } ); @@ -8818,6 +8912,7 @@ mod tests { "script.ts".to_string(), )), config_flag: ConfigFlag::Disabled, + code_cache_enabled: true, ..Flags::default() } ); From 6e738124cccd3b09667380c45e46fadaf2e7dfd0 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Thu, 4 Apr 2024 09:57:54 -0700 Subject: [PATCH 10/18] update sql query --- cli/cache/code_cache.rs | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/cli/cache/code_cache.rs b/cli/cache/code_cache.rs index 4a399f67881dd..f678a42ed5908 100644 --- a/cli/cache/code_cache.rs +++ b/cli/cache/code_cache.rs @@ -13,8 +13,8 @@ pub static CODE_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration { table_initializer: "CREATE TABLE IF NOT EXISTS codecache ( specifier TEXT NOT NULL, type TEXT NOT NULL, - source_hash TEXT, - source_timestamp INTEGER, + source_hash TEXT DEFAULT NULL, + source_timestamp INTEGER DEFAULT NULL, data BLOB NOT NULL, PRIMARY KEY (specifier, type) );", @@ -143,16 +143,23 @@ impl CodeCacheInner { specifier.to_string().into(), code_cache_type.as_str().to_string().into(), ]; - let mut param_index = 3; + + let mut column_index = 3; if let Some(source_hash) = source_hash { - query += &format!(" AND source_hash=?{}", param_index); - param_index += 1; + query += &format!(" AND source_hash=?{}", column_index); + column_index += 1; params.push(source_hash.to_string().into()); + } else { + query += " AND source_hash IS NULL"; } if let Some(source_timestamp) = source_timestamp { - query += &format!(" AND source_timestamp=?{}", param_index); + query += &format!(" AND source_timestamp=?{}", column_index); params.push(source_timestamp.to_string().into()); + } else { + query += " AND source_timestamp IS NULL"; } + query += " LIMIT 1"; + self .conn .query_row(&query, rusqlite::params_from_iter(params), |row| { @@ -174,16 +181,14 @@ impl CodeCacheInner { codecache (specifier, type, source_hash, source_timestamp, data) VALUES (?1, ?2, ?3, ?4, ?5)"; - self.conn.execute( - sql, - params![ - specifier, - code_cache_type.as_str(), - source_hash, - source_timestamp, - data - ], - )?; + let params = params![ + specifier, + code_cache_type.as_str(), + source_hash, + source_timestamp, + data + ]; + self.conn.execute(sql, params)?; Ok(()) } } From 9e33050c5a5010191887f4bc905d50ab059ef5ef Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Thu, 4 Apr 2024 11:16:59 -0700 Subject: [PATCH 11/18] fix tests on windows --- Cargo.lock | 1 + tests/Cargo.toml | 1 + tests/integration/run_tests.rs | 37 +++++++++++++++------------------- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a75aa2ea7888f..d5cd57e925847 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -657,6 +657,7 @@ dependencies = [ "once_cell", "os_pipe", "pretty_assertions", + "regex", "serde", "serde_repr", "test_server", diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 1c2d8de4b114e..8a0c9a031b0d9 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -51,6 +51,7 @@ hyper-util.workspace = true once_cell.workspace = true os_pipe.workspace = true pretty_assertions.workspace = true +regex = "1.1.0" serde.workspace = true serde_repr.workspace = true test_util.workspace = true diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs index ffe53c2b19a6d..e98b01dcb491f 100644 --- a/tests/integration/run_tests.rs +++ b/tests/integration/run_tests.rs @@ -5,6 +5,7 @@ use deno_core::serde_json::json; use deno_core::url; use deno_fetch::reqwest; use pretty_assertions::assert_eq; +use regex::Regex; use std::io::Read; use std::io::Write; use std::process::Command; @@ -5202,13 +5203,11 @@ fn code_cache_test() { let debug_output = std::str::from_utf8(&output.stderr).unwrap(); // There should be no cache hits yet, and the cache should be created. assert!(!debug_output.contains("V8 code cache hit")); - assert!(debug_output.contains( - format!( - "Updating V8 code cache for ES module: file://{}/main.js", - script_dir.path() - ) - .as_str() - )); + assert!(Regex::new( + r"Updating V8 code cache for ES module: file://(.+)/main.js" + ) + .unwrap() + .is_match(debug_output)); // Check that the code cache database exists. let code_cache_path = deno_dir.path().join("code_cache_v1"); @@ -5231,13 +5230,11 @@ fn code_cache_test() { let debug_output = std::str::from_utf8(&output.stderr).unwrap(); // There should be a cache hit, and the cache should not be created. - assert!(debug_output.contains( - format!( - "V8 code cache hit for ES module: file://{}/main.js", - script_dir.path() - ) - .as_str() - )); + assert!(Regex::new( + r"V8 code cache hit for ES module: file://(.+)/main.js" + ) + .unwrap() + .is_match(debug_output)); assert!(!debug_output.contains("Updating V8 code cache")); } @@ -5278,12 +5275,10 @@ fn code_cache_test() { let debug_output = std::str::from_utf8(&output.stderr).unwrap(); assert!(!debug_output.contains("V8 code cache hit")); - assert!(debug_output.contains( - format!( - "Updating V8 code cache for ES module: file://{}/main.js", - script_dir.path() - ) - .as_str() - )); + assert!(Regex::new( + r"Updating V8 code cache for ES module: file://(.+)/main.js" + ) + .unwrap() + .is_match(debug_output)); } } From 99ceea819258b2ba03891ae34e43189b15d51f6c Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Thu, 4 Apr 2024 12:41:58 -0700 Subject: [PATCH 12/18] fix timing test issue on windows --- tests/testdata/run/rejection_handled.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata/run/rejection_handled.ts b/tests/testdata/run/rejection_handled.ts index f058ff966514d..c29ae7089d491 100644 --- a/tests/testdata/run/rejection_handled.ts +++ b/tests/testdata/run/rejection_handled.ts @@ -14,4 +14,4 @@ setTimeout(async () => { setTimeout(() => { console.log("Success"); -}, 50); +}, 200); From a6c297ba2e00441b6efdfa2304b8a68598add2e3 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Mon, 15 Apr 2024 16:51:21 -0700 Subject: [PATCH 13/18] more fixes --- Cargo.lock | 2 +- cli/cache/cache_db.rs | 2 +- cli/cache/code_cache.rs | 173 ++++++--------------------------- cli/cache/deno_dir.rs | 2 +- cli/cache/module_info.rs | 7 ++ cli/graph_util.rs | 2 +- cli/lsp/analysis.rs | 2 +- cli/lsp/cache.rs | 2 +- cli/lsp/completions.rs | 2 +- cli/lsp/config.rs | 2 +- cli/lsp/documents.rs | 2 +- cli/lsp/language_server.rs | 2 +- cli/lsp/tsc.rs | 2 +- cli/module_loader.rs | 106 ++++++++++---------- cli/npm/byonm.rs | 2 +- cli/resolver.rs | 2 +- cli/tools/vendor/mod.rs | 2 +- cli/util/path.rs | 47 +-------- cli/worker.rs | 11 --- runtime/Cargo.toml | 1 + runtime/code_cache.rs | 6 +- runtime/fs_util.rs | 56 +++++++++++ runtime/worker.rs | 94 +++++++++--------- tests/Cargo.toml | 1 - tests/integration/run_tests.rs | 113 +++++++++------------ tests/wpt/suite | 2 +- 26 files changed, 256 insertions(+), 389 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 668881e7ff36d..7135e04c5405e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -699,7 +699,6 @@ dependencies = [ "once_cell", "os_pipe", "pretty_assertions", - "regex", "serde", "serde_repr", "test_server", @@ -1836,6 +1835,7 @@ dependencies = [ "notify", "ntapi", "once_cell", + "percent-encoding", "regex", "ring", "rustyline", diff --git a/cli/cache/cache_db.rs b/cli/cache/cache_db.rs index e934b5eb93ae7..0613d52c66f44 100644 --- a/cli/cache/cache_db.rs +++ b/cli/cache/cache_db.rs @@ -303,7 +303,7 @@ impl CacheDB { /// Query a row from the database with a mapping function. pub fn query_row( &self, - sql: &str, + sql: &'static str, params: impl Params, f: F, ) -> Result, AnyError> diff --git a/cli/cache/code_cache.rs b/cli/cache/code_cache.rs index f678a42ed5908..5e44c366e7f5e 100644 --- a/cli/cache/code_cache.rs +++ b/cli/cache/code_cache.rs @@ -2,7 +2,6 @@ use deno_core::error::AnyError; use deno_runtime::code_cache; -use deno_runtime::deno_webstorage::rusqlite; use deno_runtime::deno_webstorage::rusqlite::params; use super::cache_db::CacheDB; @@ -13,8 +12,7 @@ pub static CODE_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration { table_initializer: "CREATE TABLE IF NOT EXISTS codecache ( specifier TEXT NOT NULL, type TEXT NOT NULL, - source_hash TEXT DEFAULT NULL, - source_timestamp INTEGER DEFAULT NULL, + source_hash TEXT NOT NULL, data BLOB NOT NULL, PRIMARY KEY (specifier, type) );", @@ -56,14 +54,12 @@ impl CodeCache { &self, specifier: &str, code_cache_type: code_cache::CodeCacheType, - source_hash: Option<&str>, - source_timestamp: Option, + source_hash: &str, ) -> Option> { Self::ensure_ok(self.inner.get_sync( specifier, code_cache_type, source_hash, - source_timestamp, )) } @@ -71,15 +67,13 @@ impl CodeCache { &self, specifier: &str, code_cache_type: code_cache::CodeCacheType, - source_hash: Option<&str>, - source_timestamp: Option, + source_hash: &str, data: &[u8], ) { Self::ensure_ok(self.inner.set_sync( specifier, code_cache_type, source_hash, - source_timestamp, data, )); } @@ -90,27 +84,19 @@ impl code_cache::CodeCache for CodeCache { &self, specifier: &str, code_cache_type: code_cache::CodeCacheType, - source_hash: Option<&str>, - source_timestamp: Option, + source_hash: &str, ) -> Option> { - self.get_sync(specifier, code_cache_type, source_hash, source_timestamp) + self.get_sync(specifier, code_cache_type, source_hash) } fn set_sync( &self, specifier: &str, code_cache_type: code_cache::CodeCacheType, - source_hash: Option<&str>, - source_timestamp: Option, + source_hash: &str, data: &[u8], ) { - self.set_sync( - specifier, - code_cache_type, - source_hash, - source_timestamp, - data, - ); + self.set_sync(specifier, code_cache_type, source_hash, data); } } @@ -128,66 +114,37 @@ impl CodeCacheInner { &self, specifier: &str, code_cache_type: code_cache::CodeCacheType, - source_hash: Option<&str>, - source_timestamp: Option, + source_hash: &str, ) -> Result>, AnyError> { - let mut query = " + let query = " SELECT data FROM codecache WHERE - specifier=?1 AND type=?2" - .to_string(); - let mut params: Vec = vec![ - specifier.to_string().into(), - code_cache_type.as_str().to_string().into(), - ]; - - let mut column_index = 3; - if let Some(source_hash) = source_hash { - query += &format!(" AND source_hash=?{}", column_index); - column_index += 1; - params.push(source_hash.to_string().into()); - } else { - query += " AND source_hash IS NULL"; - } - if let Some(source_timestamp) = source_timestamp { - query += &format!(" AND source_timestamp=?{}", column_index); - params.push(source_timestamp.to_string().into()); - } else { - query += " AND source_timestamp IS NULL"; - } - query += " LIMIT 1"; - - self - .conn - .query_row(&query, rusqlite::params_from_iter(params), |row| { - let value: Vec = row.get(0)?; - Ok(value) - }) + specifier=?1 AND type=?2 AND source_hash=?3 + LIMIT 1"; + let params = params![specifier, code_cache_type.as_str(), source_hash,]; + self.conn.query_row(query, params, |row| { + let value: Vec = row.get(0)?; + Ok(value) + }) } pub fn set_sync( &self, specifier: &str, code_cache_type: code_cache::CodeCacheType, - source_hash: Option<&str>, - source_timestamp: Option, + source_hash: &str, data: &[u8], ) -> Result<(), AnyError> { let sql = " INSERT OR REPLACE INTO - codecache (specifier, type, source_hash, source_timestamp, data) + codecache (specifier, type, source_hash, data) VALUES - (?1, ?2, ?3, ?4, ?5)"; - let params = params![ - specifier, - code_cache_type.as_str(), - source_hash, - source_timestamp, - data - ]; + (?1, ?2, ?3, ?4)"; + let params = + params![specifier, code_cache_type.as_str(), source_hash, data]; self.conn.execute(sql, params)?; Ok(()) } @@ -206,8 +163,7 @@ mod test { .get_sync( "file:///foo/bar.js", code_cache::CodeCacheType::EsModule, - Some("hash"), - Some(10), + "hash", ) .unwrap() .is_none()); @@ -216,8 +172,7 @@ mod test { .set_sync( "file:///foo/bar.js", code_cache::CodeCacheType::EsModule, - Some("hash"), - Some(10), + "hash", &data_esm, ) .unwrap(); @@ -226,57 +181,27 @@ mod test { .get_sync( "file:///foo/bar.js", code_cache::CodeCacheType::EsModule, - Some("hash"), - Some(10), + "hash", ) .unwrap() .unwrap(), data_esm ); - assert!(cache - .get_sync( - "file:///foo/bar.js", - code_cache::CodeCacheType::EsModule, - Some("hash"), - Some(20), - ) - .unwrap() - .is_none()); - assert!(cache - .get_sync( - "file:///foo/bar.js", - code_cache::CodeCacheType::EsModule, - Some("hash"), - None, - ) - .unwrap() - .is_none()); - assert!(cache - .get_sync( - "file:///foo/bar.js", - code_cache::CodeCacheType::EsModule, - None, - Some(10), - ) - .unwrap() - .is_none()); assert!(cache .get_sync( "file:///foo/bar.js", code_cache::CodeCacheType::Script, - Some("hash"), - Some(10), + "hash", ) .unwrap() .is_none()); - let data_script = vec![1, 2, 3]; + let data_script = vec![4, 5, 6]; cache .set_sync( "file:///foo/bar.js", code_cache::CodeCacheType::Script, - Some("hash"), - Some(10), + "hash", &data_script, ) .unwrap(); @@ -285,8 +210,7 @@ mod test { .get_sync( "file:///foo/bar.js", code_cache::CodeCacheType::Script, - Some("hash"), - Some(10), + "hash", ) .unwrap() .unwrap(), @@ -297,46 +221,7 @@ mod test { .get_sync( "file:///foo/bar.js", code_cache::CodeCacheType::EsModule, - Some("hash"), - Some(10), - ) - .unwrap() - .unwrap(), - data_esm - ); - } - - #[test] - pub fn time_stamp_only() { - let conn = CacheDB::in_memory(&CODE_CACHE_DB, "1.0.0"); - let cache = CodeCacheInner::new(conn); - - assert!(cache - .get_sync( - "file:///foo/bar.js", - code_cache::CodeCacheType::Script, - None, - Some(10), - ) - .unwrap() - .is_none()); - let data_esm = vec![1, 2, 3]; - cache - .set_sync( - "file:///foo/bar.js", - code_cache::CodeCacheType::Script, - None, - Some(10), - &data_esm, - ) - .unwrap(); - assert_eq!( - cache - .get_sync( - "file:///foo/bar.js", - code_cache::CodeCacheType::Script, - None, - Some(10), + "hash", ) .unwrap() .unwrap(), diff --git a/cli/cache/deno_dir.rs b/cli/cache/deno_dir.rs index 6b5f3aa460c69..b56dfbc8930df 100644 --- a/cli/cache/deno_dir.rs +++ b/cli/cache/deno_dir.rs @@ -145,7 +145,7 @@ impl DenoDir { /// Path for the V8 code cache. pub fn code_cache_db_file_path(&self) -> PathBuf { // bump this version name to invalidate the entire cache - self.root.join("code_cache_v1") + self.root.join("v8_code_cache_v1") } /// Path used for the REPL history file. diff --git a/cli/cache/module_info.rs b/cli/cache/module_info.rs index db975fb517aa4..2e9274160cc64 100644 --- a/cli/cache/module_info.rs +++ b/cli/cache/module_info.rs @@ -39,6 +39,7 @@ pub static MODULE_INFO_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration { on_failure: CacheFailure::InMemory, }; +#[derive(Debug)] pub struct ModuleInfoCacheSourceHash(String); impl ModuleInfoCacheSourceHash { @@ -55,6 +56,12 @@ impl ModuleInfoCacheSourceHash { } } +impl From for String { + fn from(source_hash: ModuleInfoCacheSourceHash) -> String { + source_hash.0 + } +} + /// A cache of `deno_graph::ModuleInfo` objects. Using this leads to a considerable /// performance improvement because when it exists we can skip parsing a module for /// deno_graph. diff --git a/cli/graph_util.rs b/cli/graph_util.rs index 6214a1628d825..32f31e426dc52 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -18,9 +18,9 @@ use crate::tools::check; use crate::tools::check::TypeChecker; use crate::util::file_watcher::WatcherCommunicator; use crate::util::fs::canonicalize_path; -use crate::util::path::specifier_to_file_path; use crate::util::sync::TaskQueue; use crate::util::sync::TaskQueuePermit; +use deno_runtime::fs_util::specifier_to_file_path; use deno_config::WorkspaceMemberConfig; use deno_core::anyhow::bail; diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index ce5d0c7f4d035..e990a3b4b6151 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -10,7 +10,7 @@ use crate::args::jsr_url; use crate::npm::CliNpmResolver; use crate::resolver::CliNodeResolver; use crate::tools::lint::create_linter; -use crate::util::path::specifier_to_file_path; +use deno_runtime::fs_util::specifier_to_file_path; use deno_ast::SourceRange; use deno_ast::SourceRangedForSpanned; diff --git a/cli/lsp/cache.rs b/cli/lsp/cache.rs index e0034207d3a8f..a1048dace7244 100644 --- a/cli/lsp/cache.rs +++ b/cli/lsp/cache.rs @@ -1,7 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use crate::cache::HttpCache; -use crate::util::path::specifier_to_file_path; +use deno_runtime::fs_util::specifier_to_file_path; use deno_core::parking_lot::Mutex; use deno_core::ModuleSpecifier; diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs index 164b3b8c3dd90..a4a7c81c16c8c 100644 --- a/cli/lsp/completions.rs +++ b/cli/lsp/completions.rs @@ -15,7 +15,7 @@ use super::tsc; use crate::jsr::JsrFetchResolver; use crate::util::path::is_importable_ext; use crate::util::path::relative_specifier; -use crate::util::path::specifier_to_file_path; +use deno_runtime::fs_util::specifier_to_file_path; use deno_ast::LineAndColumnIndex; use deno_ast::SourceTextInfo; diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 3e5460a1d3db1..15bd93ced20d2 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -10,7 +10,6 @@ use crate::lsp::logging::lsp_warn; use crate::tools::lint::get_configured_rules; use crate::tools::lint::ConfiguredRules; use crate::util::fs::canonicalize_path_maybe_not_exists; -use crate::util::path::specifier_to_file_path; use deno_ast::MediaType; use deno_config::FmtOptionsConfig; use deno_config::TsConfig; @@ -25,6 +24,7 @@ use deno_core::serde_json::Value; use deno_core::ModuleSpecifier; use deno_lockfile::Lockfile; use deno_runtime::deno_node::PackageJson; +use deno_runtime::fs_util::specifier_to_file_path; use deno_runtime::permissions::PermissionsContainer; use import_map::ImportMap; use lsp::Url; diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 8100b0732b827..1356bc7a44f81 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -19,7 +19,7 @@ use crate::resolver::CliNodeResolver; use crate::resolver::SloppyImportsFsEntry; use crate::resolver::SloppyImportsResolution; use crate::resolver::SloppyImportsResolver; -use crate::util::path::specifier_to_file_path; +use deno_runtime::fs_util::specifier_to_file_path; use dashmap::DashMap; use deno_ast::MediaType; diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 342c32358d264..fcabadbed3c28 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -120,10 +120,10 @@ use crate::tools::upgrade::check_for_upgrades_for_lsp; use crate::tools::upgrade::upgrade_check_enabled; use crate::util::fs::remove_dir_all_if_exists; use crate::util::path::is_importable_ext; -use crate::util::path::specifier_to_file_path; use crate::util::path::to_percent_decoded_str; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; +use deno_runtime::fs_util::specifier_to_file_path; struct LspRootCertStoreProvider(RootCertStore); diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index ac4871dfcd237..09c1eb563c10c 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -30,8 +30,8 @@ use crate::tsc; use crate::tsc::ResolveArgs; use crate::tsc::MISSING_DEPENDENCY_SPECIFIER; use crate::util::path::relative_specifier; -use crate::util::path::specifier_to_file_path; use crate::util::path::to_percent_decoded_str; +use deno_runtime::fs_util::specifier_to_file_path; use dashmap::DashMap; use deno_ast::MediaType; diff --git a/cli/module_loader.rs b/cli/module_loader.rs index 18ba74b849218..a6c8d133810d0 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -19,7 +19,6 @@ use crate::resolver::ModuleCodeStringSource; use crate::resolver::NpmModuleLoader; use crate::tools::check; use crate::tools::check::TypeChecker; -use crate::util::path::specifier_to_file_path; use crate::util::progress_bar::ProgressBar; use crate::util::text_encoding::code_without_source_map; use crate::util::text_encoding::source_map_from_code; @@ -55,6 +54,7 @@ use deno_graph::Resolution; use deno_lockfile::Lockfile; use deno_runtime::code_cache; use deno_runtime::deno_node::NodeResolutionMode; +use deno_runtime::fs_util::code_timestamp; use deno_runtime::permissions::PermissionsContainer; use deno_semver::npm::NpmPackageReqReference; use deno_terminal::colors; @@ -63,7 +63,6 @@ use std::pin::Pin; use std::rc::Rc; use std::str; use std::sync::Arc; -use std::time::UNIX_EPOCH; pub struct ModuleLoadPreparer { options: Arc, @@ -472,36 +471,26 @@ impl CliModuleLoader { let code_cache = if module_type == ModuleType::JavaScript { self.shared.code_cache.as_ref().and_then(|cache| { let code_hash = self - .shared - .module_info_cache - .get_module_source_hash(specifier, code_source.media_type) + .get_code_hash_or_timestamp(specifier, code_source.media_type) .ok() .flatten(); - let code_timestamp = specifier_to_file_path(specifier) - .ok() - .and_then(|path| std::fs::metadata(path).ok()) - .and_then(|m| m.modified().ok()) - .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) - .map(|d| d.as_millis() as u64); - cache - .get_sync( - specifier.as_str(), - code_cache::CodeCacheType::EsModule, - code_hash.as_ref().map(|hash| hash.as_str()), - code_timestamp, - ) - .map(Cow::from) - .inspect(|_| { - log::debug!( - "V8 code cache hit for ES module: {}, [{},{}]", - specifier.to_string(), - code_hash - .as_ref() - .map(|hash| hash.as_str()) - .unwrap_or("none"), - code_timestamp.unwrap_or(0), - ); - }) + if let Some(code_hash) = code_hash { + cache + .get_sync( + specifier.as_str(), + code_cache::CodeCacheType::EsModule, + &code_hash, + ) + .map(Cow::from) + .inspect(|_| { + // This log line is also used by tests. + log::debug!( + "V8 code cache hit for ES module: {specifier}, [{code_hash:?}]" + ); + }) + } else { + None + } }) } else { None @@ -652,6 +641,25 @@ impl CliModuleLoader { resolution.map_err(|err| err.into()) } + + fn get_code_hash_or_timestamp( + &self, + specifier: &ModuleSpecifier, + media_type: MediaType, + ) -> Result, AnyError> { + let hash = self + .shared + .module_info_cache + .get_module_source_hash(specifier, media_type)?; + if let Some(hash) = hash { + return Ok(Some(hash.into())); + } + + // Use the modified timestamp from the local file system if we don't have a hash. + let timestamp = code_timestamp(specifier.as_str()) + .map(|timestamp| timestamp.to_string())?; + Ok(Some(timestamp)) + } } impl ModuleLoader for CliModuleLoader { @@ -736,33 +744,21 @@ impl ModuleLoader for CliModuleLoader { if let Some(cache) = self.shared.code_cache.as_ref() { let media_type = MediaType::from_specifier(specifier); let code_hash = self - .shared - .module_info_cache - .get_module_source_hash(specifier, media_type) + .get_code_hash_or_timestamp(specifier, media_type) .ok() .flatten(); - let code_timestamp = specifier_to_file_path(specifier) - .ok() - .and_then(|path| std::fs::metadata(path).ok()) - .and_then(|m| m.modified().ok()) - .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) - .map(|d| d.as_millis() as u64); - log::debug!( - "Updating V8 code cache for ES module: {}, [{},{}]", - specifier, - code_hash - .as_ref() - .map(|hash| hash.as_str()) - .unwrap_or("none"), - code_timestamp.unwrap_or(0) - ); - cache.set_sync( - specifier.as_str(), - code_cache::CodeCacheType::EsModule, - code_hash.as_ref().map(|hash| hash.as_str()), - code_timestamp, - code_cache, - ); + if let Some(code_hash) = code_hash { + // This log line is also used by tests. + log::debug!( + "Updating V8 code cache for ES module: {specifier}, [{code_hash:?}]" + ); + cache.set_sync( + specifier.as_str(), + code_cache::CodeCacheType::EsModule, + &code_hash, + code_cache, + ); + } } async {}.boxed_local() } diff --git a/cli/npm/byonm.rs b/cli/npm/byonm.rs index 1e61ce885ea74..9317455378f9f 100644 --- a/cli/npm/byonm.rs +++ b/cli/npm/byonm.rs @@ -21,7 +21,7 @@ use crate::args::package_json::get_local_package_json_version_reqs; use crate::args::NpmProcessState; use crate::args::NpmProcessStateKind; use crate::util::fs::canonicalize_path_maybe_not_exists_with_fs; -use crate::util::path::specifier_to_file_path; +use deno_runtime::fs_util::specifier_to_file_path; use super::common::types_package_name; use super::CliNpmResolver; diff --git a/cli/resolver.rs b/cli/resolver.rs index fc326e1b192e3..aebfcb06f0f54 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -27,6 +27,7 @@ use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::NpmResolver as DenoNodeNpmResolver; use deno_runtime::deno_node::PackageJson; +use deno_runtime::fs_util::specifier_to_file_path; use deno_runtime::permissions::PermissionsContainer; use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageReq; @@ -48,7 +49,6 @@ use crate::node::CliNodeCodeTranslator; use crate::npm::ByonmCliNpmResolver; use crate::npm::CliNpmResolver; use crate::npm::InnerCliNpmResolverRef; -use crate::util::path::specifier_to_file_path; use crate::util::sync::AtomicFlag; pub fn format_range_with_colors(range: &deno_graph::Range) -> String { diff --git a/cli/tools/vendor/mod.rs b/cli/tools/vendor/mod.rs index 5a76365eed183..2abdf6e99cd7a 100644 --- a/cli/tools/vendor/mod.rs +++ b/cli/tools/vendor/mod.rs @@ -24,7 +24,7 @@ use crate::tools::fmt::format_json; use crate::util::fs::canonicalize_path; use crate::util::fs::resolve_from_cwd; use crate::util::path::relative_specifier; -use crate::util::path::specifier_to_file_path; +use deno_runtime::fs_util::specifier_to_file_path; mod analyze; mod build; diff --git a/cli/util/path.rs b/cli/util/path.rs index 144676c015ef1..6a083a62dd009 100644 --- a/cli/util/path.rs +++ b/cli/util/path.rs @@ -9,8 +9,6 @@ use deno_ast::ModuleSpecifier; use deno_config::glob::PathGlobMatch; use deno_config::glob::PathOrPattern; use deno_config::glob::PathOrPatternSet; -use deno_core::error::uri_error; -use deno_core::error::AnyError; /// Checks if the path has an extension Deno supports for script execution. pub fn is_script_ext(path: &Path) -> bool { @@ -82,49 +80,6 @@ pub fn mapped_specifier_for_tsc( } } -/// Attempts to convert a specifier to a file path. By default, uses the Url -/// crate's `to_file_path()` method, but falls back to try and resolve unix-style -/// paths on Windows. -pub fn specifier_to_file_path( - specifier: &ModuleSpecifier, -) -> Result { - let result = if specifier.scheme() != "file" { - Err(()) - } else if cfg!(windows) { - match specifier.to_file_path() { - Ok(path) => Ok(path), - Err(()) => { - // This might be a unix-style path which is used in the tests even on Windows. - // Attempt to see if we can convert it to a `PathBuf`. This code should be removed - // once/if https://github.com/servo/rust-url/issues/730 is implemented. - if specifier.scheme() == "file" - && specifier.host().is_none() - && specifier.port().is_none() - && specifier.path_segments().is_some() - { - let path_str = specifier.path(); - match String::from_utf8( - percent_encoding::percent_decode(path_str.as_bytes()).collect(), - ) { - Ok(path_str) => Ok(PathBuf::from(path_str)), - Err(_) => Err(()), - } - } else { - Err(()) - } - } - } - } else { - specifier.to_file_path() - }; - match result { - Ok(path) => Ok(path), - Err(()) => Err(uri_error(format!( - "Invalid file path.\n Specifier: {specifier}" - ))), - } -} - /// `from.make_relative(to)` but with fixes. pub fn relative_specifier( from: &ModuleSpecifier, @@ -286,6 +241,8 @@ pub fn to_percent_decoded_str(s: &str) -> String { #[cfg(test)] mod test { + use deno_runtime::fs_util::specifier_to_file_path; + use super::*; #[test] diff --git a/cli/worker.rs b/cli/worker.rs index 374063fbfdf15..468fa17a19b76 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -4,7 +4,6 @@ use std::path::Path; use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; -use std::time::UNIX_EPOCH; use deno_ast::ModuleSpecifier; use deno_core::anyhow::bail; @@ -56,7 +55,6 @@ use crate::npm::CliNpmResolver; use crate::util::checksum; use crate::util::file_watcher::WatcherCommunicator; use crate::util::file_watcher::WatcherRestartMode; -use crate::util::path::specifier_to_file_path; use crate::version; pub trait ModuleLoaderFactory: Send + Sync { @@ -643,15 +641,6 @@ impl CliMainWorkerFactory { feature_checker, skip_op_registration: shared.options.skip_op_registration, v8_code_cache: shared.code_cache.clone(), - modified_timestamp_getter: Some(Arc::new(|specifier| { - ModuleSpecifier::parse(specifier) - .ok() - .and_then(|specifier| specifier_to_file_path(&specifier).ok()) - .and_then(|path| std::fs::metadata(path).ok()) - .and_then(|m| m.modified().ok()) - .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) - .map(|d| d.as_millis() as u64) - })), }; let mut worker = MainWorker::bootstrap_from_options( diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index dc6b9f61e7305..c624b0e7ccb17 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -112,6 +112,7 @@ log.workspace = true netif = "0.1.6" notify.workspace = true once_cell.workspace = true +percent-encoding.workspace = true regex.workspace = true ring.workspace = true rustyline = { workspace = true, features = ["custom-bindings"] } diff --git a/runtime/code_cache.rs b/runtime/code_cache.rs index ff651dc5ee0ef..ccc070365bdfe 100644 --- a/runtime/code_cache.rs +++ b/runtime/code_cache.rs @@ -19,15 +19,13 @@ pub trait CodeCache: Send + Sync { &self, specifier: &str, code_cache_type: CodeCacheType, - source_hash: Option<&str>, - source_timestamp: Option, + source_hash: &str, ) -> Option>; fn set_sync( &self, specifier: &str, code_cache_type: CodeCacheType, - source_hash: Option<&str>, - source_timestamp: Option, + source_hash: &str, data: &[u8], ); } diff --git a/runtime/fs_util.rs b/runtime/fs_util.rs index f7c006a919ead..7437cb8e8fd4f 100644 --- a/runtime/fs_util.rs +++ b/runtime/fs_util.rs @@ -1,6 +1,8 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use deno_ast::ModuleSpecifier; use deno_core::anyhow::Context; +use deno_core::error::uri_error; use deno_core::error::AnyError; pub use deno_core::normalize_path; use std::path::Path; @@ -18,6 +20,60 @@ pub fn resolve_from_cwd(path: &Path) -> Result { } } +/// Attempts to convert a specifier to a file path. By default, uses the Url +/// crate's `to_file_path()` method, but falls back to try and resolve unix-style +/// paths on Windows. +pub fn specifier_to_file_path( + specifier: &ModuleSpecifier, +) -> Result { + let result = if specifier.scheme() != "file" { + Err(()) + } else if cfg!(windows) { + match specifier.to_file_path() { + Ok(path) => Ok(path), + Err(()) => { + // This might be a unix-style path which is used in the tests even on Windows. + // Attempt to see if we can convert it to a `PathBuf`. This code should be removed + // once/if https://github.com/servo/rust-url/issues/730 is implemented. + if specifier.scheme() == "file" + && specifier.host().is_none() + && specifier.port().is_none() + && specifier.path_segments().is_some() + { + let path_str = specifier.path(); + match String::from_utf8( + percent_encoding::percent_decode(path_str.as_bytes()).collect(), + ) { + Ok(path_str) => Ok(PathBuf::from(path_str)), + Err(_) => Err(()), + } + } else { + Err(()) + } + } + } + } else { + specifier.to_file_path() + }; + match result { + Ok(path) => Ok(path), + Err(()) => Err(uri_error(format!( + "Invalid file path.\n Specifier: {specifier}" + ))), + } +} + +pub fn code_timestamp(specifier: &str) -> Result { + let specifier = ModuleSpecifier::parse(specifier)?; + let path = specifier_to_file_path(&specifier)?; + #[allow(clippy::disallowed_methods)] + let timestamp = std::fs::metadata(path)? + .modified()? + .duration_since(std::time::UNIX_EPOCH)? + .as_millis() as u64; + Ok(timestamp) +} + #[cfg(test)] mod tests { use super::*; diff --git a/runtime/worker.rs b/runtime/worker.rs index 21ef2f9d16152..29d7a8b360662 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -45,6 +45,7 @@ use log::debug; use crate::code_cache::CodeCache; use crate::code_cache::CodeCacheType; +use crate::fs_util::code_timestamp; use crate::inspector_server::InspectorServer; use crate::ops; use crate::permissions::PermissionsContainer; @@ -54,8 +55,6 @@ use crate::BootstrapOptions; pub type FormatJsErrorFn = dyn Fn(&JsError) -> String + Sync + Send; -pub type GetModifiedFileTimeFn = dyn Fn(&str) -> Option; - pub fn import_meta_resolve_callback( loader: &dyn deno_core::ModuleLoader, specifier: String, @@ -197,10 +196,6 @@ pub struct WorkerOptions { /// V8 code cache for module and script source code. pub v8_code_cache: Option>, - - /// Callback for getting the modified timestamp for a module specifier. - /// Only works with local file path specifiers. - pub modified_timestamp_getter: Option>, } impl Default for WorkerOptions { @@ -236,7 +231,6 @@ impl Default for WorkerOptions { stdio: Default::default(), feature_checker: Default::default(), v8_code_cache: Default::default(), - modified_timestamp_getter: Default::default(), } } } @@ -306,6 +300,51 @@ pub fn create_op_metrics( (op_summary_metrics, op_metrics_factory_fn) } +fn get_code_cache( + code_cache: Arc, + specifier: &str, +) -> Option> { + // Code hashes are not maintained for op_eval_context scripts. Instead we use + // the modified timestamp from the local file system. + if let Ok(code_timestamp) = code_timestamp(specifier) { + code_cache + .get_sync( + specifier, + CodeCacheType::Script, + code_timestamp.to_string().as_str(), + ) + .inspect(|_| { + // This log line is also used by tests. + log::debug!( + "V8 code cache hit for script: {specifier}, [{code_timestamp}]" + ); + }) + } else { + None + } +} + +fn set_code_cache( + code_cache: Arc, + specifier: &str, + data: &[u8], +) { + // Code hashes are not maintained for op_eval_context scripts. Instead we use + // the modified timestamp from the local file system. + if let Ok(code_timestamp) = code_timestamp(specifier) { + // This log line is also used by tests. + log::debug!( + "Updating V8 code cache for script: {specifier}, [{code_timestamp}]", + ); + code_cache.set_sync( + specifier, + CodeCacheType::Script, + code_timestamp.to_string().as_str(), + data, + ); + } +} + impl MainWorker { pub fn bootstrap_from_options( main_module: ModuleSpecifier, @@ -508,49 +547,12 @@ impl MainWorker { enable_code_cache: options.v8_code_cache.is_some(), eval_context_code_cache_cbs: options.v8_code_cache.map(|cache| { let cache_clone = cache.clone(); - let modified_timestamp_getter = - options.modified_timestamp_getter.clone(); - let modified_timestamp_getter_clone = - options.modified_timestamp_getter.clone(); ( Box::new(move |specifier: &str| { - let code_timestamp = modified_timestamp_getter - .as_ref() - .and_then(|getter| getter(specifier)); - Ok( - cache - .get_sync( - specifier, - CodeCacheType::Script, - None, - code_timestamp, - ) - .map(Cow::from) - .inspect(|_| { - log::debug!( - "V8 code cache hit for script: {}, [{}]", - specifier.to_string(), - code_timestamp.unwrap_or(0), - ); - }), - ) + Ok(get_code_cache(cache.clone(), specifier).map(Cow::Owned)) }) as Box _>, Box::new(move |specifier: &str, data: &[u8]| { - let code_timestamp = modified_timestamp_getter_clone - .as_ref() - .and_then(|getter| getter(specifier)); - log::debug!( - "Updating code cache for script: {}, [{}]", - specifier, - code_timestamp.unwrap_or(0) - ); - cache_clone.set_sync( - specifier, - CodeCacheType::Script, - None, - code_timestamp, - data, - ); + set_code_cache(cache_clone.clone(), specifier, data); }) as Box, ) }), diff --git a/tests/Cargo.toml b/tests/Cargo.toml index d6f9cdb7ab8a0..b0f9ff0af1bbf 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -52,7 +52,6 @@ hyper-util.workspace = true once_cell.workspace = true os_pipe.workspace = true pretty_assertions.workspace = true -regex = "1.1.0" serde.workspace = true serde_repr.workspace = true test_util.workspace = true diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs index e98b01dcb491f..761414d6cc147 100644 --- a/tests/integration/run_tests.rs +++ b/tests/integration/run_tests.rs @@ -5,7 +5,6 @@ use deno_core::serde_json::json; use deno_core::url; use deno_fetch::reqwest; use pretty_assertions::assert_eq; -use regex::Regex; use std::io::Read; use std::io::Write; use std::process::Command; @@ -5181,104 +5180,82 @@ fn run_etag_delete_source_cache() { #[test] fn code_cache_test() { - let script_dir = TempDir::new(); - script_dir.write("main.js", "console.log('Hello World - A');"); - let prg = util::deno_exe_path(); let deno_dir = TempDir::new(); + let test_context = TestContextBuilder::new().use_temp_cwd().build(); + let temp_dir = test_context.temp_dir(); + temp_dir.write("main.js", "console.log('Hello World - A');"); // First run with no prior cache. { - let output = Command::new(prg.clone()) + let output = test_context + .new_command() .env("DENO_DIR", deno_dir.path()) - .current_dir(util::testdata_path()) .arg("run") .arg("-Ldebug") - .arg(format!("{}/main.js", script_dir.path())) - .output() - .expect("Failed to spawn script"); - - let str_output = std::str::from_utf8(&output.stdout).unwrap(); - assert!(str_output.contains("Hello World - A")); - - let debug_output = std::str::from_utf8(&output.stderr).unwrap(); - // There should be no cache hits yet, and the cache should be created. - assert!(!debug_output.contains("V8 code cache hit")); - assert!(Regex::new( - r"Updating V8 code cache for ES module: file://(.+)/main.js" - ) - .unwrap() - .is_match(debug_output)); + .arg("main.js") + .split_output() + .run(); + + output + .assert_stdout_matches_text("Hello World - A[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/main.js[WILDCARD]"); + assert!(!output.stderr().contains("V8 code cache hit")); // Check that the code cache database exists. - let code_cache_path = deno_dir.path().join("code_cache_v1"); + let code_cache_path = deno_dir.path().join("v8_code_cache_v1"); assert!(code_cache_path.exists()); } // 2nd run with cache. { - let output = Command::new(prg.clone()) + let output = test_context + .new_command() .env("DENO_DIR", deno_dir.path()) - .current_dir(util::testdata_path()) .arg("run") .arg("-Ldebug") - .arg(format!("{}/main.js", script_dir.path())) - .output() - .expect("Failed to spawn script"); - - let str_output = std::str::from_utf8(&output.stdout).unwrap(); - assert!(str_output.contains("Hello World - A")); - - let debug_output = std::str::from_utf8(&output.stderr).unwrap(); - // There should be a cache hit, and the cache should not be created. - assert!(Regex::new( - r"V8 code cache hit for ES module: file://(.+)/main.js" - ) - .unwrap() - .is_match(debug_output)); - assert!(!debug_output.contains("Updating V8 code cache")); + .arg("main.js") + .split_output() + .run(); + + output + .assert_stdout_matches_text("Hello World - A[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/main.js[WILDCARD]"); + assert!(!output.stderr().contains("Updating V8 code cache")); } // Rerun with --no-code-cache. { - let output = Command::new(prg.clone()) + let output = test_context + .new_command() .env("DENO_DIR", deno_dir.path()) - .current_dir(util::testdata_path()) .arg("run") .arg("-Ldebug") .arg("--no-code-cache") - .arg(format!("{}/main.js", script_dir.path())) - .output() - .expect("Failed to spawn script"); - - let str_output = std::str::from_utf8(&output.stdout).unwrap(); - assert!(str_output.contains("Hello World - A")); - - let debug_output = std::str::from_utf8(&output.stderr).unwrap(); - // There should be no cache used. - assert!(!debug_output.contains("V8 code cache")); + .arg("main.js") + .split_output() + .run(); + + output + .assert_stdout_matches_text("Hello World - A[WILDCARD]") + .skip_stderr_check(); + assert!(!output.stderr().contains("V8 code cache")); } // Modify the script, and make sure that the cache is rejected. - script_dir.write("main.js", "console.log('Hello World - B');"); + temp_dir.write("main.js", "console.log('Hello World - B');"); { - let output = Command::new(prg.clone()) + let output = test_context + .new_command() .env("DENO_DIR", deno_dir.path()) - .current_dir(util::testdata_path()) .arg("run") .arg("-Ldebug") - .arg(format!("{}/main.js", script_dir.path())) - .output() - .expect("Failed to spawn script"); - - let str_output = std::str::from_utf8(&output.stdout).unwrap(); - assert!(str_output.contains("Hello World - B")); - - let debug_output = std::str::from_utf8(&output.stderr).unwrap(); - assert!(!debug_output.contains("V8 code cache hit")); - assert!(Regex::new( - r"Updating V8 code cache for ES module: file://(.+)/main.js" - ) - .unwrap() - .is_match(debug_output)); + .arg("main.js") + .split_output() + .run(); + + output + .assert_stdout_matches_text("Hello World - B[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/main.js[WILDCARD]"); + assert!(!output.stderr().contains("V8 code cache hit")); } } diff --git a/tests/wpt/suite b/tests/wpt/suite index acabb88c58045..fb81ba9b33964 160000 --- a/tests/wpt/suite +++ b/tests/wpt/suite @@ -1 +1 @@ -Subproject commit acabb88c580459224955c8c0fa0bddd6fd701f30 +Subproject commit fb81ba9b33964b3177b4f6c1715d5f46c325b443 From 28e1fdda93a68d6ec1fe6673012149ca05c5928f Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Tue, 16 Apr 2024 16:16:18 -0700 Subject: [PATCH 14/18] fix wpt --- tests/wpt/suite | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/wpt/suite b/tests/wpt/suite index fb81ba9b33964..acabb88c58045 160000 --- a/tests/wpt/suite +++ b/tests/wpt/suite @@ -1 +1 @@ -Subproject commit fb81ba9b33964b3177b4f6c1715d5f46c325b443 +Subproject commit acabb88c580459224955c8c0fa0bddd6fd701f30 From 9086f0b3a5a8c9bccced7e0e2f16d42cebb785d5 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Tue, 16 Apr 2024 16:22:29 -0700 Subject: [PATCH 15/18] move test --- cli/util/path.rs | 20 -------------------- runtime/fs_util.rs | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/cli/util/path.rs b/cli/util/path.rs index 6a083a62dd009..a3109ad04ad9a 100644 --- a/cli/util/path.rs +++ b/cli/util/path.rs @@ -241,8 +241,6 @@ pub fn to_percent_decoded_str(s: &str) -> String { #[cfg(test)] mod test { - use deno_runtime::fs_util::specifier_to_file_path; - use super::*; #[test] @@ -287,24 +285,6 @@ mod test { assert!(!is_importable_ext(Path::new("foo.mjsx"))); } - #[test] - fn test_specifier_to_file_path() { - run_success_test("file:///", "/"); - run_success_test("file:///test", "/test"); - run_success_test("file:///dir/test/test.txt", "/dir/test/test.txt"); - run_success_test( - "file:///dir/test%20test/test.txt", - "/dir/test test/test.txt", - ); - - fn run_success_test(specifier: &str, expected_path: &str) { - let result = - specifier_to_file_path(&ModuleSpecifier::parse(specifier).unwrap()) - .unwrap(); - assert_eq!(result, PathBuf::from(expected_path)); - } - } - #[test] fn test_relative_specifier() { let fixtures: Vec<(&str, &str, Option<&str>)> = vec![ diff --git a/runtime/fs_util.rs b/runtime/fs_util.rs index 7437cb8e8fd4f..09b1073003db2 100644 --- a/runtime/fs_util.rs +++ b/runtime/fs_util.rs @@ -125,4 +125,22 @@ mod tests { let absolute_expected = cwd.join(expected); assert_eq!(resolve_from_cwd(expected).unwrap(), absolute_expected); } + + #[test] + fn test_specifier_to_file_path() { + run_success_test("file:///", "/"); + run_success_test("file:///test", "/test"); + run_success_test("file:///dir/test/test.txt", "/dir/test/test.txt"); + run_success_test( + "file:///dir/test%20test/test.txt", + "/dir/test test/test.txt", + ); + + fn run_success_test(specifier: &str, expected_path: &str) { + let result = + specifier_to_file_path(&ModuleSpecifier::parse(specifier).unwrap()) + .unwrap(); + assert_eq!(result, PathBuf::from(expected_path)); + } + } } From 2ae474505125efb9b49e9f9c087171e4ccb186d5 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Tue, 16 Apr 2024 23:16:29 -0700 Subject: [PATCH 16/18] add npm tests --- tests/integration/run_tests.rs | 120 +++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs index 761414d6cc147..e477a98d664e5 100644 --- a/tests/integration/run_tests.rs +++ b/tests/integration/run_tests.rs @@ -5259,3 +5259,123 @@ fn code_cache_test() { assert!(!output.stderr().contains("V8 code cache hit")); } } + +#[test] +fn code_cache_npm_test() { + let deno_dir = TempDir::new(); + let test_context = TestContextBuilder::new() + .use_temp_cwd() + .use_http_server() + .build(); + let temp_dir = test_context.temp_dir(); + temp_dir.write( + "main.js", + "import chalk from \"npm:chalk@5\";console.log(chalk('Hello World'));", + ); + + // First run with no prior cache. + { + let output = test_context + .new_command() + .env("DENO_DIR", deno_dir.path()) + .envs(env_vars_for_npm_tests()) + .arg("run") + .arg("-Ldebug") + .arg("-A") + .arg("main.js") + .split_output() + .run(); + + output + .assert_stdout_matches_text("Hello World[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/main.js[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/npm/registry/chalk/5.[WILDCARD]/source/index.js[WILDCARD]"); + assert!(!output.stderr().contains("V8 code cache hit")); + + // Check that the code cache database exists. + let code_cache_path = deno_dir.path().join("v8_code_cache_v1"); + assert!(code_cache_path.exists()); + } + + // 2nd run with cache. + { + let output = test_context + .new_command() + .env("DENO_DIR", deno_dir.path()) + .envs(env_vars_for_npm_tests()) + .arg("run") + .arg("-Ldebug") + .arg("-A") + .arg("main.js") + .split_output() + .run(); + + output + .assert_stdout_matches_text("Hello World[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/main.js[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/npm/registry/chalk/5.[WILDCARD]/source/index.js[WILDCARD]"); + assert!(!output.stderr().contains("Updating V8 code cache")); + } +} + +#[test] +fn code_cache_npm_with_require_test() { + let deno_dir = TempDir::new(); + let test_context = TestContextBuilder::new() + .use_temp_cwd() + .use_http_server() + .build(); + let temp_dir = test_context.temp_dir(); + temp_dir.write( + "main.js", + "import fraction from \"npm:autoprefixer\";console.log(typeof fraction);", + ); + + // First run with no prior cache. + { + let output = test_context + .new_command() + .env("DENO_DIR", deno_dir.path()) + .envs(env_vars_for_npm_tests()) + .arg("run") + .arg("-Ldebug") + .arg("-A") + .arg("main.js") + .split_output() + .run(); + + output + .assert_stdout_matches_text("function[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/main.js[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for ES module: file:///[WILDCARD]/npm/registry/autoprefixer/[WILDCARD]/autoprefixer.js[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for script: file:///[WILDCARD]/npm/registry/autoprefixer/[WILDCARD]/autoprefixer.js[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]Updating V8 code cache for script: file:///[WILDCARD]/npm/registry/browserslist/[WILDCARD]/index.js[WILDCARD]"); + assert!(!output.stderr().contains("V8 code cache hit")); + + // Check that the code cache database exists. + let code_cache_path = deno_dir.path().join("v8_code_cache_v1"); + assert!(code_cache_path.exists()); + } + + // 2nd run with cache. + { + let output = test_context + .new_command() + .env("DENO_DIR", deno_dir.path()) + .envs(env_vars_for_npm_tests()) + .arg("run") + .arg("-Ldebug") + .arg("-A") + .arg("main.js") + .split_output() + .run(); + + output + .assert_stdout_matches_text("function[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/main.js[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for ES module: file:///[WILDCARD]/npm/registry/autoprefixer/[WILDCARD]/autoprefixer.js[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for script: file:///[WILDCARD]/npm/registry/autoprefixer/[WILDCARD]/autoprefixer.js[WILDCARD]") + .assert_stderr_matches_text("[WILDCARD]V8 code cache hit for script: file:///[WILDCARD]/npm/registry/browserslist/[WILDCARD]/index.js[WILDCARD]"); + assert!(!output.stderr().contains("Updating V8 code cache")); + } +} From cbb223759f7ce172d2760a440d6381919e6ab225 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Tue, 16 Apr 2024 23:18:14 -0700 Subject: [PATCH 17/18] revert test timeout --- tests/testdata/run/rejection_handled.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata/run/rejection_handled.ts b/tests/testdata/run/rejection_handled.ts index c29ae7089d491..f058ff966514d 100644 --- a/tests/testdata/run/rejection_handled.ts +++ b/tests/testdata/run/rejection_handled.ts @@ -14,4 +14,4 @@ setTimeout(async () => { setTimeout(() => { console.log("Success"); -}, 200); +}, 50); From ae14b42a9eb14d566d43418be0e43ff6d7856ea0 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Tue, 16 Apr 2024 23:48:33 -0700 Subject: [PATCH 18/18] bring back larger test timeout --- tests/testdata/run/rejection_handled.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata/run/rejection_handled.ts b/tests/testdata/run/rejection_handled.ts index f058ff966514d..c29ae7089d491 100644 --- a/tests/testdata/run/rejection_handled.ts +++ b/tests/testdata/run/rejection_handled.ts @@ -14,4 +14,4 @@ setTimeout(async () => { setTimeout(() => { console.log("Success"); -}, 50); +}, 200);