// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::path::Path; use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; use deno_ast::ModuleSpecifier; use deno_core::anyhow::bail; use deno_core::error::AnyError; use deno_core::futures::FutureExt; use deno_core::url::Url; use deno_core::v8; use deno_core::CompiledWasmModuleStore; use deno_core::Extension; use deno_core::FeatureChecker; use deno_core::ModuleLoader; use deno_core::PollEventLoopOptions; use deno_core::SharedArrayBufferStore; use deno_runtime::code_cache; use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel; use deno_runtime::deno_fs; use deno_runtime::deno_node::NodeExtInitServices; use deno_runtime::deno_node::NodeRequireLoader; use deno_runtime::deno_node::NodeRequireLoaderRc; use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_node::PackageJsonResolver; use deno_runtime::deno_permissions::PermissionsContainer; use deno_runtime::deno_tls::RootCertStoreProvider; use deno_runtime::deno_web::BlobStore; use deno_runtime::fmt_errors::format_js_error; use deno_runtime::inspector_server::InspectorServer; use deno_runtime::ops::otel::OtelConfig; use deno_runtime::ops::process::NpmProcessStateProviderRc; use deno_runtime::ops::worker_host::CreateWebWorkerCb; use deno_runtime::web_worker::WebWorker; use deno_runtime::web_worker::WebWorkerOptions; use deno_runtime::web_worker::WebWorkerServiceOptions; use deno_runtime::worker::MainWorker; use deno_runtime::worker::WorkerOptions; use deno_runtime::worker::WorkerServiceOptions; use deno_runtime::BootstrapOptions; use deno_runtime::WorkerExecutionMode; use deno_runtime::WorkerLogLevel; use deno_semver::npm::NpmPackageReqReference; use deno_terminal::colors; use node_resolver::NodeModuleKind; use node_resolver::NodeResolutionMode; use tokio::select; use crate::args::CliLockfile; use crate::args::DenoSubcommand; use crate::args::StorageKeyResolver; use crate::errors; use crate::npm::CliNpmResolver; use crate::util::checksum; use crate::util::file_watcher::WatcherCommunicator; use crate::util::file_watcher::WatcherRestartMode; use crate::version; pub struct CreateModuleLoaderResult { pub module_loader: Rc, pub node_require_loader: Rc, } pub trait ModuleLoaderFactory: Send + Sync { fn create_for_main( &self, root_permissions: PermissionsContainer, ) -> CreateModuleLoaderResult; fn create_for_worker( &self, parent_permissions: PermissionsContainer, permissions: PermissionsContainer, ) -> CreateModuleLoaderResult; } #[async_trait::async_trait(?Send)] pub trait HmrRunner: Send + Sync { async fn start(&mut self) -> Result<(), AnyError>; async fn stop(&mut self) -> Result<(), AnyError>; async fn run(&mut self) -> Result<(), AnyError>; } pub trait CliCodeCache: code_cache::CodeCache { /// Gets if the code cache is still enabled. fn enabled(&self) -> bool { true } fn as_code_cache(self: Arc) -> Arc; } #[async_trait::async_trait(?Send)] pub trait CoverageCollector: Send + Sync { async fn start_collecting(&mut self) -> Result<(), AnyError>; async fn stop_collecting(&mut self) -> Result<(), AnyError>; } pub type CreateHmrRunnerCb = Box< dyn Fn(deno_core::LocalInspectorSession) -> Box + Send + Sync, >; pub type CreateCoverageCollectorCb = Box< dyn Fn(deno_core::LocalInspectorSession) -> Box + Send + Sync, >; pub struct CliMainWorkerOptions { pub argv: Vec, pub log_level: WorkerLogLevel, pub enable_op_summary_metrics: bool, pub enable_testing_features: bool, pub has_node_modules_dir: bool, pub hmr: bool, pub inspect_brk: bool, pub inspect_wait: bool, pub strace_ops: Option>, pub is_inspecting: bool, pub location: Option, pub argv0: Option, pub node_debug: Option, pub origin_data_folder_path: Option, pub seed: Option, pub unsafely_ignore_certificate_errors: Option>, pub skip_op_registration: bool, pub create_hmr_runner: Option, pub create_coverage_collector: Option, pub node_ipc: Option, pub serve_port: Option, pub serve_host: Option, } struct SharedWorkerState { blob_store: Arc, broadcast_channel: InMemoryBroadcastChannel, code_cache: Option>, compiled_wasm_module_store: CompiledWasmModuleStore, feature_checker: Arc, fs: Arc, maybe_file_watcher_communicator: Option>, maybe_inspector_server: Option>, maybe_lockfile: Option>, module_loader_factory: Box, node_resolver: Arc, npm_resolver: Arc, pkg_json_resolver: Arc, root_cert_store_provider: Arc, root_permissions: PermissionsContainer, shared_array_buffer_store: SharedArrayBufferStore, storage_key_resolver: StorageKeyResolver, options: CliMainWorkerOptions, subcommand: DenoSubcommand, otel_config: Option, // `None` means OpenTelemetry is disabled. } impl SharedWorkerState { pub fn create_node_init_services( &self, node_require_loader: NodeRequireLoaderRc, ) -> NodeExtInitServices { NodeExtInitServices { node_require_loader, node_resolver: self.node_resolver.clone(), npm_resolver: self.npm_resolver.clone().into_npm_pkg_folder_resolver(), pkg_json_resolver: self.pkg_json_resolver.clone(), } } pub fn npm_process_state_provider(&self) -> NpmProcessStateProviderRc { self.npm_resolver.clone().into_process_state_provider() } } pub struct CliMainWorker { main_module: ModuleSpecifier, worker: MainWorker, shared: Arc, } impl CliMainWorker { pub fn into_main_worker(self) -> MainWorker { self.worker } pub async fn setup_repl(&mut self) -> Result<(), AnyError> { self.worker.run_event_loop(false).await?; Ok(()) } pub async fn run(&mut self) -> Result { let mut maybe_coverage_collector = self.maybe_setup_coverage_collector().await?; let mut maybe_hmr_runner = self.maybe_setup_hmr_runner().await?; log::debug!("main_module {}", self.main_module); self.execute_main_module().await?; self.worker.dispatch_load_event()?; loop { if let Some(hmr_runner) = maybe_hmr_runner.as_mut() { let watcher_communicator = self.shared.maybe_file_watcher_communicator.clone().unwrap(); let hmr_future = hmr_runner.run().boxed_local(); let event_loop_future = self.worker.run_event_loop(false).boxed_local(); let result; select! { hmr_result = hmr_future => { result = hmr_result; }, event_loop_result = event_loop_future => { result = event_loop_result; } } if let Err(e) = result { watcher_communicator .change_restart_mode(WatcherRestartMode::Automatic); return Err(e); } } else { self .worker .run_event_loop(maybe_coverage_collector.is_none()) .await?; } let web_continue = self.worker.dispatch_beforeunload_event()?; if !web_continue { let node_continue = self.worker.dispatch_process_beforeexit_event()?; if !node_continue { break; } } } self.worker.dispatch_unload_event()?; self.worker.dispatch_process_exit_event()?; if let Some(coverage_collector) = maybe_coverage_collector.as_mut() { self .worker .js_runtime .with_event_loop_future( coverage_collector.stop_collecting().boxed_local(), PollEventLoopOptions::default(), ) .await?; } if let Some(hmr_runner) = maybe_hmr_runner.as_mut() { self .worker .js_runtime .with_event_loop_future( hmr_runner.stop().boxed_local(), PollEventLoopOptions::default(), ) .await?; } Ok(self.worker.exit_code()) } pub async fn run_for_watcher(self) -> Result<(), AnyError> { /// The FileWatcherModuleExecutor provides module execution with safe dispatching of life-cycle events by tracking the /// state of any pending events and emitting accordingly on drop in the case of a future /// cancellation. struct FileWatcherModuleExecutor { inner: CliMainWorker, pending_unload: bool, } impl FileWatcherModuleExecutor { pub fn new(worker: CliMainWorker) -> FileWatcherModuleExecutor { FileWatcherModuleExecutor { inner: worker, pending_unload: false, } } /// Execute the given main module emitting load and unload events before and after execution /// respectively. pub async fn execute(&mut self) -> Result<(), AnyError> { self.inner.execute_main_module().await?; self.inner.worker.dispatch_load_event()?; self.pending_unload = true; let result = loop { match self.inner.worker.run_event_loop(false).await { Ok(()) => {} Err(error) => break Err(error), } let web_continue = self.inner.worker.dispatch_beforeunload_event()?; if !web_continue { let node_continue = self.inner.worker.dispatch_process_beforeexit_event()?; if !node_continue { break Ok(()); } } }; self.pending_unload = false; result?; self.inner.worker.dispatch_unload_event()?; self.inner.worker.dispatch_process_exit_event()?; Ok(()) } } impl Drop for FileWatcherModuleExecutor { fn drop(&mut self) { if self.pending_unload { let _ = self.inner.worker.dispatch_unload_event(); } } } let mut executor = FileWatcherModuleExecutor::new(self); executor.execute().await } pub async fn execute_main_module(&mut self) -> Result<(), AnyError> { let id = self.worker.preload_main_module(&self.main_module).await?; self.worker.evaluate_module(id).await } pub async fn execute_side_module(&mut self) -> Result<(), AnyError> { let id = self.worker.preload_side_module(&self.main_module).await?; self.worker.evaluate_module(id).await } pub async fn maybe_setup_hmr_runner( &mut self, ) -> Result>, AnyError> { if !self.shared.options.hmr { return Ok(None); } let Some(setup_hmr_runner) = self.shared.options.create_hmr_runner.as_ref() else { return Ok(None); }; let session = self.worker.create_inspector_session(); let mut hmr_runner = setup_hmr_runner(session); self .worker .js_runtime .with_event_loop_future( hmr_runner.start().boxed_local(), PollEventLoopOptions::default(), ) .await?; Ok(Some(hmr_runner)) } pub async fn maybe_setup_coverage_collector( &mut self, ) -> Result>, AnyError> { let Some(create_coverage_collector) = self.shared.options.create_coverage_collector.as_ref() else { return Ok(None); }; let session = self.worker.create_inspector_session(); let mut coverage_collector = create_coverage_collector(session); self .worker .js_runtime .with_event_loop_future( coverage_collector.start_collecting().boxed_local(), PollEventLoopOptions::default(), ) .await?; Ok(Some(coverage_collector)) } pub fn execute_script_static( &mut self, name: &'static str, source_code: &'static str, ) -> Result, AnyError> { self.worker.js_runtime.execute_script(name, source_code) } } // TODO(bartlomieju): this should be moved to some other place, added to avoid string // duplication between worker setups and `deno info` output. pub fn get_cache_storage_dir() -> PathBuf { // Note: we currently use temp_dir() to avoid managing storage size. std::env::temp_dir().join("deno_cache") } #[derive(Clone)] pub struct CliMainWorkerFactory { shared: Arc, } impl CliMainWorkerFactory { #[allow(clippy::too_many_arguments)] pub fn new( blob_store: Arc, code_cache: Option>, feature_checker: Arc, fs: Arc, maybe_file_watcher_communicator: Option>, maybe_inspector_server: Option>, maybe_lockfile: Option>, module_loader_factory: Box, node_resolver: Arc, npm_resolver: Arc, pkg_json_resolver: Arc, root_cert_store_provider: Arc, root_permissions: PermissionsContainer, storage_key_resolver: StorageKeyResolver, subcommand: DenoSubcommand, options: CliMainWorkerOptions, otel_config: Option, ) -> Self { Self { shared: Arc::new(SharedWorkerState { blob_store, broadcast_channel: Default::default(), code_cache, compiled_wasm_module_store: Default::default(), feature_checker, fs, maybe_file_watcher_communicator, maybe_inspector_server, maybe_lockfile, module_loader_factory, node_resolver, npm_resolver, pkg_json_resolver, root_cert_store_provider, root_permissions, shared_array_buffer_store: Default::default(), storage_key_resolver, options, subcommand, otel_config, }), } } pub async fn create_main_worker( &self, mode: WorkerExecutionMode, main_module: ModuleSpecifier, ) -> Result { self .create_custom_worker( mode, main_module, self.shared.root_permissions.clone(), vec![], Default::default(), ) .await } pub async fn create_custom_worker( &self, mode: WorkerExecutionMode, main_module: ModuleSpecifier, permissions: PermissionsContainer, custom_extensions: Vec, stdio: deno_runtime::deno_io::Stdio, ) -> Result { let shared = &self.shared; let CreateModuleLoaderResult { module_loader, node_require_loader, } = shared .module_loader_factory .create_for_main(permissions.clone()); let main_module = if let Ok(package_ref) = NpmPackageReqReference::from_specifier(&main_module) { if let Some(npm_resolver) = shared.npm_resolver.as_managed() { npm_resolver .add_package_reqs(&[package_ref.req().clone()]) .await?; } // use a fake referrer that can be used to discover the package.json if necessary let referrer = ModuleSpecifier::from_directory_path(self.shared.fs.cwd()?) .unwrap() .join("package.json")?; let package_folder = shared .npm_resolver .resolve_pkg_folder_from_deno_module_req( package_ref.req(), &referrer, )?; let main_module = self .resolve_binary_entrypoint(&package_folder, package_ref.sub_path())?; if let Some(lockfile) = &shared.maybe_lockfile { // For npm binary commands, ensure that the lockfile gets updated // so that we can re-use the npm resolution the next time it runs // for better performance lockfile.write_if_changed()?; } main_module } else { main_module }; let maybe_inspector_server = shared.maybe_inspector_server.clone(); let create_web_worker_cb = create_web_worker_callback(shared.clone(), stdio.clone()); let maybe_storage_key = shared .storage_key_resolver .resolve_storage_key(&main_module); let origin_storage_dir = maybe_storage_key.as_ref().map(|key| { shared .options .origin_data_folder_path .as_ref() .unwrap() // must be set if storage key resolver returns a value .join(checksum::gen(&[key.as_bytes()])) }); let cache_storage_dir = maybe_storage_key.map(|key| { // TODO(@satyarohith): storage quota management get_cache_storage_dir().join(checksum::gen(&[key.as_bytes()])) }); // TODO(bartlomieju): this is cruft, update FeatureChecker to spit out // list of enabled features. let feature_checker = shared.feature_checker.clone(); let mut unstable_features = Vec::with_capacity(crate::UNSTABLE_GRANULAR_FLAGS.len()); for granular_flag in crate::UNSTABLE_GRANULAR_FLAGS { if feature_checker.check(granular_flag.name) { unstable_features.push(granular_flag.id); } } let services = WorkerServiceOptions { root_cert_store_provider: Some(shared.root_cert_store_provider.clone()), module_loader, fs: shared.fs.clone(), node_services: Some( shared.create_node_init_services(node_require_loader), ), npm_process_state_provider: Some(shared.npm_process_state_provider()), blob_store: shared.blob_store.clone(), broadcast_channel: shared.broadcast_channel.clone(), fetch_dns_resolver: Default::default(), shared_array_buffer_store: Some(shared.shared_array_buffer_store.clone()), compiled_wasm_module_store: Some( shared.compiled_wasm_module_store.clone(), ), feature_checker, permissions, v8_code_cache: shared.code_cache.clone().map(|c| c.as_code_cache()), }; let options = WorkerOptions { bootstrap: BootstrapOptions { deno_version: crate::version::DENO_VERSION_INFO.deno.to_string(), args: shared.options.argv.clone(), cpu_count: std::thread::available_parallelism() .map(|p| p.get()) .unwrap_or(1), log_level: shared.options.log_level, enable_op_summary_metrics: shared.options.enable_op_summary_metrics, enable_testing_features: shared.options.enable_testing_features, locale: deno_core::v8::icu::get_language_tag(), location: shared.options.location.clone(), no_color: !colors::use_color(), is_stdout_tty: deno_terminal::is_stdout_tty(), is_stderr_tty: deno_terminal::is_stderr_tty(), color_level: colors::get_color_level(), unstable_features, user_agent: version::DENO_VERSION_INFO.user_agent.to_string(), inspect: shared.options.is_inspecting, has_node_modules_dir: shared.options.has_node_modules_dir, argv0: shared.options.argv0.clone(), node_debug: shared.options.node_debug.clone(), node_ipc_fd: shared.options.node_ipc, mode, serve_port: shared.options.serve_port, serve_host: shared.options.serve_host.clone(), otel_config: shared.otel_config.clone(), }, extensions: custom_extensions, startup_snapshot: crate::js::deno_isolate_init(), create_params: create_isolate_create_params(), unsafely_ignore_certificate_errors: shared .options .unsafely_ignore_certificate_errors .clone(), seed: shared.options.seed, format_js_error_fn: Some(Arc::new(format_js_error)), create_web_worker_cb, maybe_inspector_server, should_break_on_first_statement: shared.options.inspect_brk, should_wait_for_inspector_session: shared.options.inspect_wait, strace_ops: shared.options.strace_ops.clone(), get_error_class_fn: Some(&errors::get_error_class_name), cache_storage_dir, origin_storage_dir, stdio, skip_op_registration: shared.options.skip_op_registration, enable_stack_trace_arg_in_ops: crate::args::has_trace_permissions_enabled( ), }; let mut worker = MainWorker::bootstrap_from_options( main_module.clone(), services, options, ); if self.shared.subcommand.needs_test() { macro_rules! test_file { ($($file:literal),*) => { $(worker.js_runtime.lazy_load_es_module_with_code( concat!("ext:cli/", $file), deno_core::ascii_str_include!(concat!("js/", $file)), )?;)* } } test_file!( "40_test_common.js", "40_test.js", "40_bench.js", "40_jupyter.js" ); } Ok(CliMainWorker { main_module, worker, shared: shared.clone(), }) } fn resolve_binary_entrypoint( &self, package_folder: &Path, sub_path: Option<&str>, ) -> Result { match self .shared .node_resolver .resolve_binary_export(package_folder, sub_path) { Ok(specifier) => Ok(specifier), Err(original_err) => { // if the binary entrypoint was not found, fallback to regular node resolution let result = self.resolve_binary_entrypoint_fallback(package_folder, sub_path); match result { Ok(Some(specifier)) => Ok(specifier), Ok(None) => Err(original_err.into()), Err(fallback_err) => { bail!("{:#}\n\nFallback failed: {:#}", original_err, fallback_err) } } } } } /// resolve the binary entrypoint using regular node resolution fn resolve_binary_entrypoint_fallback( &self, package_folder: &Path, sub_path: Option<&str>, ) -> Result, AnyError> { // only fallback if the user specified a sub path if sub_path.is_none() { // it's confusing to users if the package doesn't have any binary // entrypoint and we just execute the main script which will likely // have blank output, so do not resolve the entrypoint in this case return Ok(None); } let specifier = self .shared .node_resolver .resolve_package_subpath_from_deno_module( package_folder, sub_path, /* referrer */ None, NodeModuleKind::Esm, NodeResolutionMode::Execution, )?; if specifier .to_file_path() .map(|p| p.exists()) .unwrap_or(false) { Ok(Some(specifier)) } else { bail!("Cannot find module '{}'", specifier) } } } fn create_web_worker_callback( shared: Arc, stdio: deno_runtime::deno_io::Stdio, ) -> Arc { Arc::new(move |args| { let maybe_inspector_server = shared.maybe_inspector_server.clone(); let CreateModuleLoaderResult { module_loader, node_require_loader, } = shared.module_loader_factory.create_for_worker( args.parent_permissions.clone(), args.permissions.clone(), ); let create_web_worker_cb = create_web_worker_callback(shared.clone(), stdio.clone()); let maybe_storage_key = shared .storage_key_resolver .resolve_storage_key(&args.main_module); let cache_storage_dir = maybe_storage_key.map(|key| { // TODO(@satyarohith): storage quota management get_cache_storage_dir().join(checksum::gen(&[key.as_bytes()])) }); // TODO(bartlomieju): this is cruft, update FeatureChecker to spit out // list of enabled features. let feature_checker = shared.feature_checker.clone(); let mut unstable_features = Vec::with_capacity(crate::UNSTABLE_GRANULAR_FLAGS.len()); for granular_flag in crate::UNSTABLE_GRANULAR_FLAGS { if feature_checker.check(granular_flag.name) { unstable_features.push(granular_flag.id); } } let services = WebWorkerServiceOptions { root_cert_store_provider: Some(shared.root_cert_store_provider.clone()), module_loader, fs: shared.fs.clone(), node_services: Some( shared.create_node_init_services(node_require_loader), ), blob_store: shared.blob_store.clone(), broadcast_channel: shared.broadcast_channel.clone(), shared_array_buffer_store: Some(shared.shared_array_buffer_store.clone()), compiled_wasm_module_store: Some( shared.compiled_wasm_module_store.clone(), ), maybe_inspector_server, feature_checker, npm_process_state_provider: Some(shared.npm_process_state_provider()), permissions: args.permissions, }; let options = WebWorkerOptions { name: args.name, main_module: args.main_module.clone(), worker_id: args.worker_id, bootstrap: BootstrapOptions { deno_version: crate::version::DENO_VERSION_INFO.deno.to_string(), args: shared.options.argv.clone(), cpu_count: std::thread::available_parallelism() .map(|p| p.get()) .unwrap_or(1), log_level: shared.options.log_level, enable_op_summary_metrics: shared.options.enable_op_summary_metrics, enable_testing_features: shared.options.enable_testing_features, locale: deno_core::v8::icu::get_language_tag(), location: Some(args.main_module), no_color: !colors::use_color(), color_level: colors::get_color_level(), is_stdout_tty: deno_terminal::is_stdout_tty(), is_stderr_tty: deno_terminal::is_stderr_tty(), unstable_features, user_agent: version::DENO_VERSION_INFO.user_agent.to_string(), inspect: shared.options.is_inspecting, has_node_modules_dir: shared.options.has_node_modules_dir, argv0: shared.options.argv0.clone(), node_debug: shared.options.node_debug.clone(), node_ipc_fd: None, mode: WorkerExecutionMode::Worker, serve_port: shared.options.serve_port, serve_host: shared.options.serve_host.clone(), otel_config: shared.otel_config.clone(), }, extensions: vec![], startup_snapshot: crate::js::deno_isolate_init(), create_params: create_isolate_create_params(), unsafely_ignore_certificate_errors: shared .options .unsafely_ignore_certificate_errors .clone(), seed: shared.options.seed, create_web_worker_cb, format_js_error_fn: Some(Arc::new(format_js_error)), worker_type: args.worker_type, get_error_class_fn: Some(&errors::get_error_class_name), stdio: stdio.clone(), cache_storage_dir, strace_ops: shared.options.strace_ops.clone(), close_on_idle: args.close_on_idle, maybe_worker_metadata: args.maybe_worker_metadata, enable_stack_trace_arg_in_ops: crate::args::has_trace_permissions_enabled( ), }; WebWorker::bootstrap_from_options(services, options) }) } /// By default V8 uses 1.4Gb heap limit which is meant for browser tabs. /// Instead probe for the total memory on the system and use it instead /// as a default. pub fn create_isolate_create_params() -> Option { let maybe_mem_info = deno_runtime::sys_info::mem_info(); maybe_mem_info.map(|mem_info| { v8::CreateParams::default() .heap_limits_from_system_memory(mem_info.total, 0) }) } #[allow(clippy::print_stdout)] #[allow(clippy::print_stderr)] #[cfg(test)] mod tests { use super::*; use deno_core::resolve_path; use deno_core::FsModuleLoader; use deno_fs::RealFs; use deno_runtime::deno_permissions::Permissions; use deno_runtime::permissions::RuntimePermissionDescriptorParser; fn create_test_worker() -> MainWorker { let main_module = resolve_path("./hello.js", &std::env::current_dir().unwrap()).unwrap(); let fs = Arc::new(RealFs); let permission_desc_parser = Arc::new(RuntimePermissionDescriptorParser::new(fs.clone())); let options = WorkerOptions { startup_snapshot: crate::js::deno_isolate_init(), ..Default::default() }; MainWorker::bootstrap_from_options( main_module, WorkerServiceOptions { module_loader: Rc::new(FsModuleLoader), permissions: PermissionsContainer::new( permission_desc_parser, Permissions::none_without_prompt(), ), blob_store: Default::default(), broadcast_channel: Default::default(), feature_checker: Default::default(), node_services: Default::default(), npm_process_state_provider: Default::default(), root_cert_store_provider: Default::default(), fetch_dns_resolver: Default::default(), shared_array_buffer_store: Default::default(), compiled_wasm_module_store: Default::default(), v8_code_cache: Default::default(), fs, }, options, ) } #[tokio::test] async fn execute_mod_esm_imports_a() { let p = test_util::testdata_path().join("runtime/esm_imports_a.js"); let module_specifier = ModuleSpecifier::from_file_path(&p).unwrap(); let mut worker = create_test_worker(); let result = worker.execute_main_module(&module_specifier).await; if let Err(err) = result { eprintln!("execute_mod err {err:?}"); } if let Err(e) = worker.run_event_loop(false).await { panic!("Future got unexpected error: {e:?}"); } } #[tokio::test] async fn execute_mod_circular() { let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .parent() .unwrap() .join("tests/circular1.js"); let module_specifier = ModuleSpecifier::from_file_path(&p).unwrap(); let mut worker = create_test_worker(); let result = worker.execute_main_module(&module_specifier).await; if let Err(err) = result { eprintln!("execute_mod err {err:?}"); } if let Err(e) = worker.run_event_loop(false).await { panic!("Future got unexpected error: {e:?}"); } } #[tokio::test] async fn execute_mod_resolve_error() { // "foo" is not a valid module specifier so this should return an error. let mut worker = create_test_worker(); let module_specifier = resolve_path("./does-not-exist", &std::env::current_dir().unwrap()) .unwrap(); let result = worker.execute_main_module(&module_specifier).await; assert!(result.is_err()); } #[tokio::test] async fn execute_mod_002_hello() { // This assumes cwd is project root (an assumption made throughout the // tests). let mut worker = create_test_worker(); let p = test_util::testdata_path().join("run/001_hello.js"); let module_specifier = ModuleSpecifier::from_file_path(&p).unwrap(); let result = worker.execute_main_module(&module_specifier).await; assert!(result.is_ok()); } }