mirror of
https://github.com/denoland/deno.git
synced 2024-12-26 00:59:24 -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:
parent
ff5def9ed5
commit
2769d60250
4 changed files with 380 additions and 201 deletions
|
@ -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);
|
||||
}
|
||||
|
|
111
cli/main.rs
111
cli/main.rs
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue