From 2769d602506af1953312b28580506fca3fcbe030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 8 Jun 2022 12:07:25 +0200 Subject: [PATCH] fix: watch dynamic imports in --watch (#14775) Fix dynamic imports being watched in the watcher when using `--watch`. --- cli/file_watcher.rs | 133 +++++++++++-- cli/main.rs | 111 ++--------- cli/proc_state.rs | 78 +++++++- cli/tests/integration/watcher_tests.rs | 259 +++++++++++++++++-------- 4 files changed, 380 insertions(+), 201 deletions(-) diff --git a/cli/file_watcher.rs b/cli/file_watcher.rs index 3c74237caa..36b4276e77 100644 --- a/cli/file_watcher.rs +++ b/cli/file_watcher.rs @@ -19,6 +19,7 @@ use std::sync::Arc; use std::time::Duration; use tokio::select; use tokio::sync::mpsc; +use tokio::sync::mpsc::UnboundedReceiver; use tokio::time::sleep; const CLEAR_SCREEN: &str = "\x1B[2J\x1B[1;1H"; @@ -114,6 +115,18 @@ pub struct PrintConfig { pub clear_screen: bool, } +fn create_print_after_restart_fn(clear_screen: bool) -> impl Fn() { + move || { + if clear_screen { + eprint!("{}", CLEAR_SCREEN); + } + info!( + "{} File change detected! Restarting!", + colors::intense_blue("Watcher"), + ); + } +} + /// Creates a file watcher, which will call `resolver` with every file change. /// /// - `resolver` is used for resolving file paths to be watched at every restarting @@ -147,15 +160,7 @@ where let mut paths_to_watch; let mut resolution_result; - let print_after_restart = || { - if clear_screen { - eprint!("{}", CLEAR_SCREEN); - } - info!( - "{} File change detected! Restarting!", - colors::intense_blue("Watcher"), - ); - }; + let print_after_restart = create_print_after_restart_fn(clear_screen); match resolver(None).await { ResolutionResult::Ignore => { @@ -188,7 +193,8 @@ where info!("{} {} started.", colors::intense_blue("Watcher"), job_name,); loop { - let watcher = new_watcher(&paths_to_watch, sender.clone())?; + let mut watcher = new_watcher(sender.clone())?; + add_paths_to_watcher(&mut watcher, &paths_to_watch); match resolution_result { Ok(operation_arg) => { @@ -234,8 +240,99 @@ where } } +/// Creates a file watcher. +/// +/// - `operation` is the actual operation we want to run every time the watcher detects file +/// changes. For example, in the case where we would like to bundle, then `operation` would +/// have the logic for it like bundling the code. +pub async fn watch_func2( + mut paths_to_watch_receiver: mpsc::UnboundedReceiver>, + mut operation: O, + operation_args: T, + print_config: PrintConfig, +) -> Result<(), AnyError> +where + O: FnMut(T) -> F, + F: Future>, +{ + let (watcher_sender, mut watcher_receiver) = + DebouncedReceiver::new_with_sender(); + + let PrintConfig { + job_name, + clear_screen, + } = print_config; + + let print_after_restart = create_print_after_restart_fn(clear_screen); + + info!("{} {} started.", colors::intense_blue("Watcher"), job_name,); + + fn consume_paths_to_watch( + watcher: &mut RecommendedWatcher, + receiver: &mut UnboundedReceiver>, + ) { + loop { + match receiver.try_recv() { + Ok(paths) => { + add_paths_to_watcher(watcher, &paths); + } + Err(e) => match e { + mpsc::error::TryRecvError::Empty => { + break; + } + // there must be at least one receiver alive + _ => unreachable!(), + }, + } + } + } + + loop { + let mut watcher = new_watcher(watcher_sender.clone())?; + consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); + + let receiver_future = async { + loop { + let maybe_paths = paths_to_watch_receiver.recv().await; + add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); + } + }; + let operation_future = error_handler(operation(operation_args.clone())); + + select! { + _ = receiver_future => {}, + _ = watcher_receiver.recv() => { + print_after_restart(); + continue; + }, + _ = operation_future => { + // TODO(bartlomieju): print exit code here? + info!( + "{} {} finished. Restarting on file change...", + colors::intense_blue("Watcher"), + job_name, + ); + consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver); + }, + }; + + let receiver_future = async { + loop { + let maybe_paths = paths_to_watch_receiver.recv().await; + add_paths_to_watcher(&mut watcher, &maybe_paths.unwrap()); + } + }; + select! { + _ = receiver_future => {}, + _ = watcher_receiver.recv() => { + print_after_restart(); + continue; + }, + }; + } +} + fn new_watcher( - paths: &[PathBuf], sender: Arc>>, ) -> Result { let mut watcher: RecommendedWatcher = @@ -257,11 +354,13 @@ fn new_watcher( watcher.configure(Config::PreciseEvents(true)).unwrap(); - log::debug!("Watching paths: {:?}", paths); - for path in paths { - // Ignore any error e.g. `PathNotFound` - let _ = watcher.watch(path, RecursiveMode::Recursive); - } - Ok(watcher) } + +fn add_paths_to_watcher(watcher: &mut RecommendedWatcher, paths: &[PathBuf]) { + // Ignore any error e.g. `PathNotFound` + for path in paths { + let _ = watcher.watch(path, RecursiveMode::Recursive); + } + log::debug!("Watching paths: {:?}", paths); +} diff --git a/cli/main.rs b/cli/main.rs index b6c7e97ff6..f9f92359cb 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -995,100 +995,6 @@ async fn run_from_stdin(flags: Flags) -> Result { // TODO(bartlomieju): this function is not handling `exit_code` set by the runtime // code properly. async fn run_with_watch(flags: Flags, script: String) -> Result { - let flags = Arc::new(flags); - let resolver = |_| { - let script1 = script.clone(); - let script2 = script.clone(); - let flags = flags.clone(); - let watch_flag = flags.watch.clone(); - async move { - let main_module = resolve_url_or_path(&script1)?; - let ps = ProcState::build(flags).await?; - let mut cache = cache::FetchCacher::new( - ps.dir.gen_cache.clone(), - ps.file_fetcher.clone(), - Permissions::allow_all(), - Permissions::allow_all(), - ); - let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone()); - let maybe_imports = if let Some(config_file) = &ps.maybe_config_file { - config_file.to_maybe_imports()? - } else { - None - }; - let maybe_import_map_resolver = - ps.maybe_import_map.clone().map(ImportMapResolver::new); - let maybe_jsx_resolver = ps.maybe_config_file.as_ref().and_then(|cf| { - cf.to_maybe_jsx_import_source_module() - .map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone())) - }); - let maybe_resolver = if maybe_jsx_resolver.is_some() { - maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver()) - } else { - maybe_import_map_resolver - .as_ref() - .map(|im| im.as_resolver()) - }; - let graph = deno_graph::create_graph( - vec![(main_module.clone(), deno_graph::ModuleKind::Esm)], - false, - maybe_imports, - &mut cache, - maybe_resolver, - maybe_locker, - None, - None, - ) - .await; - let check_js = ps - .maybe_config_file - .as_ref() - .map(|cf| cf.get_check_js()) - .unwrap_or(false); - graph_valid( - &graph, - ps.flags.type_check_mode != flags::TypeCheckMode::None, - check_js, - )?; - - // Find all local files in graph - let mut paths_to_watch: Vec = graph - .specifiers() - .iter() - .filter_map(|(_, r)| { - r.as_ref().ok().and_then(|(s, _, _)| s.to_file_path().ok()) - }) - .collect(); - - // Add the extra files listed in the watch flag - if let Some(watch_paths) = watch_flag { - paths_to_watch.extend(watch_paths); - } - - if let Ok(Some(import_map_path)) = - config_file::resolve_import_map_specifier( - ps.flags.import_map_path.as_deref(), - ps.maybe_config_file.as_ref(), - ) - .map(|ms| ms.and_then(|ref s| s.to_file_path().ok())) - { - paths_to_watch.push(import_map_path); - } - - Ok((paths_to_watch, main_module, ps)) - } - .map(move |result| match result { - Ok((paths_to_watch, module_info, ps)) => ResolutionResult::Restart { - paths_to_watch, - result: Ok((ps, module_info)), - }, - Err(e) => ResolutionResult::Restart { - paths_to_watch: vec![PathBuf::from(script2)], - result: Err(e), - }, - }) - }; - /// 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. @@ -1144,10 +1050,19 @@ async fn run_with_watch(flags: Flags, script: String) -> Result { } } - let operation = |(ps, main_module): (ProcState, ModuleSpecifier)| { + let flags = Arc::new(flags); + let main_module = resolve_url_or_path(&script)?; + let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); + + let operation = |(sender, main_module): ( + tokio::sync::mpsc::UnboundedSender>, + ModuleSpecifier, + )| { let flags = flags.clone(); let permissions = Permissions::from_options(&flags.permissions_options()); async move { + let ps = ProcState::build_for_file_watcher(flags.clone(), sender.clone()) + .await?; // We make use an module executor guard to ensure that unload is always fired when an // operation is called. let mut executor = FileWatcherModuleExecutor::new( @@ -1167,15 +1082,17 @@ async fn run_with_watch(flags: Flags, script: String) -> Result { } }; - file_watcher::watch_func( - resolver, + file_watcher::watch_func2( + receiver, operation, + (sender, main_module), file_watcher::PrintConfig { job_name: "Process".to_string(), clear_screen: !flags.no_clear_screen, }, ) .await?; + Ok(0) } diff --git a/cli/proc_state.rs b/cli/proc_state.rs index 1606975d0e..d90b3f9526 100644 --- a/cli/proc_state.rs +++ b/cli/proc_state.rs @@ -4,6 +4,7 @@ use crate::cache; use crate::colors; use crate::compat; use crate::compat::NodeEsmResolver; +use crate::config_file; use crate::config_file::ConfigFile; use crate::config_file::MaybeImportsResult; use crate::deno_dir; @@ -56,6 +57,7 @@ use log::warn; use std::collections::HashSet; use std::env; use std::ops::Deref; +use std::path::PathBuf; use std::sync::Arc; /// This structure represents state of single "deno" program. @@ -81,6 +83,7 @@ pub struct Inner { pub shared_array_buffer_store: SharedArrayBufferStore, pub compiled_wasm_module_store: CompiledWasmModuleStore, maybe_resolver: Option>, + maybe_file_watcher_reporter: Option, } impl Deref for ProcState { @@ -92,6 +95,41 @@ impl Deref for ProcState { impl ProcState { pub async fn build(flags: Arc) -> Result { + Self::build_with_sender(flags, None).await + } + + pub async fn build_for_file_watcher( + flags: Arc, + files_to_watch_sender: tokio::sync::mpsc::UnboundedSender>, + ) -> Result { + let ps = Self::build_with_sender( + flags.clone(), + Some(files_to_watch_sender.clone()), + ) + .await?; + + // Add the extra files listed in the watch flag + if let Some(watch_paths) = &flags.watch { + files_to_watch_sender.send(watch_paths.clone()).unwrap(); + } + + if let Ok(Some(import_map_path)) = + config_file::resolve_import_map_specifier( + ps.flags.import_map_path.as_deref(), + ps.maybe_config_file.as_ref(), + ) + .map(|ms| ms.and_then(|ref s| s.to_file_path().ok())) + { + files_to_watch_sender.send(vec![import_map_path]).unwrap(); + } + + Ok(ps) + } + + async fn build_with_sender( + flags: Arc, + maybe_sender: Option>>, + ) -> Result { let maybe_custom_root = flags .cache_path .clone() @@ -209,6 +247,12 @@ impl ProcState { None }; + let maybe_file_watcher_reporter = + maybe_sender.map(|sender| FileWatcherReporter { + sender, + file_paths: Arc::new(Mutex::new(vec![])), + }); + Ok(ProcState(Arc::new(Inner { dir, coverage_dir, @@ -225,6 +269,7 @@ impl ProcState { shared_array_buffer_store, compiled_wasm_module_store, maybe_resolver, + maybe_file_watcher_reporter, }))) } @@ -358,6 +403,13 @@ impl ProcState { reload: reload_on_watch, }; + let maybe_file_watcher_reporter: Option<&dyn deno_graph::source::Reporter> = + if let Some(reporter) = &self.maybe_file_watcher_reporter { + Some(reporter) + } else { + None + }; + let graph = create_graph( roots.clone(), is_dynamic, @@ -366,7 +418,7 @@ impl ProcState { maybe_resolver, maybe_locker, None, - None, + maybe_file_watcher_reporter, ) .await; @@ -719,3 +771,27 @@ fn source_map_from_code(code: String) -> Option> { None } } + +#[derive(Debug)] +struct FileWatcherReporter { + sender: tokio::sync::mpsc::UnboundedSender>, + file_paths: Arc>>, +} + +impl deno_graph::source::Reporter for FileWatcherReporter { + fn on_load( + &self, + specifier: &ModuleSpecifier, + modules_done: usize, + modules_total: usize, + ) { + let mut file_paths = self.file_paths.lock(); + if specifier.scheme() == "file" { + file_paths.push(specifier.to_file_path().unwrap()); + } + + if modules_done == modules_total { + self.sender.send(file_paths.drain(..).collect()).unwrap(); + } + } +} diff --git a/cli/tests/integration/watcher_tests.rs b/cli/tests/integration/watcher_tests.rs index a74b7a6d33..ec2588911b 100644 --- a/cli/tests/integration/watcher_tests.rs +++ b/cli/tests/integration/watcher_tests.rs @@ -48,15 +48,22 @@ fn read_all_lints(stderr_lines: &mut impl Iterator) -> String { str } -fn wait_for(s: &str, lines: &mut impl Iterator) { +fn wait_for( + condition: impl Fn(&str) -> bool, + lines: &mut impl Iterator, +) { loop { let msg = lines.next().unwrap(); - if msg.contains(s) { + if condition(&msg) { break; } } } +fn wait_contains(s: &str, lines: &mut impl Iterator) { + wait_for(|msg| msg.contains(s), lines) +} + fn read_line(s: &str, lines: &mut impl Iterator) -> String { lines.find(|m| m.contains(s)).unwrap() } @@ -408,7 +415,7 @@ fn bundle_js_watch() { assert_contains!(stderr_lines.next().unwrap(), "mod6.bundle.js"); let file = PathBuf::from(&bundle); assert!(file.is_file()); - wait_for("Bundle finished", &mut stderr_lines); + wait_contains("Bundle finished", &mut stderr_lines); write(&file_to_watch, "console.log('Hello world2');").unwrap(); @@ -420,14 +427,14 @@ fn bundle_js_watch() { assert_contains!(stderr_lines.next().unwrap(), "mod6.bundle.js"); let file = PathBuf::from(&bundle); assert!(file.is_file()); - wait_for("Bundle finished", &mut stderr_lines); + wait_contains("Bundle finished", &mut stderr_lines); // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax write(&file_to_watch, "syntax error ^^").unwrap(); assert_contains!(stderr_lines.next().unwrap(), "File change detected!"); assert_contains!(stderr_lines.next().unwrap(), "error: "); - wait_for("Bundle failed", &mut stderr_lines); + wait_contains("Bundle failed", &mut stderr_lines); check_alive_then_kill(deno); } @@ -470,15 +477,15 @@ fn bundle_watch_not_exit() { assert_contains!(stderr_lines.next().unwrap(), "file_to_watch.ts"); assert_contains!(stderr_lines.next().unwrap(), "target.js"); - wait_for("Bundle finished", &mut stderr_lines); + wait_contains("Bundle finished", &mut stderr_lines); // bundled file is created assert!(target_file.is_file()); check_alive_then_kill(deno); } -#[flaky_test::flaky_test] -fn run_watch() { +#[test] +fn run_watch_no_dynamic() { let t = TempDir::new(); let file_to_watch = t.path().join("file_to_watch.js"); write(&file_to_watch, "console.log('Hello world');").unwrap(); @@ -488,6 +495,8 @@ fn run_watch() { .arg("run") .arg("--watch") .arg("--unstable") + .arg("-L") + .arg("debug") .arg(&file_to_watch) .env("NO_COLOR", "1") .stdout(std::process::Stdio::piped()) @@ -496,15 +505,21 @@ fn run_watch() { .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - assert_contains!(stdout_lines.next().unwrap(), "Hello world"); - wait_for("Process finished", &mut stderr_lines); + wait_contains("Hello world", &mut stdout_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), + &mut stderr_lines, + ); // Change content of the file write(&file_to_watch, "console.log('Hello world2');").unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - assert_contains!(stdout_lines.next().unwrap(), "Hello world2"); - wait_for("Process finished", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines); + wait_contains("Hello world2", &mut stdout_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), + &mut stderr_lines, + ); // Add dependency let another_file = t.path().join("another_file.js"); @@ -515,23 +530,32 @@ fn run_watch() { ) .unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - assert_contains!(stdout_lines.next().unwrap(), '0'); - wait_for("Process finished", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines); + wait_contains("0", &mut stdout_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("another_file.js"), + &mut stderr_lines, + ); // Confirm that restarting occurs when a new file is updated write(&another_file, "export const foo = 42;").unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - assert_contains!(stdout_lines.next().unwrap(), "42"); - wait_for("Process finished", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines); + wait_contains("42", &mut stdout_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), + &mut stderr_lines, + ); // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax write(&file_to_watch, "syntax error ^^").unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - assert_contains!(stderr_lines.next().unwrap(), "error:"); - wait_for("Process failed", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines); + wait_contains("error:", &mut stderr_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), + &mut stderr_lines, + ); // Then restore the file write( @@ -540,23 +564,29 @@ fn run_watch() { ) .unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - assert_contains!(stdout_lines.next().unwrap(), "42"); - wait_for("Process finished", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines); + wait_contains("42", &mut stdout_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("another_file.js"), + &mut stderr_lines, + ); // Update the content of the imported file with invalid syntax write(&another_file, "syntax error ^^").unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - assert_contains!(stderr_lines.next().unwrap(), "error:"); - wait_for("Process failed", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines); + wait_contains("error:", &mut stderr_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("another_file.js"), + &mut stderr_lines, + ); // Modify the imported file and make sure that restarting occurs write(&another_file, "export const foo = 'modified!';").unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - assert_contains!(stdout_lines.next().unwrap(), "modified!"); - wait_for("Process finished", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines); + wait_contains("modified!", &mut stdout_lines); + wait_contains("Watching paths", &mut stderr_lines); check_alive_then_kill(child); } @@ -581,6 +611,8 @@ fn run_watch_external_watch_files() { .current_dir(util::testdata_path()) .arg("run") .arg(watch_arg) + .arg("-L") + .arg("debug") .arg("--unstable") .arg(&file_to_watch) .env("NO_COLOR", "1") @@ -589,15 +621,20 @@ fn run_watch_external_watch_files() { .spawn() .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - - assert_contains!(stdout_lines.next().unwrap(), "Hello world"); - wait_for("Process finished", &mut stderr_lines); + wait_contains("Process started", &mut stderr_lines); + wait_contains("Hello world", &mut stdout_lines); + wait_for( + |m| { + m.contains("Watching paths") && m.contains("external_file_to_watch.txt") + }, + &mut stderr_lines, + ); // Change content of the external file write(&external_file_to_watch, "Hello world2").unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - wait_for("Process finished", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines); + wait_contains("Process finished", &mut stderr_lines); check_alive_then_kill(child); } @@ -625,6 +662,8 @@ fn run_watch_load_unload_events() { .arg("run") .arg("--watch") .arg("--unstable") + .arg("-L") + .arg("debug") .arg(&file_to_watch) .env("NO_COLOR", "1") .env("DENO_FUTURE_CHECK", "1") @@ -635,7 +674,11 @@ fn run_watch_load_unload_events() { let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); // Wait for the first load event to fire - assert_contains!(stdout_lines.next().unwrap(), "load"); + wait_contains("load", &mut stdout_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), + &mut stderr_lines, + ); // Change content of the file, this time without an interval to keep it alive. write( @@ -653,18 +696,16 @@ fn run_watch_load_unload_events() { .unwrap(); // Wait for the restart - let next_line = stderr_lines.next().unwrap(); - assert_contains!(&next_line, "Process started"); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); + wait_contains("Restarting", &mut stderr_lines); // Confirm that the unload event was dispatched from the first run - assert_contains!(stdout_lines.next().unwrap(), "unload"); + wait_contains("unload", &mut stdout_lines); // Followed by the load event of the second run - assert_contains!(stdout_lines.next().unwrap(), "load"); + wait_contains("load", &mut stdout_lines); // Which is then unloaded as there is nothing keeping it alive. - assert_contains!(stdout_lines.next().unwrap(), "unload"); + wait_contains("unload", &mut stdout_lines); check_alive_then_kill(child); } @@ -680,6 +721,8 @@ fn run_watch_not_exit() { .arg("run") .arg("--watch") .arg("--unstable") + .arg("-L") + .arg("debug") .arg(&file_to_watch) .env("NO_COLOR", "1") .env("DENO_FUTURE_CHECK", "1") @@ -689,19 +732,19 @@ fn run_watch_not_exit() { .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - let next_line = stderr_lines.next().unwrap(); - assert_contains!(&next_line, "Process started"); - assert_contains!(stderr_lines.next().unwrap(), "error:"); - assert_contains!(stderr_lines.next().unwrap(), "Process failed"); + wait_contains("Process started", &mut stderr_lines); + wait_contains("error:", &mut stderr_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), + &mut stderr_lines, + ); // Make sure the watcher actually restarts and works fine with the proper syntax write(&file_to_watch, "console.log(42);").unwrap(); - let next_line = stderr_lines.next().unwrap(); - assert_contains!(&next_line, CLEAR_SCREEN); - assert_contains!(&next_line, "Restarting"); - assert_contains!(stdout_lines.next().unwrap(), "42"); - wait_for("Process finished", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines); + wait_contains("42", &mut stdout_lines); + wait_contains("Process finished", &mut stderr_lines); check_alive_then_kill(child); } @@ -756,7 +799,7 @@ fn run_watch_with_import_map_and_relative_paths() { check_alive_then_kill(child); } -#[flaky_test] +#[test] fn test_watch() { let t = TempDir::new(); @@ -779,7 +822,7 @@ fn test_watch() { stdout_lines.next().unwrap(), "0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out" ); - wait_for("Test finished", &mut stderr_lines); + wait_contains("Test finished", &mut stderr_lines); let foo_file = t.path().join("foo.js"); let bar_file = t.path().join("bar.js"); @@ -806,7 +849,7 @@ fn test_watch() { stdout_lines.next(); stdout_lines.next(); stdout_lines.next(); - wait_for("Test finished", &mut stderr_lines); + wait_contains("Test finished", &mut stderr_lines); // Change content of the file write( @@ -821,7 +864,7 @@ fn test_watch() { stdout_lines.next(); stdout_lines.next(); stdout_lines.next(); - wait_for("Test finished", &mut stderr_lines); + wait_contains("Test finished", &mut stderr_lines); // Add test let another_test = t.path().join("new_test.js"); @@ -832,7 +875,7 @@ fn test_watch() { stdout_lines.next(); stdout_lines.next(); stdout_lines.next(); - wait_for("Test finished", &mut stderr_lines); + wait_contains("Test finished", &mut stderr_lines); // Confirm that restarting occurs when a new file is updated write(&another_test, "Deno.test('another one', () => 3 + 3); Deno.test('another another one', () => 4 + 4)") @@ -844,7 +887,7 @@ fn test_watch() { stdout_lines.next(); stdout_lines.next(); stdout_lines.next(); - wait_for("Test finished", &mut stderr_lines); + wait_contains("Test finished", &mut stderr_lines); // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax write(&another_test, "syntax error ^^").unwrap(); @@ -860,7 +903,7 @@ fn test_watch() { stdout_lines.next(); stdout_lines.next(); stdout_lines.next(); - wait_for("Test finished", &mut stderr_lines); + wait_contains("Test finished", &mut stderr_lines); // Confirm that the watcher keeps on working even if the file is updated and the test fails // This also confirms that it restarts when dependencies change @@ -872,9 +915,9 @@ fn test_watch() { assert_contains!(stderr_lines.next().unwrap(), "Restarting"); assert_contains!(stdout_lines.next().unwrap(), "running 1 test"); assert_contains!(stdout_lines.next().unwrap(), "FAILED"); - wait_for("test result", &mut stdout_lines); + wait_contains("test result", &mut stdout_lines); stdout_lines.next(); - wait_for("Test finished", &mut stderr_lines); + wait_contains("Test finished", &mut stderr_lines); // Then restore the file write(&foo_file, "export default function foo() { 1 + 1 }").unwrap(); @@ -884,7 +927,7 @@ fn test_watch() { stdout_lines.next(); stdout_lines.next(); stdout_lines.next(); - wait_for("Test finished", &mut stderr_lines); + wait_contains("Test finished", &mut stderr_lines); // Test that circular dependencies work fine write( @@ -923,7 +966,7 @@ fn test_watch_doc() { stdout_lines.next().unwrap(), "0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out" ); - wait_for("Test finished", &mut stderr_lines); + wait_contains("Test finished", &mut stderr_lines); let foo_file = t.path().join("foo.ts"); write( @@ -979,23 +1022,48 @@ fn test_watch_module_graph_error_referrer() { let line3 = stderr_lines.next().unwrap(); assert_contains!(&line3, " at "); assert_contains!(&line3, "file_to_watch.js"); - wait_for("Process failed", &mut stderr_lines); + wait_contains("Process finished", &mut stderr_lines); check_alive_then_kill(child); } #[test] -fn watch_with_no_clear_screen_flag() { +fn run_watch_dynamic_imports() { let t = TempDir::new(); let file_to_watch = t.path().join("file_to_watch.js"); - write(&file_to_watch, "export const foo = 0;").unwrap(); + write( + &file_to_watch, + r#" + console.log("Hopefully dynamic import will be watched..."); + await import("./imported.js"); + "#, + ) + .unwrap(); + let file_to_watch2 = t.path().join("imported.js"); + write( + &file_to_watch2, + r#" + import "./imported2.js"; + console.log("I'm dynamically imported and I cause restarts!"); + "#, + ) + .unwrap(); + let file_to_watch3 = t.path().join("imported2.js"); + write( + &file_to_watch3, + r#" + console.log("I'm statically imported from the dynamic import"); + "#, + ) + .unwrap(); - // choose deno run subcommand to test --no-clear-screen flag let mut child = util::deno_cmd() .current_dir(util::testdata_path()) .arg("run") .arg("--watch") - .arg("--no-clear-screen") .arg("--unstable") + .arg("--allow-read") + .arg("-L") + .arg("debug") .arg(&file_to_watch) .env("NO_COLOR", "1") .env("DENO_FUTURE_CHECK", "1") @@ -1003,30 +1071,49 @@ fn watch_with_no_clear_screen_flag() { .stderr(std::process::Stdio::piped()) .spawn() .unwrap(); - let (_, mut stderr_lines) = child_lines(&mut child); + let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - let next_line = stderr_lines.next().unwrap(); + assert_contains!(stderr_lines.next().unwrap(), "Process started"); - // no clear screen - assert!(!&next_line.contains(CLEAR_SCREEN)); - assert_contains!(&next_line, "Process started"); - assert_contains!( - stderr_lines.next().unwrap(), - "Process finished. Restarting on file change..." + wait_contains( + "Hopefully dynamic import will be watched...", + &mut stdout_lines, + ); + wait_contains( + "I'm statically imported from the dynamic import", + &mut stdout_lines, + ); + wait_contains( + "I'm dynamically imported and I cause restarts!", + &mut stdout_lines, ); - // Change content of the file - write(&file_to_watch, "export const bar = 0;").unwrap(); + wait_contains("finished", &mut stderr_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("imported2.js"), + &mut stderr_lines, + ); - let next_line = stderr_lines.next().unwrap(); + write( + &file_to_watch3, + r#" + console.log("I'm statically imported from the dynamic import and I've changed"); + "#, + ) + .unwrap(); - // no clear screen - assert!(!&next_line.contains(CLEAR_SCREEN)); - - assert_contains!(&next_line, "Watcher File change detected! Restarting!"); - assert_contains!( - stderr_lines.next().unwrap(), - "Process finished. Restarting on file change..." + wait_contains("Restarting", &mut stderr_lines); + wait_contains( + "Hopefully dynamic import will be watched...", + &mut stdout_lines, + ); + wait_contains( + "I'm statically imported from the dynamic import and I've changed", + &mut stdout_lines, + ); + wait_contains( + "I'm dynamically imported and I cause restarts!", + &mut stdout_lines, ); check_alive_then_kill(child);