diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2019-04-16 15:13:42 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-04-16 15:13:42 -0400 |
commit | 1bfb44336914a775cd01120165517394d30aec23 (patch) | |
tree | 2f126b5f36ad8fe2ddd9cae118058affd413c68d /cli | |
parent | 0c463582206881b6461742633a67f51632db614e (diff) |
Implement async module loading in CLI (#2084)
Diffstat (limited to 'cli')
-rw-r--r-- | cli/errors.rs | 10 | ||||
-rw-r--r-- | cli/main.rs | 124 | ||||
-rw-r--r-- | cli/modules.rs | 263 | ||||
-rw-r--r-- | cli/ops.rs | 16 | ||||
-rw-r--r-- | cli/state.rs | 3 | ||||
-rw-r--r-- | cli/worker.rs | 298 |
6 files changed, 278 insertions, 436 deletions
diff --git a/cli/errors.rs b/cli/errors.rs index 3873f70ff..bd3e7ba73 100644 --- a/cli/errors.rs +++ b/cli/errors.rs @@ -217,3 +217,13 @@ impl fmt::Display for RustOrJsError { } } } + +// TODO(ry) This is ugly. They are essentially the same type. +impl From<deno::JSErrorOr<DenoError>> for RustOrJsError { + fn from(e: deno::JSErrorOr<DenoError>) -> Self { + match e { + deno::JSErrorOr::JSError(err) => RustOrJsError::Js(err), + deno::JSErrorOr::Other(err) => RustOrJsError::Rust(err), + } + } +} diff --git a/cli/main.rs b/cli/main.rs index dc2099595..b6cd49d0e 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -20,7 +20,6 @@ mod global_timer; mod http_body; mod http_util; pub mod js_errors; -pub mod modules; pub mod msg; pub mod msg_util; pub mod ops; @@ -37,6 +36,7 @@ pub mod worker; use crate::errors::RustOrJsError; use crate::state::ThreadSafeState; +use crate::worker::root_specifier_to_url; use crate::worker::Worker; use futures::lazy; use futures::Future; @@ -74,6 +74,49 @@ where } } +// TODO(ry) Move this to main.rs +pub fn print_file_info(worker: &Worker, url: &str) { + let maybe_out = + worker::fetch_module_meta_data_and_maybe_compile(&worker.state, url, "."); + if let Err(err) = maybe_out { + println!("{}", err); + return; + } + let out = maybe_out.unwrap(); + + println!("{} {}", ansi::bold("local:".to_string()), &(out.filename)); + + println!( + "{} {}", + ansi::bold("type:".to_string()), + msg::enum_name_media_type(out.media_type) + ); + + if out.maybe_output_code_filename.is_some() { + println!( + "{} {}", + ansi::bold("compiled:".to_string()), + out.maybe_output_code_filename.as_ref().unwrap(), + ); + } + + if out.maybe_source_map_filename.is_some() { + println!( + "{} {}", + ansi::bold("map:".to_string()), + out.maybe_source_map_filename.as_ref().unwrap() + ); + } + + let deps = worker.modules.deps(&out.module_name); + println!("{}{}", ansi::bold("deps:\n".to_string()), deps.name); + if let Some(ref depsdeps) = deps.deps { + for d in depsdeps { + println!("{}", d); + } + } +} + fn main() { #[cfg(windows)] ansi_term::enable_ansi_support().ok(); // For Windows 10 @@ -102,17 +145,18 @@ fn main() { let should_display_info = flags.info; let state = ThreadSafeState::new(flags, rest_argv, ops::op_selector_std); - let mut main_worker = Worker::new( + let mut worker = Worker::new( "main".to_string(), startup_data::deno_isolate_init(), state.clone(), ); - let main_future = lazy(move || { - // Setup runtime. - js_check(main_worker.execute("denoMain()")); + // TODO(ry) somehow combine the two branches below. They're very similar but + // it's difficult to get the types to workout. - if state.flags.eval { + if state.flags.eval { + let main_future = lazy(move || { + js_check(worker.execute("denoMain()")); // Wrap provided script in async function so asynchronous methods // work. This is required until top-level await is not supported. let js_source = format!( @@ -125,25 +169,51 @@ fn main() { ); // ATM imports in `deno eval` are not allowed // TODO Support ES modules once Worker supports evaluating anonymous modules. - js_check(main_worker.execute(&js_source)); - } else { - // Execute main module. - if let Some(main_module) = state.main_module() { - debug!("main_module {}", main_module); - js_check(main_worker.execute_mod(&main_module, should_prefetch)); - if should_display_info { - // Display file info and exit. Do not run file - main_worker.print_file_info(&main_module); - std::process::exit(0); - } - } - } - - main_worker.then(|result| { - js_check(result); - Ok(()) - }) - }); - - tokio_util::run(main_future); + js_check(worker.execute(&js_source)); + worker.then(|result| { + js_check(result); + Ok(()) + }) + }); + tokio_util::run(main_future); + } else if let Some(main_module) = state.main_module() { + // Normal situation of executing a module. + + let main_future = lazy(move || { + // Setup runtime. + js_check(worker.execute("denoMain()")); + debug!("main_module {}", main_module); + + let main_url = root_specifier_to_url(&main_module).unwrap(); + + worker + .execute_mod_async(&main_url, should_prefetch) + .and_then(move |worker| { + if should_display_info { + // Display file info and exit. Do not run file + print_file_info(&worker, &main_module); + std::process::exit(0); + } + worker.then(|result| { + js_check(result); + Ok(()) + }) + }).map_err(|(err, _worker)| print_err_and_exit(err)) + }); + tokio_util::run(main_future); + } else { + // REPL situation. + let main_future = lazy(move || { + // Setup runtime. + js_check(worker.execute("denoMain()")); + worker + .then(|result| { + js_check(result); + Ok(()) + }).map_err(|(err, _worker): (RustOrJsError, Worker)| { + print_err_and_exit(err) + }) + }); + tokio_util::run(main_future); + } } diff --git a/cli/modules.rs b/cli/modules.rs deleted file mode 100644 index f40f5ca08..000000000 --- a/cli/modules.rs +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::ansi; -use crate::deno_dir::DenoDir; -use crate::msg; -use deno::deno_mod; -use std::collections::HashMap; -use std::collections::HashSet; -use std::fmt; - -pub struct ModuleInfo { - name: String, - children: Vec<deno_mod>, -} - -/// A symbolic module entity. -pub enum SymbolicModule { - /// This module is an alias to another module. - /// This is useful such that multiple names could point to - /// the same underlying module (particularly due to redirects). - Alias(String), - /// This module associates with a V8 module by id. - Mod(deno_mod), -} - -#[derive(Default)] -/// Alias-able module name map -pub struct ModuleNameMap { - inner: HashMap<String, SymbolicModule>, -} - -impl ModuleNameMap { - pub fn new() -> Self { - ModuleNameMap { - inner: HashMap::new(), - } - } - - /// Get the id of a module. - /// If this module is internally represented as an alias, - /// follow the alias chain to get the final module id. - pub fn get(&self, name: &str) -> Option<deno_mod> { - let mut mod_name = name; - loop { - let cond = self.inner.get(mod_name); - match cond { - Some(SymbolicModule::Alias(target)) => { - mod_name = target; - } - Some(SymbolicModule::Mod(mod_id)) => { - return Some(*mod_id); - } - _ => { - return None; - } - } - } - } - - /// Insert a name assocated module id. - pub fn insert(&mut self, name: String, id: deno_mod) { - self.inner.insert(name, SymbolicModule::Mod(id)); - } - - /// Create an alias to another module. - pub fn alias(&mut self, name: String, target: String) { - self.inner.insert(name, SymbolicModule::Alias(target)); - } -} - -/// A collection of JS modules. -#[derive(Default)] -pub struct Modules { - pub info: HashMap<deno_mod, ModuleInfo>, - pub by_name: ModuleNameMap, -} - -impl Modules { - pub fn new() -> Modules { - Self { - info: HashMap::new(), - by_name: ModuleNameMap::new(), - } - } - - pub fn get_id(&self, name: &str) -> Option<deno_mod> { - self.by_name.get(name) - } - - pub fn get_children(&self, id: deno_mod) -> Option<&Vec<deno_mod>> { - self.info.get(&id).map(|i| &i.children) - } - - pub fn get_name(&self, id: deno_mod) -> Option<&String> { - self.info.get(&id).map(|i| &i.name) - } - - pub fn is_registered(&self, name: &str) -> bool { - self.by_name.get(name).is_some() - } - - pub fn register(&mut self, id: deno_mod, name: &str) { - let name = String::from(name); - debug!("register {}", name); - self.by_name.insert(name.clone(), id); - self.info.insert( - id, - ModuleInfo { - name, - children: Vec::new(), - }, - ); - } - - pub fn alias(&mut self, name: &str, target: &str) { - self.by_name.alias(name.to_owned(), target.to_owned()); - } - - pub fn resolve_cb( - &mut self, - deno_dir: &DenoDir, - specifier: &str, - referrer: deno_mod, - ) -> deno_mod { - debug!("resolve_cb {}", specifier); - - let maybe_info = self.info.get_mut(&referrer); - if maybe_info.is_none() { - debug!("cant find referrer {}", referrer); - return 0; - } - let info = maybe_info.unwrap(); - let referrer_name = &info.name; - let r = deno_dir.resolve_module(specifier, referrer_name); - if let Err(err) = r { - debug!("potentially swallowed err: {}", err); - return 0; - } - let (name, _local_filename) = r.unwrap(); - - if let Some(child_id) = self.by_name.get(&name) { - info.children.push(child_id); - return child_id; - } else { - return 0; - } - } - - pub fn print_file_info(&self, deno_dir: &DenoDir, filename: String) { - // TODO Note the --reload flag is ignored here. - let maybe_out = deno_dir.fetch_module_meta_data(&filename, ".", true); - if maybe_out.is_err() { - println!("{}", maybe_out.unwrap_err()); - return; - } - let out = maybe_out.unwrap(); - - println!("{} {}", ansi::bold("local:".to_string()), &(out.filename)); - println!( - "{} {}", - ansi::bold("type:".to_string()), - msg::enum_name_media_type(out.media_type) - ); - if out.maybe_output_code_filename.is_some() { - println!( - "{} {}", - ansi::bold("compiled:".to_string()), - out.maybe_output_code_filename.as_ref().unwrap(), - ); - } - if out.maybe_source_map_filename.is_some() { - println!( - "{} {}", - ansi::bold("map:".to_string()), - out.maybe_source_map_filename.as_ref().unwrap() - ); - } - - let deps = Deps::new(self, &out.module_name); - println!("{}{}", ansi::bold("deps:\n".to_string()), deps.name); - if let Some(ref depsdeps) = deps.deps { - for d in depsdeps { - println!("{}", d); - } - } - } -} - -pub struct Deps { - pub name: String, - pub deps: Option<Vec<Deps>>, - prefix: String, - is_last: bool, -} - -impl Deps { - pub fn new(modules: &Modules, module_name: &str) -> Deps { - let mut seen = HashSet::new(); - let id = modules.get_id(module_name).unwrap(); - Self::helper(&mut seen, "".to_string(), true, modules, id) - } - - fn helper( - seen: &mut HashSet<deno_mod>, - prefix: String, - is_last: bool, - modules: &Modules, - id: deno_mod, - ) -> Deps { - let name = modules.get_name(id).unwrap().to_string(); - if seen.contains(&id) { - Deps { - name, - prefix, - deps: None, - is_last, - } - } else { - seen.insert(id); - let child_ids = modules.get_children(id).unwrap(); - let child_count = child_ids.iter().count(); - let deps = child_ids - .iter() - .enumerate() - .map(|(index, dep_id)| { - let new_is_last = index == child_count - 1; - let mut new_prefix = prefix.clone(); - new_prefix.push(if is_last { ' ' } else { '│' }); - new_prefix.push(' '); - Self::helper(seen, new_prefix, new_is_last, modules, *dep_id) - }).collect(); - Deps { - name, - prefix, - deps: Some(deps), - is_last, - } - } - } -} - -impl fmt::Display for Deps { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut has_children = false; - if let Some(ref deps) = self.deps { - has_children = !deps.is_empty(); - } - write!( - f, - "{}{}─{} {}", - self.prefix, - if self.is_last { "└" } else { "├" }, - if has_children { "┬" } else { "─" }, - self.name - )?; - - if let Some(ref deps) = self.deps { - for d in deps { - write!(f, "\n{}", d)?; - } - } - Ok(()) - } -} diff --git a/cli/ops.rs b/cli/ops.rs index 0d752e226..5e2a62439 100644 --- a/cli/ops.rs +++ b/cli/ops.rs @@ -19,6 +19,7 @@ use crate::state::ThreadSafeState; use crate::tokio_util; use crate::tokio_write; use crate::version; +use crate::worker::root_specifier_to_url; use crate::worker::Worker; use deno::deno_buf; use deno::js_check; @@ -1878,9 +1879,14 @@ fn op_create_worker( Worker::new(name, startup_data::deno_isolate_init(), child_state); js_check(worker.execute("denoMain()")); js_check(worker.execute("workerMain()")); - let result = worker.execute_mod(specifier, false); + + let specifier_url = + root_specifier_to_url(specifier).map_err(DenoError::from)?; + + // TODO(ry) Use execute_mod_async here. + let result = worker.execute_mod(&specifier_url, false); match result { - Ok(_) => { + Ok(worker) => { let mut workers_tl = parent_state.workers.lock().unwrap(); workers_tl.insert(rid, worker.shared()); let builder = &mut FlatBufferBuilder::new(); @@ -1898,8 +1904,10 @@ fn op_create_worker( }, )) } - Err(errors::RustOrJsError::Js(_)) => Err(errors::worker_init_failed()), - Err(errors::RustOrJsError::Rust(err)) => Err(err), + Err((errors::RustOrJsError::Js(_), _worker)) => { + Err(errors::worker_init_failed()) + } + Err((errors::RustOrJsError::Rust(err), _worker)) => Err(err), } }())) } diff --git a/cli/state.rs b/cli/state.rs index 3434566fa..24f2f5053 100644 --- a/cli/state.rs +++ b/cli/state.rs @@ -3,7 +3,6 @@ use crate::deno_dir; use crate::errors::DenoResult; use crate::flags; use crate::global_timer::GlobalTimer; -use crate::modules::Modules; use crate::ops; use crate::permissions::DenoPermissions; use crate::resources; @@ -54,7 +53,6 @@ pub struct State { pub permissions: DenoPermissions, pub flags: flags::DenoFlags, pub metrics: Metrics, - pub modules: Mutex<Modules>, pub worker_channels: Mutex<WorkerChannels>, pub global_timer: Mutex<GlobalTimer>, pub workers: Mutex<UserWorkerTable>, @@ -106,7 +104,6 @@ impl ThreadSafeState { permissions: DenoPermissions::from_flags(&flags), flags, metrics: Metrics::default(), - modules: Mutex::new(Modules::new()), worker_channels: Mutex::new(internal_channels), global_timer: Mutex::new(GlobalTimer::new()), workers: Mutex::new(UserWorkerTable::new()), diff --git a/cli/worker.rs b/cli/worker.rs index 7178639c5..c43dbf6ee 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -9,19 +9,21 @@ use crate::msg; use crate::state::ThreadSafeState; use crate::tokio_util; use deno; -use deno::deno_mod; use deno::JSError; +use deno::Loader; use deno::StartupData; use futures::future::Either; use futures::Async; use futures::Future; use std::sync::atomic::Ordering; +use url::Url; /// Wraps deno::Isolate to provide source maps, ops for the CLI, and /// high-level module loading pub struct Worker { inner: deno::Isolate<ThreadSafeState>, - state: ThreadSafeState, + pub modules: deno::Modules, + pub state: ThreadSafeState, } impl Worker { @@ -33,6 +35,7 @@ impl Worker { let state_ = state.clone(); Self { inner: deno::Isolate::new(startup_data, state_), + modules: deno::Modules::new(), state, } } @@ -52,143 +55,133 @@ impl Worker { self.inner.execute(js_filename, js_source) } - // TODO(ry) make this return a future. - fn mod_load_deps(&self, id: deno_mod) -> Result<(), RustOrJsError> { - // basically iterate over the imports, start loading them. - - let referrer_name = { - let g = self.state.modules.lock().unwrap(); - g.get_name(id).unwrap().clone() - }; - - for specifier in self.inner.mod_get_imports(id) { - let (name, _local_filename) = self - .state - .dir - .resolve_module(&specifier, &referrer_name) - .map_err(DenoError::from) - .map_err(RustOrJsError::from)?; - - debug!("mod_load_deps {}", name); - - if !self.state.modules.lock().unwrap().is_registered(&name) { - let out = fetch_module_meta_data_and_maybe_compile( - &self.state, - &specifier, - &referrer_name, - )?; - let child_id = self.mod_new_and_register( - false, - &out.module_name.clone(), - &out.js_source(), - )?; - - // The resolved module is an alias to another module (due to redirects). - // Save such alias to the module map. - if out.module_redirect_source_name.is_some() { - self.mod_alias( - &out.module_redirect_source_name.clone().unwrap(), - &out.module_name, - ); + /// Consumes worker. Executes the provided JavaScript module. + pub fn execute_mod_async( + self, + js_url: &Url, + is_prefetch: bool, + ) -> impl Future<Item = Self, Error = (RustOrJsError, Self)> { + let recursive_load = deno::RecursiveLoad::new(js_url.as_str(), self); + recursive_load.and_then( + move |(id, mut self_)| -> Result<Self, (deno::JSErrorOr<DenoError>, Self)> { + if is_prefetch { + Ok(self_) + } else { + let result = self_.inner.mod_evaluate(id); + if let Err(err) = result { + Err((deno::JSErrorOr::JSError(err), self_)) + } else { + Ok(self_) + } } - - self.mod_load_deps(child_id)?; - } - } - - Ok(()) + }, + ) + .map_err(|(err, self_)| { + // Convert to RustOrJsError AND apply_source_map. + let err = match err { + deno::JSErrorOr::JSError(err) => RustOrJsError::Js(self_.apply_source_map(err)), + deno::JSErrorOr::Other(err) => RustOrJsError::Rust(err), + }; + (err, self_) + }) } - /// Executes the provided JavaScript module. + /// Consumes worker. Executes the provided JavaScript module. pub fn execute_mod( - &mut self, - js_filename: &str, + self, + js_url: &Url, is_prefetch: bool, - ) -> Result<(), RustOrJsError> { - // TODO move state::execute_mod impl here. - self - .execute_mod_inner(js_filename, is_prefetch) - .map_err(|err| match err { - RustOrJsError::Js(err) => RustOrJsError::Js(self.apply_source_map(err)), - x => x, - }) + ) -> Result<Self, (RustOrJsError, Self)> { + tokio_util::block_on(self.execute_mod_async(js_url, is_prefetch)) } - /// High-level way to execute modules. - /// This will issue HTTP requests and file system calls. - /// Blocks. TODO(ry) Don't block. - fn execute_mod_inner( - &mut self, - url: &str, - is_prefetch: bool, - ) -> Result<(), RustOrJsError> { - let out = fetch_module_meta_data_and_maybe_compile(&self.state, url, ".") - .map_err(RustOrJsError::from)?; - - // Be careful. - // url might not match the actual out.module_name - // due to the mechanism of redirection. - - let id = self - .mod_new_and_register(true, &out.module_name.clone(), &out.js_source()) - .map_err(RustOrJsError::from)?; - - // The resolved module is an alias to another module (due to redirects). - // Save such alias to the module map. - if out.module_redirect_source_name.is_some() { - self.mod_alias( - &out.module_redirect_source_name.clone().unwrap(), - &out.module_name, - ); - } + /// Applies source map to the error. + fn apply_source_map(&self, err: JSError) -> JSError { + js_errors::apply_source_map(&err, &self.state.dir) + } +} - self.mod_load_deps(id)?; +// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier +// TODO(ry) Add tests. +// TODO(ry) Move this to core? +pub fn resolve_module_spec( + specifier: &str, + base: &str, +) -> Result<String, url::ParseError> { + // 1. Apply the URL parser to specifier. If the result is not failure, return + // the result. + // let specifier = parse_local_or_remote(specifier)?.to_string(); + if let Ok(specifier_url) = Url::parse(specifier) { + return Ok(specifier_url.to_string()); + } - let state = self.state.clone(); + // 2. If specifier does not start with the character U+002F SOLIDUS (/), the + // two-character sequence U+002E FULL STOP, U+002F SOLIDUS (./), or the + // three-character sequence U+002E FULL STOP, U+002E FULL STOP, U+002F + // SOLIDUS (../), return failure. + if !specifier.starts_with("/") + && !specifier.starts_with("./") + && !specifier.starts_with("../") + { + // TODO(ry) This is (probably) not the correct error to return here. + return Err(url::ParseError::RelativeUrlWithCannotBeABaseBase); + } - let mut resolve = move |specifier: &str, referrer: deno_mod| -> deno_mod { - state.metrics.resolve_count.fetch_add(1, Ordering::Relaxed); - let mut modules = state.modules.lock().unwrap(); - modules.resolve_cb(&state.dir, specifier, referrer) - }; + // 3. Return the result of applying the URL parser to specifier with base URL + // as the base URL. + let base_url = Url::parse(base)?; + let u = base_url.join(&specifier)?; + Ok(u.to_string()) +} - self - .inner - .mod_instantiate(id, &mut resolve) - .map_err(RustOrJsError::from)?; - if !is_prefetch { - self.inner.mod_evaluate(id).map_err(RustOrJsError::from)?; - } - Ok(()) +/// Takes a string representing a path or URL to a module, but of the type +/// passed through the command-line interface for the main module. This is +/// slightly different than specifiers used in import statements: "foo.js" for +/// example is allowed here, whereas in import statements a leading "./" is +/// required ("./foo.js"). This function is aware of the current working +/// directory and returns an absolute URL. +pub fn root_specifier_to_url( + root_specifier: &str, +) -> Result<Url, url::ParseError> { + let maybe_url = Url::parse(root_specifier); + if let Ok(url) = maybe_url { + Ok(url) + } else { + let cwd = std::env::current_dir().unwrap(); + let base = Url::from_directory_path(cwd).unwrap(); + base.join(root_specifier) } +} - /// Wraps Isolate::mod_new but registers with modules. - fn mod_new_and_register( - &self, - main: bool, - name: &str, - source: &str, - ) -> Result<deno_mod, JSError> { - let id = self.inner.mod_new(main, name, source)?; - self.state.modules.lock().unwrap().register(id, &name); - Ok(id) - } +impl Loader for Worker { + type Dispatch = ThreadSafeState; + type Error = DenoError; - /// Create an alias for another module. - /// The alias could later be used to grab the module - /// which `target` points to. - fn mod_alias(&self, name: &str, target: &str) { - self.state.modules.lock().unwrap().alias(name, target); + fn resolve(specifier: &str, referrer: &str) -> Result<String, Self::Error> { + resolve_module_spec(specifier, referrer) + .map_err(|url_err| DenoError::from(url_err)) } - pub fn print_file_info(&self, module: &str) { - let m = self.state.modules.lock().unwrap(); - m.print_file_info(&self.state.dir, module.to_string()); + /// Given an absolute url, load its source code. + fn load(&mut self, url: &str) -> Box<deno::SourceCodeFuture<Self::Error>> { + self + .state + .metrics + .resolve_count + .fetch_add(1, Ordering::SeqCst); + Box::new( + fetch_module_meta_data_and_maybe_compile_async(&self.state, url, ".") + .map_err(|err| { + eprintln!("{}", err); + err + }).map(|module_meta_data| module_meta_data.js_source()), + ) } - /// Applies source map to the error. - fn apply_source_map(&self, err: JSError) -> JSError { - js_errors::apply_source_map(&err, &self.state.dir) + fn isolate_and_modules<'a: 'b + 'c, 'b, 'c>( + &'a mut self, + ) -> (&'b mut deno::Isolate<Self::Dispatch>, &'c mut deno::Modules) { + (&mut self.inner, &mut self.modules) } } @@ -236,7 +229,7 @@ fn fetch_module_meta_data_and_maybe_compile_async( }) } -fn fetch_module_meta_data_and_maybe_compile( +pub fn fetch_module_meta_data_and_maybe_compile( state: &ThreadSafeState, specifier: &str, referrer: &str, @@ -260,46 +253,54 @@ mod tests { use std::sync::atomic::Ordering; #[test] - fn execute_mod() { + fn execute_mod_esm_imports_a() { let filename = std::env::current_dir() .unwrap() .join("tests/esm_imports_a.js"); - let filename = filename.to_str().unwrap().to_string(); + let js_url = Url::from_file_path(filename).unwrap(); - let argv = vec![String::from("./deno"), filename.clone()]; + let argv = vec![String::from("./deno"), js_url.to_string()]; let (flags, rest_argv) = flags::set_flags(argv).unwrap(); let state = ThreadSafeState::new(flags, rest_argv, op_selector_std); let state_ = state.clone(); tokio_util::run(lazy(move || { - let mut worker = - Worker::new("TEST".to_string(), StartupData::None, state); - if let Err(err) = worker.execute_mod(&filename, false) { - eprintln!("execute_mod err {:?}", err); - } + let worker = Worker::new("TEST".to_string(), StartupData::None, state); + let result = worker.execute_mod(&js_url, false); + let worker = match result { + Err((err, worker)) => { + eprintln!("execute_mod err {:?}", err); + worker + } + Ok(worker) => worker, + }; tokio_util::panic_on_error(worker) })); let metrics = &state_.metrics; - assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 1); + assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 2); } #[test] fn execute_mod_circular() { let filename = std::env::current_dir().unwrap().join("tests/circular1.js"); - let filename = filename.to_str().unwrap().to_string(); + let js_url = Url::from_file_path(filename).unwrap(); - let argv = vec![String::from("./deno"), filename.clone()]; + let argv = vec![String::from("./deno"), js_url.to_string()]; let (flags, rest_argv) = flags::set_flags(argv).unwrap(); let state = ThreadSafeState::new(flags, rest_argv, op_selector_std); let state_ = state.clone(); tokio_util::run(lazy(move || { - let mut worker = - Worker::new("TEST".to_string(), StartupData::None, state); - if let Err(err) = worker.execute_mod(&filename, false) { - eprintln!("execute_mod err {:?}", err); - } + let worker = Worker::new("TEST".to_string(), StartupData::None, state); + let result = worker.execute_mod(&js_url, false); + let worker = match result { + Err((err, worker)) => { + eprintln!("execute_mod err {:?}", err); + worker + } + Ok(worker) => worker, + }; tokio_util::panic_on_error(worker) })); @@ -372,7 +373,7 @@ mod tests { tokio_util::init(|| { let mut worker = create_test_worker(); js_check( - worker.execute("onmessage = () => { delete window['onmessage']; }"), + worker.execute("onmessage = () => { delete window.onmessage; }"), ); let resource = worker.state.resource.clone(); @@ -400,4 +401,23 @@ mod tests { assert_eq!(resources::get_type(rid), None); }) } + + #[test] + fn execute_mod_resolve_error() { + // "foo" is not a vailid module specifier so this should return an error. + let worker = create_test_worker(); + let js_url = root_specifier_to_url("does-not-exist").unwrap(); + let result = worker.execute_mod_async(&js_url, false).wait(); + assert!(result.is_err()); + } + + #[test] + fn execute_mod_002_hello() { + // This assumes cwd is project root (an assumption made throughout the + // tests). + let worker = create_test_worker(); + let js_url = root_specifier_to_url("./tests/002_hello.ts").unwrap(); + let result = worker.execute_mod_async(&js_url, false).wait(); + assert!(result.is_ok()); + } } |