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 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<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(
paths: &[PathBuf],
sender: Arc<mpsc::UnboundedSender<Vec<PathBuf>>>,
) -> Result<RecommendedWatcher, AnyError> {
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);
}

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
// code properly.
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
/// 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<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 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<i32, AnyError> {
}
};
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)
}

View File

@ -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<Arc<dyn deno_graph::source::Resolver + Send + Sync>>,
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
}
impl Deref for ProcState {
@ -92,6 +95,41 @@ impl Deref for ProcState {
impl ProcState {
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
.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<Vec<u8>> {
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
}
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 {
let msg = lines.next().unwrap();
if msg.contains(s) {
if condition(&msg) {
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 {
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);