0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-01 20:25:12 -05:00

fix: watch dynamic imports in --watch (#14775)

Fix dynamic imports being watched in the watcher when using `--watch`.
This commit is contained in:
Bartek Iwańczuk 2022-06-08 12:07:25 +02:00 committed by GitHub
parent ff5def9ed5
commit 2769d60250
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 380 additions and 201 deletions

View file

@ -19,6 +19,7 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::select; use tokio::select;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::sync::mpsc::UnboundedReceiver;
use tokio::time::sleep; use tokio::time::sleep;
const CLEAR_SCREEN: &str = "\x1B[2J\x1B[1;1H"; const CLEAR_SCREEN: &str = "\x1B[2J\x1B[1;1H";
@ -114,6 +115,18 @@ pub struct PrintConfig {
pub clear_screen: bool, 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. /// 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 /// - `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 paths_to_watch;
let mut resolution_result; let mut resolution_result;
let print_after_restart = || { let print_after_restart = create_print_after_restart_fn(clear_screen);
if clear_screen {
eprint!("{}", CLEAR_SCREEN);
}
info!(
"{} File change detected! Restarting!",
colors::intense_blue("Watcher"),
);
};
match resolver(None).await { match resolver(None).await {
ResolutionResult::Ignore => { ResolutionResult::Ignore => {
@ -188,7 +193,8 @@ where
info!("{} {} started.", colors::intense_blue("Watcher"), job_name,); info!("{} {} started.", colors::intense_blue("Watcher"), job_name,);
loop { 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 { match resolution_result {
Ok(operation_arg) => { 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<T: Clone, O, F>(
mut paths_to_watch_receiver: mpsc::UnboundedReceiver<Vec<PathBuf>>,
mut operation: O,
operation_args: T,
print_config: PrintConfig,
) -> Result<(), AnyError>
where
O: FnMut(T) -> F,
F: Future<Output = Result<(), AnyError>>,
{
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<Vec<PathBuf>>,
) {
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( fn new_watcher(
paths: &[PathBuf],
sender: Arc<mpsc::UnboundedSender<Vec<PathBuf>>>, sender: Arc<mpsc::UnboundedSender<Vec<PathBuf>>>,
) -> Result<RecommendedWatcher, AnyError> { ) -> Result<RecommendedWatcher, AnyError> {
let mut watcher: RecommendedWatcher = let mut watcher: RecommendedWatcher =
@ -257,11 +354,13 @@ fn new_watcher(
watcher.configure(Config::PreciseEvents(true)).unwrap(); 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) 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);
}

View file

@ -995,100 +995,6 @@ async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> {
// TODO(bartlomieju): this function is not handling `exit_code` set by the runtime // TODO(bartlomieju): this function is not handling `exit_code` set by the runtime
// code properly. // code properly.
async fn run_with_watch(flags: Flags, script: String) -> Result<i32, AnyError> { async fn run_with_watch(flags: Flags, script: String) -> Result<i32, AnyError> {
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<PathBuf> = 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 /// 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 /// state of any pending events and emitting accordingly on drop in the case of a future
/// cancellation. /// cancellation.
@ -1144,10 +1050,19 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<i32, AnyError> {
} }
} }
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<Vec<PathBuf>>,
ModuleSpecifier,
)| {
let flags = flags.clone(); let flags = flags.clone();
let permissions = Permissions::from_options(&flags.permissions_options()); let permissions = Permissions::from_options(&flags.permissions_options());
async move { 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 // We make use an module executor guard to ensure that unload is always fired when an
// operation is called. // operation is called.
let mut executor = FileWatcherModuleExecutor::new( let mut executor = FileWatcherModuleExecutor::new(
@ -1167,15 +1082,17 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<i32, AnyError> {
} }
}; };
file_watcher::watch_func( file_watcher::watch_func2(
resolver, receiver,
operation, operation,
(sender, main_module),
file_watcher::PrintConfig { file_watcher::PrintConfig {
job_name: "Process".to_string(), job_name: "Process".to_string(),
clear_screen: !flags.no_clear_screen, clear_screen: !flags.no_clear_screen,
}, },
) )
.await?; .await?;
Ok(0) Ok(0)
} }

View file

@ -4,6 +4,7 @@ use crate::cache;
use crate::colors; use crate::colors;
use crate::compat; use crate::compat;
use crate::compat::NodeEsmResolver; use crate::compat::NodeEsmResolver;
use crate::config_file;
use crate::config_file::ConfigFile; use crate::config_file::ConfigFile;
use crate::config_file::MaybeImportsResult; use crate::config_file::MaybeImportsResult;
use crate::deno_dir; use crate::deno_dir;
@ -56,6 +57,7 @@ use log::warn;
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
use std::ops::Deref; use std::ops::Deref;
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
/// This structure represents state of single "deno" program. /// This structure represents state of single "deno" program.
@ -81,6 +83,7 @@ pub struct Inner {
pub shared_array_buffer_store: SharedArrayBufferStore, pub shared_array_buffer_store: SharedArrayBufferStore,
pub compiled_wasm_module_store: CompiledWasmModuleStore, pub compiled_wasm_module_store: CompiledWasmModuleStore,
maybe_resolver: Option<Arc<dyn deno_graph::source::Resolver + Send + Sync>>, maybe_resolver: Option<Arc<dyn deno_graph::source::Resolver + Send + Sync>>,
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
} }
impl Deref for ProcState { impl Deref for ProcState {
@ -92,6 +95,41 @@ impl Deref for ProcState {
impl ProcState { impl ProcState {
pub async fn build(flags: Arc<flags::Flags>) -> Result<Self, AnyError> { pub async fn build(flags: Arc<flags::Flags>) -> Result<Self, AnyError> {
Self::build_with_sender(flags, None).await
}
pub async fn build_for_file_watcher(
flags: Arc<flags::Flags>,
files_to_watch_sender: tokio::sync::mpsc::UnboundedSender<Vec<PathBuf>>,
) -> Result<Self, AnyError> {
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<flags::Flags>,
maybe_sender: Option<tokio::sync::mpsc::UnboundedSender<Vec<PathBuf>>>,
) -> Result<Self, AnyError> {
let maybe_custom_root = flags let maybe_custom_root = flags
.cache_path .cache_path
.clone() .clone()
@ -209,6 +247,12 @@ impl ProcState {
None None
}; };
let maybe_file_watcher_reporter =
maybe_sender.map(|sender| FileWatcherReporter {
sender,
file_paths: Arc::new(Mutex::new(vec![])),
});
Ok(ProcState(Arc::new(Inner { Ok(ProcState(Arc::new(Inner {
dir, dir,
coverage_dir, coverage_dir,
@ -225,6 +269,7 @@ impl ProcState {
shared_array_buffer_store, shared_array_buffer_store,
compiled_wasm_module_store, compiled_wasm_module_store,
maybe_resolver, maybe_resolver,
maybe_file_watcher_reporter,
}))) })))
} }
@ -358,6 +403,13 @@ impl ProcState {
reload: reload_on_watch, 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( let graph = create_graph(
roots.clone(), roots.clone(),
is_dynamic, is_dynamic,
@ -366,7 +418,7 @@ impl ProcState {
maybe_resolver, maybe_resolver,
maybe_locker, maybe_locker,
None, None,
None, maybe_file_watcher_reporter,
) )
.await; .await;
@ -719,3 +771,27 @@ fn source_map_from_code(code: String) -> Option<Vec<u8>> {
None None
} }
} }
#[derive(Debug)]
struct FileWatcherReporter {
sender: tokio::sync::mpsc::UnboundedSender<Vec<PathBuf>>,
file_paths: Arc<Mutex<Vec<PathBuf>>>,
}
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();
}
}
}

View file

@ -48,15 +48,22 @@ fn read_all_lints(stderr_lines: &mut impl Iterator<Item = String>) -> String {
str str
} }
fn wait_for(s: &str, lines: &mut impl Iterator<Item = String>) { fn wait_for(
condition: impl Fn(&str) -> bool,
lines: &mut impl Iterator<Item = String>,
) {
loop { loop {
let msg = lines.next().unwrap(); let msg = lines.next().unwrap();
if msg.contains(s) { if condition(&msg) {
break; break;
} }
} }
} }
fn wait_contains(s: &str, lines: &mut impl Iterator<Item = String>) {
wait_for(|msg| msg.contains(s), lines)
}
fn read_line(s: &str, lines: &mut impl Iterator<Item = String>) -> String { fn read_line(s: &str, lines: &mut impl Iterator<Item = String>) -> String {
lines.find(|m| m.contains(s)).unwrap() lines.find(|m| m.contains(s)).unwrap()
} }
@ -408,7 +415,7 @@ fn bundle_js_watch() {
assert_contains!(stderr_lines.next().unwrap(), "mod6.bundle.js"); assert_contains!(stderr_lines.next().unwrap(), "mod6.bundle.js");
let file = PathBuf::from(&bundle); let file = PathBuf::from(&bundle);
assert!(file.is_file()); 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(); 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"); assert_contains!(stderr_lines.next().unwrap(), "mod6.bundle.js");
let file = PathBuf::from(&bundle); let file = PathBuf::from(&bundle);
assert!(file.is_file()); 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 // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax
write(&file_to_watch, "syntax error ^^").unwrap(); write(&file_to_watch, "syntax error ^^").unwrap();
assert_contains!(stderr_lines.next().unwrap(), "File change detected!"); assert_contains!(stderr_lines.next().unwrap(), "File change detected!");
assert_contains!(stderr_lines.next().unwrap(), "error: "); 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); 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(), "file_to_watch.ts");
assert_contains!(stderr_lines.next().unwrap(), "target.js"); 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 // bundled file is created
assert!(target_file.is_file()); assert!(target_file.is_file());
check_alive_then_kill(deno); check_alive_then_kill(deno);
} }
#[flaky_test::flaky_test] #[test]
fn run_watch() { fn run_watch_no_dynamic() {
let t = TempDir::new(); let t = TempDir::new();
let file_to_watch = t.path().join("file_to_watch.js"); let file_to_watch = t.path().join("file_to_watch.js");
write(&file_to_watch, "console.log('Hello world');").unwrap(); write(&file_to_watch, "console.log('Hello world');").unwrap();
@ -488,6 +495,8 @@ fn run_watch() {
.arg("run") .arg("run")
.arg("--watch") .arg("--watch")
.arg("--unstable") .arg("--unstable")
.arg("-L")
.arg("debug")
.arg(&file_to_watch) .arg(&file_to_watch)
.env("NO_COLOR", "1") .env("NO_COLOR", "1")
.stdout(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped())
@ -496,15 +505,21 @@ fn run_watch() {
.unwrap(); .unwrap();
let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child);
assert_contains!(stdout_lines.next().unwrap(), "Hello world"); wait_contains("Hello world", &mut stdout_lines);
wait_for("Process finished", &mut stderr_lines); wait_for(
|m| m.contains("Watching paths") && m.contains("file_to_watch.js"),
&mut stderr_lines,
);
// Change content of the file // Change content of the file
write(&file_to_watch, "console.log('Hello world2');").unwrap(); write(&file_to_watch, "console.log('Hello world2');").unwrap();
assert_contains!(stderr_lines.next().unwrap(), "Restarting"); wait_contains("Restarting", &mut stderr_lines);
assert_contains!(stdout_lines.next().unwrap(), "Hello world2"); wait_contains("Hello world2", &mut stdout_lines);
wait_for("Process finished", &mut stderr_lines); wait_for(
|m| m.contains("Watching paths") && m.contains("file_to_watch.js"),
&mut stderr_lines,
);
// Add dependency // Add dependency
let another_file = t.path().join("another_file.js"); let another_file = t.path().join("another_file.js");
@ -515,23 +530,32 @@ fn run_watch() {
) )
.unwrap(); .unwrap();
assert_contains!(stderr_lines.next().unwrap(), "Restarting"); wait_contains("Restarting", &mut stderr_lines);
assert_contains!(stdout_lines.next().unwrap(), '0'); wait_contains("0", &mut stdout_lines);
wait_for("Process finished", &mut stderr_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 // Confirm that restarting occurs when a new file is updated
write(&another_file, "export const foo = 42;").unwrap(); write(&another_file, "export const foo = 42;").unwrap();
assert_contains!(stderr_lines.next().unwrap(), "Restarting"); wait_contains("Restarting", &mut stderr_lines);
assert_contains!(stdout_lines.next().unwrap(), "42"); wait_contains("42", &mut stdout_lines);
wait_for("Process finished", &mut stderr_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 // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax
write(&file_to_watch, "syntax error ^^").unwrap(); write(&file_to_watch, "syntax error ^^").unwrap();
assert_contains!(stderr_lines.next().unwrap(), "Restarting"); wait_contains("Restarting", &mut stderr_lines);
assert_contains!(stderr_lines.next().unwrap(), "error:"); wait_contains("error:", &mut stderr_lines);
wait_for("Process failed", &mut stderr_lines); wait_for(
|m| m.contains("Watching paths") && m.contains("file_to_watch.js"),
&mut stderr_lines,
);
// Then restore the file // Then restore the file
write( write(
@ -540,23 +564,29 @@ fn run_watch() {
) )
.unwrap(); .unwrap();
assert_contains!(stderr_lines.next().unwrap(), "Restarting"); wait_contains("Restarting", &mut stderr_lines);
assert_contains!(stdout_lines.next().unwrap(), "42"); wait_contains("42", &mut stdout_lines);
wait_for("Process finished", &mut stderr_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 // Update the content of the imported file with invalid syntax
write(&another_file, "syntax error ^^").unwrap(); write(&another_file, "syntax error ^^").unwrap();
assert_contains!(stderr_lines.next().unwrap(), "Restarting"); wait_contains("Restarting", &mut stderr_lines);
assert_contains!(stderr_lines.next().unwrap(), "error:"); wait_contains("error:", &mut stderr_lines);
wait_for("Process failed", &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 // Modify the imported file and make sure that restarting occurs
write(&another_file, "export const foo = 'modified!';").unwrap(); write(&another_file, "export const foo = 'modified!';").unwrap();
assert_contains!(stderr_lines.next().unwrap(), "Restarting"); wait_contains("Restarting", &mut stderr_lines);
assert_contains!(stdout_lines.next().unwrap(), "modified!"); wait_contains("modified!", &mut stdout_lines);
wait_for("Process finished", &mut stderr_lines); wait_contains("Watching paths", &mut stderr_lines);
check_alive_then_kill(child); check_alive_then_kill(child);
} }
@ -581,6 +611,8 @@ fn run_watch_external_watch_files() {
.current_dir(util::testdata_path()) .current_dir(util::testdata_path())
.arg("run") .arg("run")
.arg(watch_arg) .arg(watch_arg)
.arg("-L")
.arg("debug")
.arg("--unstable") .arg("--unstable")
.arg(&file_to_watch) .arg(&file_to_watch)
.env("NO_COLOR", "1") .env("NO_COLOR", "1")
@ -589,15 +621,20 @@ fn run_watch_external_watch_files() {
.spawn() .spawn()
.unwrap(); .unwrap();
let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child);
wait_contains("Process started", &mut stderr_lines);
assert_contains!(stdout_lines.next().unwrap(), "Hello world"); wait_contains("Hello world", &mut stdout_lines);
wait_for("Process finished", &mut stderr_lines); wait_for(
|m| {
m.contains("Watching paths") && m.contains("external_file_to_watch.txt")
},
&mut stderr_lines,
);
// Change content of the external file // Change content of the external file
write(&external_file_to_watch, "Hello world2").unwrap(); write(&external_file_to_watch, "Hello world2").unwrap();
assert_contains!(stderr_lines.next().unwrap(), "Restarting"); wait_contains("Restarting", &mut stderr_lines);
wait_for("Process finished", &mut stderr_lines); wait_contains("Process finished", &mut stderr_lines);
check_alive_then_kill(child); check_alive_then_kill(child);
} }
@ -625,6 +662,8 @@ fn run_watch_load_unload_events() {
.arg("run") .arg("run")
.arg("--watch") .arg("--watch")
.arg("--unstable") .arg("--unstable")
.arg("-L")
.arg("debug")
.arg(&file_to_watch) .arg(&file_to_watch)
.env("NO_COLOR", "1") .env("NO_COLOR", "1")
.env("DENO_FUTURE_CHECK", "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); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child);
// Wait for the first load event to fire // 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. // Change content of the file, this time without an interval to keep it alive.
write( write(
@ -653,18 +696,16 @@ fn run_watch_load_unload_events() {
.unwrap(); .unwrap();
// Wait for the restart // Wait for the restart
let next_line = stderr_lines.next().unwrap(); wait_contains("Restarting", &mut stderr_lines);
assert_contains!(&next_line, "Process started");
assert_contains!(stderr_lines.next().unwrap(), "Restarting");
// Confirm that the unload event was dispatched from the first run // 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 // 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. // 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); check_alive_then_kill(child);
} }
@ -680,6 +721,8 @@ fn run_watch_not_exit() {
.arg("run") .arg("run")
.arg("--watch") .arg("--watch")
.arg("--unstable") .arg("--unstable")
.arg("-L")
.arg("debug")
.arg(&file_to_watch) .arg(&file_to_watch)
.env("NO_COLOR", "1") .env("NO_COLOR", "1")
.env("DENO_FUTURE_CHECK", "1") .env("DENO_FUTURE_CHECK", "1")
@ -689,19 +732,19 @@ fn run_watch_not_exit() {
.unwrap(); .unwrap();
let (mut stdout_lines, 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(); wait_contains("Process started", &mut stderr_lines);
assert_contains!(&next_line, "Process started"); wait_contains("error:", &mut stderr_lines);
assert_contains!(stderr_lines.next().unwrap(), "error:"); wait_for(
assert_contains!(stderr_lines.next().unwrap(), "Process failed"); |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 // Make sure the watcher actually restarts and works fine with the proper syntax
write(&file_to_watch, "console.log(42);").unwrap(); write(&file_to_watch, "console.log(42);").unwrap();
let next_line = stderr_lines.next().unwrap(); wait_contains("Restarting", &mut stderr_lines);
assert_contains!(&next_line, CLEAR_SCREEN); wait_contains("42", &mut stdout_lines);
assert_contains!(&next_line, "Restarting"); wait_contains("Process finished", &mut stderr_lines);
assert_contains!(stdout_lines.next().unwrap(), "42");
wait_for("Process finished", &mut stderr_lines);
check_alive_then_kill(child); check_alive_then_kill(child);
} }
@ -756,7 +799,7 @@ fn run_watch_with_import_map_and_relative_paths() {
check_alive_then_kill(child); check_alive_then_kill(child);
} }
#[flaky_test] #[test]
fn test_watch() { fn test_watch() {
let t = TempDir::new(); let t = TempDir::new();
@ -779,7 +822,7 @@ fn test_watch() {
stdout_lines.next().unwrap(), stdout_lines.next().unwrap(),
"0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out" "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 foo_file = t.path().join("foo.js");
let bar_file = t.path().join("bar.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(); 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 // Change content of the file
write( write(
@ -821,7 +864,7 @@ fn test_watch() {
stdout_lines.next(); stdout_lines.next();
stdout_lines.next(); 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 // Add test
let another_test = t.path().join("new_test.js"); 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(); 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 // 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)") 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(); 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 // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax
write(&another_test, "syntax error ^^").unwrap(); write(&another_test, "syntax error ^^").unwrap();
@ -860,7 +903,7 @@ fn test_watch() {
stdout_lines.next(); stdout_lines.next();
stdout_lines.next(); 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 // 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 // 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!(stderr_lines.next().unwrap(), "Restarting");
assert_contains!(stdout_lines.next().unwrap(), "running 1 test"); assert_contains!(stdout_lines.next().unwrap(), "running 1 test");
assert_contains!(stdout_lines.next().unwrap(), "FAILED"); assert_contains!(stdout_lines.next().unwrap(), "FAILED");
wait_for("test result", &mut stdout_lines); wait_contains("test result", &mut stdout_lines);
stdout_lines.next(); stdout_lines.next();
wait_for("Test finished", &mut stderr_lines); wait_contains("Test finished", &mut stderr_lines);
// Then restore the file // Then restore the file
write(&foo_file, "export default function foo() { 1 + 1 }").unwrap(); 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(); 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 // Test that circular dependencies work fine
write( write(
@ -923,7 +966,7 @@ fn test_watch_doc() {
stdout_lines.next().unwrap(), stdout_lines.next().unwrap(),
"0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out" "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"); let foo_file = t.path().join("foo.ts");
write( write(
@ -979,23 +1022,48 @@ fn test_watch_module_graph_error_referrer() {
let line3 = stderr_lines.next().unwrap(); let line3 = stderr_lines.next().unwrap();
assert_contains!(&line3, " at "); assert_contains!(&line3, " at ");
assert_contains!(&line3, "file_to_watch.js"); 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); check_alive_then_kill(child);
} }
#[test] #[test]
fn watch_with_no_clear_screen_flag() { fn run_watch_dynamic_imports() {
let t = TempDir::new(); let t = TempDir::new();
let file_to_watch = t.path().join("file_to_watch.js"); 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() let mut child = util::deno_cmd()
.current_dir(util::testdata_path()) .current_dir(util::testdata_path())
.arg("run") .arg("run")
.arg("--watch") .arg("--watch")
.arg("--no-clear-screen")
.arg("--unstable") .arg("--unstable")
.arg("--allow-read")
.arg("-L")
.arg("debug")
.arg(&file_to_watch) .arg(&file_to_watch)
.env("NO_COLOR", "1") .env("NO_COLOR", "1")
.env("DENO_FUTURE_CHECK", "1") .env("DENO_FUTURE_CHECK", "1")
@ -1003,30 +1071,49 @@ fn watch_with_no_clear_screen_flag() {
.stderr(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped())
.spawn() .spawn()
.unwrap(); .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 wait_contains(
assert!(!&next_line.contains(CLEAR_SCREEN)); "Hopefully dynamic import will be watched...",
assert_contains!(&next_line, "Process started"); &mut stdout_lines,
assert_contains!( );
stderr_lines.next().unwrap(), wait_contains(
"Process finished. Restarting on file change..." "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 wait_contains("finished", &mut stderr_lines);
write(&file_to_watch, "export const bar = 0;").unwrap(); 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 wait_contains("Restarting", &mut stderr_lines);
assert!(!&next_line.contains(CLEAR_SCREEN)); wait_contains(
"Hopefully dynamic import will be watched...",
assert_contains!(&next_line, "Watcher File change detected! Restarting!"); &mut stdout_lines,
assert_contains!( );
stderr_lines.next().unwrap(), wait_contains(
"Process finished. Restarting on file change..." "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); check_alive_then_kill(child);