From bad4b7554bd499975170f7d4e1a30540783aea69 Mon Sep 17 00:00:00 2001 From: Cre3per <12541974+Cre3per@users.noreply.github.com> Date: Mon, 17 Apr 2023 15:49:03 +0200 Subject: [PATCH] refactor(tests): Watcher test timeout (#18459) Closes #17438 --- cli/tests/integration/watcher_tests.rs | 762 +++++++++++++++---------- 1 file changed, 475 insertions(+), 287 deletions(-) diff --git a/cli/tests/integration/watcher_tests.rs b/cli/tests/integration/watcher_tests.rs index 407a63f6c4..04320060b2 100644 --- a/cli/tests/integration/watcher_tests.rs +++ b/cli/tests/integration/watcher_tests.rs @@ -2,32 +2,60 @@ use flaky_test::flaky_test; use std::fs::write; -use std::io::BufRead; use test_util as util; use test_util::assert_contains; use test_util::TempDir; +use tokio::io::AsyncBufReadExt; use util::DenoChild; use util::assert_not_contains; const CLEAR_SCREEN: &str = r#"[2J"#; +/// Logs to stderr every time next_line() is called +struct LoggingLines +where + R: tokio::io::AsyncBufRead + Unpin, +{ + pub lines: tokio::io::Lines, + pub stream_name: String, +} + +impl LoggingLines +where + R: tokio::io::AsyncBufRead + Unpin, +{ + pub async fn next_line(&mut self) -> tokio::io::Result> { + let line = self.lines.next_line().await; + eprintln!( + "{}: {}", + self.stream_name, + line.as_ref().unwrap().clone().unwrap() + ); + line + } +} + // Helper function to skip watcher output that contains "Restarting" // phrase. -fn skip_restarting_line( - stderr_lines: &mut impl Iterator, -) -> String { +async fn skip_restarting_line(stderr_lines: &mut LoggingLines) -> String +where + R: tokio::io::AsyncBufRead + Unpin, +{ loop { - let msg = stderr_lines.next().unwrap(); + let msg = next_line(stderr_lines).await.unwrap(); if !msg.contains("Restarting") { return msg; } } } -fn read_all_lints(stderr_lines: &mut impl Iterator) -> String { +async fn read_all_lints(stderr_lines: &mut LoggingLines) -> String +where + R: tokio::io::AsyncBufRead + Unpin, +{ let mut str = String::new(); - for t in stderr_lines { + while let Some(t) = next_line(stderr_lines).await { let t = util::strip_ansi_codes(&t); if t.starts_with("Watcher File change detected") { continue; @@ -43,20 +71,56 @@ fn read_all_lints(stderr_lines: &mut impl Iterator) -> String { str } -fn wait_for( - condition: impl Fn(&str) -> bool, - lines: &mut impl Iterator, -) { - loop { - let msg = lines.next().unwrap(); - if condition(&msg) { - break; - } - } +async fn next_line(lines: &mut LoggingLines) -> Option +where + R: tokio::io::AsyncBufRead + Unpin, +{ + let timeout = tokio::time::Duration::from_secs(60); + + tokio::time::timeout(timeout, lines.next_line()) + .await + .unwrap_or_else(|_| { + panic!( + "Output did not contain a new line after {} seconds", + timeout.as_secs() + ) + }) + .unwrap() } -fn wait_contains(s: &str, lines: &mut impl Iterator) { - wait_for(|msg| msg.contains(s), lines) +/// Returns the matched line or None if there are no more lines in this stream +async fn wait_for( + condition: impl Fn(&str) -> bool, + lines: &mut LoggingLines, +) -> Option +where + R: tokio::io::AsyncBufRead + Unpin, +{ + while let Some(line) = lines.next_line().await.unwrap() { + if condition(line.as_str()) { + return Some(line); + } + } + + None +} + +async fn wait_contains(s: &str, lines: &mut LoggingLines) -> String +where + R: tokio::io::AsyncBufRead + Unpin, +{ + let timeout = tokio::time::Duration::from_secs(60); + + tokio::time::timeout(timeout, wait_for(|line| line.contains(s), lines)) + .await + .unwrap_or_else(|_| { + panic!( + "Output did not contain \"{}\" after {} seconds", + s, + timeout.as_secs() + ) + }) + .unwrap_or_else(|| panic!("Output ended without containing \"{}\"", s)) } /// Before test cases touch files, they need to wait for the watcher to be @@ -67,18 +131,36 @@ fn wait_contains(s: &str, lines: &mut impl Iterator) { /// deno must be running with --log-level=debug /// file_name should be the file name and, optionally, extension. file_name /// may not be a full path, as it is not portable. -fn wait_for_watcher( +async fn wait_for_watcher( file_name: &str, - stderr_lines: &mut impl Iterator, -) { - wait_for( - |m| m.contains("Watching paths") && m.contains(file_name), - stderr_lines, - ); -} + stderr_lines: &mut LoggingLines, +) -> String +where + R: tokio::io::AsyncBufRead + Unpin, +{ + let timeout = tokio::time::Duration::from_secs(60); -fn read_line(s: &str, lines: &mut impl Iterator) -> String { - lines.find(|m| m.contains(s)).unwrap() + tokio::time::timeout( + timeout, + wait_for( + |line| line.contains("Watching paths") && line.contains(file_name), + stderr_lines, + ), + ) + .await + .unwrap_or_else(|_| { + panic!( + "Watcher did not start for file \"{}\" after {} seconds", + file_name, + timeout.as_secs() + ) + }) + .unwrap_or_else(|| { + panic!( + "Output ended without before the watcher started watching file \"{}\"", + file_name + ) + }) } fn check_alive_then_kill(mut child: DenoChild) { @@ -88,26 +170,31 @@ fn check_alive_then_kill(mut child: DenoChild) { fn child_lines( child: &mut std::process::Child, -) -> (impl Iterator, impl Iterator) { - let stdout_lines = std::io::BufReader::new(child.stdout.take().unwrap()) - .lines() - .map(|r| { - let line = r.unwrap(); - eprintln!("STDOUT: {line}"); - line - }); - let stderr_lines = std::io::BufReader::new(child.stderr.take().unwrap()) - .lines() - .map(|r| { - let line = r.unwrap(); - eprintln!("STDERR: {line}"); - line - }); +) -> ( + LoggingLines>, + LoggingLines>, +) { + let stdout_lines = LoggingLines { + lines: tokio::io::BufReader::new( + tokio::process::ChildStdout::from_std(child.stdout.take().unwrap()) + .unwrap(), + ) + .lines(), + stream_name: "STDOUT".to_string(), + }; + let stderr_lines = LoggingLines { + lines: tokio::io::BufReader::new( + tokio::process::ChildStderr::from_std(child.stderr.take().unwrap()) + .unwrap(), + ) + .lines(), + stream_name: "STDERR".to_string(), + }; (stdout_lines, stderr_lines) } -#[test] -fn lint_watch_test() { +#[tokio::test] +async fn lint_watch_test() { let t = TempDir::new(); let badly_linted_original = util::testdata_path().join("lint/watch/badly_linted.js"); @@ -136,9 +223,10 @@ fn lint_watch_test() { .spawn() .unwrap(); let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); - let next_line = stderr_lines.next().unwrap(); + let next_line = next_line(&mut stderr_lines).await.unwrap(); + assert_contains!(&next_line, "Lint started"); - let mut output = read_all_lints(&mut stderr_lines); + let mut output = read_all_lints(&mut stderr_lines).await; let expected = std::fs::read_to_string(badly_linted_output).unwrap(); assert_eq!(output, expected); @@ -146,14 +234,14 @@ fn lint_watch_test() { std::fs::copy(badly_linted_fixed1, &badly_linted).unwrap(); std::thread::sleep(std::time::Duration::from_secs(1)); - output = read_all_lints(&mut stderr_lines); + output = read_all_lints(&mut stderr_lines).await; let expected = std::fs::read_to_string(badly_linted_fixed1_output).unwrap(); assert_eq!(output, expected); // Change content of the file again to be badly-linted std::fs::copy(badly_linted_fixed2, &badly_linted).unwrap(); - output = read_all_lints(&mut stderr_lines); + output = read_all_lints(&mut stderr_lines).await; let expected = std::fs::read_to_string(badly_linted_fixed2_output).unwrap(); assert_eq!(output, expected); @@ -164,8 +252,8 @@ fn lint_watch_test() { drop(t); } -#[test] -fn lint_watch_without_args_test() { +#[tokio::test] +async fn lint_watch_without_args_test() { let t = TempDir::new(); let badly_linted_original = util::testdata_path().join("lint/watch/badly_linted.js"); @@ -194,16 +282,16 @@ fn lint_watch_without_args_test() { .unwrap(); let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); - let next_line = stderr_lines.next().unwrap(); + let next_line = next_line(&mut stderr_lines).await.unwrap(); assert_contains!(&next_line, "Lint started"); - let mut output = read_all_lints(&mut stderr_lines); + let mut output = read_all_lints(&mut stderr_lines).await; let expected = std::fs::read_to_string(badly_linted_output).unwrap(); assert_eq!(output, expected); // Change content of the file again to be badly-linted std::fs::copy(badly_linted_fixed1, &badly_linted).unwrap(); - output = read_all_lints(&mut stderr_lines); + output = read_all_lints(&mut stderr_lines).await; let expected = std::fs::read_to_string(badly_linted_fixed1_output).unwrap(); assert_eq!(output, expected); @@ -211,7 +299,7 @@ fn lint_watch_without_args_test() { std::fs::copy(badly_linted_fixed2, &badly_linted).unwrap(); std::thread::sleep(std::time::Duration::from_secs(1)); - output = read_all_lints(&mut stderr_lines); + output = read_all_lints(&mut stderr_lines).await; let expected = std::fs::read_to_string(badly_linted_fixed2_output).unwrap(); assert_eq!(output, expected); @@ -222,8 +310,8 @@ fn lint_watch_without_args_test() { drop(t); } -#[test] -fn lint_all_files_on_each_change_test() { +#[tokio::test] +async fn lint_all_files_on_each_change_test() { let t = TempDir::new(); let badly_linted_fixed0 = util::testdata_path().join("lint/watch/badly_linted.js"); @@ -249,11 +337,17 @@ fn lint_all_files_on_each_change_test() { .unwrap(); let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); - assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 2 files"); + assert_contains!( + wait_contains("Checked", &mut stderr_lines).await, + "Checked 2 files" + ); std::fs::copy(badly_linted_fixed2, badly_linted_2).unwrap(); - assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 2 files"); + assert_contains!( + wait_contains("Checked", &mut stderr_lines).await, + "Checked 2 files" + ); assert!(child.try_wait().unwrap().is_none()); @@ -261,8 +355,8 @@ fn lint_all_files_on_each_change_test() { drop(t); } -#[test] -fn fmt_watch_test() { +#[tokio::test] +async fn fmt_watch_test() { let fmt_testdata_path = util::testdata_path().join("fmt"); let t = TempDir::new(); let fixed = fmt_testdata_path.join("badly_formatted_fixed.js"); @@ -282,13 +376,16 @@ fn fmt_watch_test() { .unwrap(); let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); - let next_line = stderr_lines.next().unwrap(); + let next_line = next_line(&mut stderr_lines).await.unwrap(); assert_contains!(&next_line, "Fmt started"); assert_contains!( - skip_restarting_line(&mut stderr_lines), + skip_restarting_line(&mut stderr_lines).await, "badly_formatted.js" ); - assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 1 file"); + assert_contains!( + wait_contains("Checked", &mut stderr_lines).await, + "Checked 1 file" + ); let expected = std::fs::read_to_string(fixed.clone()).unwrap(); let actual = std::fs::read_to_string(badly_formatted.clone()).unwrap(); @@ -298,10 +395,13 @@ fn fmt_watch_test() { std::fs::copy(&badly_formatted_original, &badly_formatted).unwrap(); assert_contains!( - skip_restarting_line(&mut stderr_lines), + skip_restarting_line(&mut stderr_lines).await, "badly_formatted.js" ); - assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 1 file"); + assert_contains!( + wait_contains("Checked", &mut stderr_lines).await, + "Checked 1 file" + ); // Check if file has been automatically formatted by watcher let expected = std::fs::read_to_string(fixed).unwrap(); @@ -310,8 +410,8 @@ fn fmt_watch_test() { check_alive_then_kill(child); } -#[test] -fn fmt_watch_without_args_test() { +#[tokio::test] +async fn fmt_watch_without_args_test() { let fmt_testdata_path = util::testdata_path().join("fmt"); let t = TempDir::new(); let fixed = fmt_testdata_path.join("badly_formatted_fixed.js"); @@ -330,13 +430,16 @@ fn fmt_watch_without_args_test() { .unwrap(); let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); - let next_line = stderr_lines.next().unwrap(); + let next_line = next_line(&mut stderr_lines).await.unwrap(); assert_contains!(&next_line, "Fmt started"); assert_contains!( - skip_restarting_line(&mut stderr_lines), + skip_restarting_line(&mut stderr_lines).await, "badly_formatted.js" ); - assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 1 file"); + assert_contains!( + wait_contains("Checked", &mut stderr_lines).await, + "Checked 1 file" + ); let expected = std::fs::read_to_string(fixed.clone()).unwrap(); let actual = std::fs::read_to_string(badly_formatted.clone()).unwrap(); @@ -345,10 +448,13 @@ fn fmt_watch_without_args_test() { // Change content of the file again to be badly formatted std::fs::copy(&badly_formatted_original, &badly_formatted).unwrap(); assert_contains!( - skip_restarting_line(&mut stderr_lines), + skip_restarting_line(&mut stderr_lines).await, "badly_formatted.js" ); - assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 1 file"); + assert_contains!( + wait_contains("Checked", &mut stderr_lines).await, + "Checked 1 file" + ); // Check if file has been automatically formatted by watcher let expected = std::fs::read_to_string(fixed).unwrap(); @@ -357,8 +463,8 @@ fn fmt_watch_without_args_test() { check_alive_then_kill(child); } -#[test] -fn fmt_check_all_files_on_each_change_test() { +#[tokio::test] +async fn fmt_check_all_files_on_each_change_test() { let t = TempDir::new(); let fmt_testdata_path = util::testdata_path().join("fmt"); let badly_formatted_original = fmt_testdata_path.join("badly_formatted.mjs"); @@ -381,7 +487,7 @@ fn fmt_check_all_files_on_each_change_test() { let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); assert_contains!( - read_line("error", &mut stderr_lines), + wait_contains("error", &mut stderr_lines).await, "Found 2 not formatted files in 2 files" ); @@ -389,15 +495,15 @@ fn fmt_check_all_files_on_each_change_test() { std::fs::copy(&badly_formatted_original, &badly_formatted_1).unwrap(); assert_contains!( - read_line("error", &mut stderr_lines), + wait_contains("error", &mut stderr_lines).await, "Found 2 not formatted files in 2 files" ); check_alive_then_kill(child); } -#[test] -fn bundle_js_watch() { +#[tokio::test] +async fn bundle_js_watch() { use std::path::PathBuf; // Test strategy extends this of test bundle_js by adding watcher let t = TempDir::new(); @@ -421,42 +527,60 @@ fn bundle_js_watch() { let (_stdout_lines, mut stderr_lines) = child_lines(&mut deno); - assert_contains!(stderr_lines.next().unwrap(), "Warning"); - assert_contains!(stderr_lines.next().unwrap(), "deno_emit"); - assert_contains!(stderr_lines.next().unwrap(), "Check"); - let next_line = stderr_lines.next().unwrap(); - assert_contains!(&next_line, "Bundle started"); - assert_contains!(stderr_lines.next().unwrap(), "file_to_watch.ts"); - assert_contains!(stderr_lines.next().unwrap(), "mod6.bundle.js"); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "Warning"); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "deno_emit"); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "Check"); + assert_contains!( + next_line(&mut stderr_lines).await.unwrap(), + "Bundle started" + ); + assert_contains!( + next_line(&mut stderr_lines).await.unwrap(), + "file_to_watch.ts" + ); + assert_contains!( + next_line(&mut stderr_lines).await.unwrap(), + "mod6.bundle.js" + ); let file = PathBuf::from(&bundle); assert!(file.is_file()); - wait_contains("Bundle finished", &mut stderr_lines); + + wait_contains("Bundle finished", &mut stderr_lines).await; write(&file_to_watch, "console.log('Hello world2');").unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Check"); - let next_line = stderr_lines.next().unwrap(); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "Check"); + let line = next_line(&mut stderr_lines).await.unwrap(); // Should not clear screen, as we are in non-TTY environment - assert_not_contains!(&next_line, CLEAR_SCREEN); - assert_contains!(&next_line, "File change detected!"); - assert_contains!(stderr_lines.next().unwrap(), "file_to_watch.ts"); - assert_contains!(stderr_lines.next().unwrap(), "mod6.bundle.js"); + assert_not_contains!(&line, CLEAR_SCREEN); + assert_contains!(&line, "File change detected!"); + assert_contains!( + next_line(&mut stderr_lines).await.unwrap(), + "file_to_watch.ts" + ); + assert_contains!( + next_line(&mut stderr_lines).await.unwrap(), + "mod6.bundle.js" + ); let file = PathBuf::from(&bundle); assert!(file.is_file()); - wait_contains("Bundle finished", &mut stderr_lines); + wait_contains("Bundle finished", &mut stderr_lines).await; // 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_contains("Bundle failed", &mut stderr_lines); + assert_contains!( + next_line(&mut stderr_lines).await.unwrap(), + "File change detected!" + ); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "error: "); + wait_contains("Bundle failed", &mut stderr_lines).await; check_alive_then_kill(deno); } /// Confirm that the watcher continues to work even if module resolution fails at the *first* attempt -#[test] -fn bundle_watch_not_exit() { +#[tokio::test] +async fn bundle_watch_not_exit() { let t = TempDir::new(); let file_to_watch = t.path().join("file_to_watch.ts"); write(&file_to_watch, "syntax error ^^").unwrap(); @@ -476,37 +600,52 @@ fn bundle_watch_not_exit() { .unwrap(); let (_stdout_lines, mut stderr_lines) = child_lines(&mut deno); - assert_contains!(stderr_lines.next().unwrap(), "Warning"); - assert_contains!(stderr_lines.next().unwrap(), "deno_emit"); - assert_contains!(stderr_lines.next().unwrap(), "Bundle started"); - assert_contains!(stderr_lines.next().unwrap(), "error:"); - assert_eq!(stderr_lines.next().unwrap(), ""); - assert_eq!(stderr_lines.next().unwrap(), " syntax error ^^"); - assert_eq!(stderr_lines.next().unwrap(), " ~~~~~"); - assert_contains!(stderr_lines.next().unwrap(), "Bundle failed"); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "Warning"); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "deno_emit"); + assert_contains!( + next_line(&mut stderr_lines).await.unwrap(), + "Bundle started" + ); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "error:"); + assert_eq!(next_line(&mut stderr_lines).await.unwrap(), ""); + assert_eq!( + next_line(&mut stderr_lines).await.unwrap(), + " syntax error ^^" + ); + assert_eq!( + next_line(&mut stderr_lines).await.unwrap(), + " ~~~~~" + ); + assert_contains!( + next_line(&mut stderr_lines).await.unwrap(), + "Bundle failed" + ); // the target file hasn't been created yet assert!(!target_file.is_file()); // Make sure the watcher actually restarts and works fine with the proper syntax write(&file_to_watch, "console.log(42);").unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Check"); - let next_line = stderr_lines.next().unwrap(); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "Check"); + let line = next_line(&mut stderr_lines).await.unwrap(); // Should not clear screen, as we are in non-TTY environment - assert_not_contains!(&next_line, CLEAR_SCREEN); - assert_contains!(&next_line, "File change detected!"); - assert_contains!(stderr_lines.next().unwrap(), "file_to_watch.ts"); - assert_contains!(stderr_lines.next().unwrap(), "target.js"); + assert_not_contains!(&line, CLEAR_SCREEN); + assert_contains!(&line, "File change detected!"); + assert_contains!( + next_line(&mut stderr_lines).await.unwrap(), + "file_to_watch.ts" + ); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "target.js"); - wait_contains("Bundle finished", &mut stderr_lines); + wait_contains("Bundle finished", &mut stderr_lines).await; // bundled file is created assert!(target_file.is_file()); check_alive_then_kill(deno); } -#[test] -fn run_watch_no_dynamic() { +#[tokio::test] +async 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(); @@ -526,15 +665,15 @@ fn run_watch_no_dynamic() { .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - wait_contains("Hello world", &mut stdout_lines); - wait_for_watcher("file_to_watch.js", &mut stderr_lines); + wait_contains("Hello world", &mut stdout_lines).await; + wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; // Change content of the file write(&file_to_watch, "console.log('Hello world2');").unwrap(); - wait_contains("Restarting", &mut stderr_lines); - wait_contains("Hello world2", &mut stdout_lines); - wait_for_watcher("file_to_watch.js", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines).await; + wait_contains("Hello world2", &mut stdout_lines).await; + wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; // Add dependency let another_file = t.path().join("another_file.js"); @@ -545,23 +684,23 @@ fn run_watch_no_dynamic() { ) .unwrap(); - wait_contains("Restarting", &mut stderr_lines); - wait_contains("0", &mut stdout_lines); - wait_for_watcher("another_file.js", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines).await; + wait_contains("0", &mut stdout_lines).await; + wait_for_watcher("another_file.js", &mut stderr_lines).await; // Confirm that restarting occurs when a new file is updated write(&another_file, "export const foo = 42;").unwrap(); - wait_contains("Restarting", &mut stderr_lines); - wait_contains("42", &mut stdout_lines); - wait_for_watcher("file_to_watch.js", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines).await; + wait_contains("42", &mut stdout_lines).await; + wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax write(&file_to_watch, "syntax error ^^").unwrap(); - wait_contains("Restarting", &mut stderr_lines); - wait_contains("error:", &mut stderr_lines); - wait_for_watcher("file_to_watch.js", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines).await; + wait_contains("error:", &mut stderr_lines).await; + wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; // Then restore the file write( @@ -570,23 +709,23 @@ fn run_watch_no_dynamic() { ) .unwrap(); - wait_contains("Restarting", &mut stderr_lines); - wait_contains("42", &mut stdout_lines); - wait_for_watcher("another_file.js", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines).await; + wait_contains("42", &mut stdout_lines).await; + wait_for_watcher("another_file.js", &mut stderr_lines).await; // Update the content of the imported file with invalid syntax write(&another_file, "syntax error ^^").unwrap(); - wait_contains("Restarting", &mut stderr_lines); - wait_contains("error:", &mut stderr_lines); - wait_for_watcher("another_file.js", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines).await; + wait_contains("error:", &mut stderr_lines).await; + wait_for_watcher("another_file.js", &mut stderr_lines).await; // Modify the imported file and make sure that restarting occurs write(&another_file, "export const foo = 'modified!';").unwrap(); - wait_contains("Restarting", &mut stderr_lines); - wait_contains("modified!", &mut stdout_lines); - wait_contains("Watching paths", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines).await; + wait_contains("modified!", &mut stdout_lines).await; + wait_contains("Watching paths", &mut stderr_lines).await; check_alive_then_kill(child); } @@ -594,8 +733,8 @@ fn run_watch_no_dynamic() { // if that's because of a bug in code or the runner itself. We should reenable // it once we upgrade to XL runners for macOS. #[cfg(not(target_os = "macos"))] -#[test] -fn run_watch_external_watch_files() { +#[tokio::test] +async fn run_watch_external_watch_files() { let t = TempDir::new(); let file_to_watch = t.path().join("file_to_watch.js"); write(&file_to_watch, "console.log('Hello world');").unwrap(); @@ -625,25 +764,25 @@ fn run_watch_external_watch_files() { .spawn() .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - wait_contains("Process started", &mut stderr_lines); - wait_contains("Hello world", &mut stdout_lines); - wait_for_watcher("external_file_to_watch.txt", &mut stderr_lines); + wait_contains("Process started", &mut stderr_lines).await; + wait_contains("Hello world", &mut stdout_lines).await; + wait_for_watcher("external_file_to_watch.txt", &mut stderr_lines).await; // Change content of the external file write(&external_file_to_watch, "Hello world2").unwrap(); - wait_contains("Restarting", &mut stderr_lines); - wait_contains("Process finished", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines).await; + wait_contains("Process finished", &mut stderr_lines).await; // Again (https://github.com/denoland/deno/issues/17584) write(&external_file_to_watch, "Hello world3").unwrap(); - wait_contains("Restarting", &mut stderr_lines); - wait_contains("Process finished", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines).await; + wait_contains("Process finished", &mut stderr_lines).await; check_alive_then_kill(child); } -#[test] -fn run_watch_load_unload_events() { +#[tokio::test] +async fn run_watch_load_unload_events() { let t = TempDir::new(); let file_to_watch = t.path().join("file_to_watch.js"); write( @@ -677,8 +816,8 @@ 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 - wait_contains("load", &mut stdout_lines); - wait_for_watcher("file_to_watch.js", &mut stderr_lines); + wait_contains("load", &mut stdout_lines).await; + wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; // Change content of the file, this time without an interval to keep it alive. write( @@ -696,22 +835,22 @@ fn run_watch_load_unload_events() { .unwrap(); // Wait for the restart - wait_contains("Restarting", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines).await; // Confirm that the unload event was dispatched from the first run - wait_contains("unload", &mut stdout_lines); + wait_contains("unload", &mut stdout_lines).await; // Followed by the load event of the second run - wait_contains("load", &mut stdout_lines); + wait_contains("load", &mut stdout_lines).await; // Which is then unloaded as there is nothing keeping it alive. - wait_contains("unload", &mut stdout_lines); + wait_contains("unload", &mut stdout_lines).await; check_alive_then_kill(child); } /// Confirm that the watcher continues to work even if module resolution fails at the *first* attempt -#[test] -fn run_watch_not_exit() { +#[tokio::test] +async fn run_watch_not_exit() { let t = TempDir::new(); let file_to_watch = t.path().join("file_to_watch.js"); write(&file_to_watch, "syntax error ^^").unwrap(); @@ -731,21 +870,21 @@ fn run_watch_not_exit() { .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - wait_contains("Process started", &mut stderr_lines); - wait_contains("error:", &mut stderr_lines); - wait_for_watcher("file_to_watch.js", &mut stderr_lines); + wait_contains("Process started", &mut stderr_lines).await; + wait_contains("error:", &mut stderr_lines).await; + wait_for_watcher("file_to_watch.js", &mut stderr_lines).await; // Make sure the watcher actually restarts and works fine with the proper syntax write(&file_to_watch, "console.log(42);").unwrap(); - wait_contains("Restarting", &mut stderr_lines); - wait_contains("42", &mut stdout_lines); - wait_contains("Process finished", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines).await; + wait_contains("42", &mut stdout_lines).await; + wait_contains("Process finished", &mut stderr_lines).await; check_alive_then_kill(child); } -#[test] -fn run_watch_with_import_map_and_relative_paths() { +#[tokio::test] +async fn run_watch_with_import_map_and_relative_paths() { fn create_relative_tmp_file( directory: &TempDir, filename: &'static str, @@ -786,16 +925,19 @@ fn run_watch_with_import_map_and_relative_paths() { .spawn() .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(), "Process finished"); - assert_contains!(stdout_lines.next().unwrap(), "Hello world"); + let line = next_line(&mut stderr_lines).await.unwrap(); + assert_contains!(&line, "Process started"); + assert_contains!( + next_line(&mut stderr_lines).await.unwrap(), + "Process finished" + ); + assert_contains!(next_line(&mut stdout_lines).await.unwrap(), "Hello world"); check_alive_then_kill(child); } -#[test] -fn run_watch_with_ext_flag() { +#[tokio::test] +async fn run_watch_with_ext_flag() { let t = TempDir::new(); let file_to_watch = t.path().join("file_to_watch"); write(&file_to_watch, "interface I{}; console.log(42);").unwrap(); @@ -816,11 +958,11 @@ fn run_watch_with_ext_flag() { .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - wait_contains("42", &mut stdout_lines); + wait_contains("42", &mut stdout_lines).await; // Make sure the watcher actually restarts and works fine with the proper language - wait_for_watcher("file_to_watch", &mut stderr_lines); - wait_contains("Process finished", &mut stderr_lines); + wait_for_watcher("file_to_watch", &mut stderr_lines).await; + wait_contains("Process finished", &mut stderr_lines).await; write( &file_to_watch, @@ -828,15 +970,15 @@ fn run_watch_with_ext_flag() { ) .unwrap(); - wait_contains("Restarting!", &mut stderr_lines); - wait_contains("123", &mut stdout_lines); - wait_contains("Process finished", &mut stderr_lines); + wait_contains("Restarting!", &mut stderr_lines).await; + wait_contains("123", &mut stdout_lines).await; + wait_contains("Process finished", &mut stderr_lines).await; check_alive_then_kill(child); } -#[test] -fn run_watch_error_messages() { +#[tokio::test] +async fn run_watch_error_messages() { let t = TempDir::new(); let file_to_watch = t.path().join("file_to_watch.js"); write( @@ -857,16 +999,16 @@ fn run_watch_error_messages() { .unwrap(); let (_, mut stderr_lines) = child_lines(&mut child); - wait_contains("Process started", &mut stderr_lines); - wait_contains("error: Uncaught SyntaxError: outer", &mut stderr_lines); - wait_contains("Caused by: TypeError: inner", &mut stderr_lines); - wait_contains("Process finished", &mut stderr_lines); + wait_contains("Process started", &mut stderr_lines).await; + wait_contains("error: Uncaught SyntaxError: outer", &mut stderr_lines).await; + wait_contains("Caused by: TypeError: inner", &mut stderr_lines).await; + wait_contains("Process finished", &mut stderr_lines).await; check_alive_then_kill(child); } -#[test] -fn test_watch() { +#[tokio::test] +async fn test_watch() { let t = TempDir::new(); let mut child = util::deno_cmd() @@ -883,9 +1025,12 @@ fn test_watch() { .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - assert_eq!(stdout_lines.next().unwrap(), ""); - assert_contains!(stdout_lines.next().unwrap(), "0 passed | 0 failed"); - wait_contains("Test finished", &mut stderr_lines); + assert_eq!(next_line(&mut stdout_lines).await.unwrap(), ""); + assert_contains!( + next_line(&mut stdout_lines).await.unwrap(), + "0 passed | 0 failed" + ); + wait_contains("Test finished", &mut stderr_lines).await; let foo_file = t.path().join("foo.js"); let bar_file = t.path().join("bar.js"); @@ -904,15 +1049,21 @@ fn test_watch() { ) .unwrap(); - assert_eq!(stdout_lines.next().unwrap(), ""); - assert_contains!(stdout_lines.next().unwrap(), "running 1 test"); - assert_contains!(stdout_lines.next().unwrap(), "foo", "bar"); - assert_contains!(stdout_lines.next().unwrap(), "running 1 test"); - assert_contains!(stdout_lines.next().unwrap(), "foo", "bar"); - stdout_lines.next(); - stdout_lines.next(); - stdout_lines.next(); - wait_contains("Test finished", &mut stderr_lines); + assert_eq!(next_line(&mut stdout_lines).await.unwrap(), ""); + assert_contains!( + next_line(&mut stdout_lines).await.unwrap(), + "running 1 test" + ); + assert_contains!(next_line(&mut stdout_lines).await.unwrap(), "foo", "bar"); + assert_contains!( + next_line(&mut stdout_lines).await.unwrap(), + "running 1 test" + ); + assert_contains!(next_line(&mut stdout_lines).await.unwrap(), "foo", "bar"); + next_line(&mut stdout_lines).await; + next_line(&mut stdout_lines).await; + next_line(&mut stdout_lines).await; + wait_contains("Test finished", &mut stderr_lines).await; // Change content of the file write( @@ -921,55 +1072,76 @@ fn test_watch() { ) .unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - assert_contains!(stdout_lines.next().unwrap(), "running 1 test"); - assert_contains!(stdout_lines.next().unwrap(), "foobar"); - stdout_lines.next(); - stdout_lines.next(); - stdout_lines.next(); - wait_contains("Test finished", &mut stderr_lines); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "Restarting"); + assert_contains!( + next_line(&mut stdout_lines).await.unwrap(), + "running 1 test" + ); + assert_contains!(next_line(&mut stdout_lines).await.unwrap(), "foobar"); + next_line(&mut stdout_lines).await; + next_line(&mut stdout_lines).await; + next_line(&mut stdout_lines).await; + wait_contains("Test finished", &mut stderr_lines).await; // Add test let another_test = t.path().join("new_test.js"); write(&another_test, "Deno.test('another one', () => 3 + 3)").unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - assert_contains!(stdout_lines.next().unwrap(), "running 1 test"); - assert_contains!(stdout_lines.next().unwrap(), "another one"); - stdout_lines.next(); - stdout_lines.next(); - stdout_lines.next(); - wait_contains("Test finished", &mut stderr_lines); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "Restarting"); + assert_contains!( + next_line(&mut stdout_lines).await.unwrap(), + "running 1 test" + ); + assert_contains!(next_line(&mut stdout_lines).await.unwrap(), "another one"); + next_line(&mut stdout_lines).await; + next_line(&mut stdout_lines).await; + next_line(&mut stdout_lines).await; + wait_contains("Test finished", &mut stderr_lines).await; // 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)") .unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - assert_contains!(stdout_lines.next().unwrap(), "running 2 tests"); - assert_contains!(stdout_lines.next().unwrap(), "another one"); - assert_contains!(stdout_lines.next().unwrap(), "another another one"); - stdout_lines.next(); - stdout_lines.next(); - stdout_lines.next(); - wait_contains("Test finished", &mut stderr_lines); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "Restarting"); + assert_contains!( + next_line(&mut stdout_lines).await.unwrap(), + "running 2 tests" + ); + assert_contains!(next_line(&mut stdout_lines).await.unwrap(), "another one"); + assert_contains!( + next_line(&mut stdout_lines).await.unwrap(), + "another another one" + ); + next_line(&mut stdout_lines).await; + next_line(&mut stdout_lines).await; + next_line(&mut stdout_lines).await; + wait_contains("Test finished", &mut stderr_lines).await; // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax write(&another_test, "syntax error ^^").unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - assert_contains!(stderr_lines.next().unwrap(), "error:"); - assert_eq!(stderr_lines.next().unwrap(), ""); - assert_eq!(stderr_lines.next().unwrap(), " syntax error ^^"); - assert_eq!(stderr_lines.next().unwrap(), " ~~~~~"); - assert_contains!(stderr_lines.next().unwrap(), "Test failed"); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "Restarting"); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "error:"); + assert_eq!(next_line(&mut stderr_lines).await.unwrap(), ""); + assert_eq!( + next_line(&mut stderr_lines).await.unwrap(), + " syntax error ^^" + ); + assert_eq!( + next_line(&mut stderr_lines).await.unwrap(), + " ~~~~~" + ); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "Test failed"); // Then restore the file write(&another_test, "Deno.test('another one', () => 3 + 3)").unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - assert_contains!(stdout_lines.next().unwrap(), "running 1 test"); - assert_contains!(stdout_lines.next().unwrap(), "another one"); - stdout_lines.next(); - stdout_lines.next(); - stdout_lines.next(); - wait_contains("Test finished", &mut stderr_lines); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "Restarting"); + assert_contains!( + next_line(&mut stdout_lines).await.unwrap(), + "running 1 test" + ); + assert_contains!(next_line(&mut stdout_lines).await.unwrap(), "another one"); + next_line(&mut stdout_lines).await; + next_line(&mut stdout_lines).await; + next_line(&mut stdout_lines).await; + wait_contains("Test finished", &mut stderr_lines).await; // 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 @@ -978,22 +1150,28 @@ fn test_watch() { "export default function foo() { throw new Error('Whoops!'); }", ) .unwrap(); - 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(|m| m.contains("FAILED"), &mut stdout_lines); - stdout_lines.next(); - wait_contains("Test finished", &mut stderr_lines); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "Restarting"); + assert_contains!( + next_line(&mut stdout_lines).await.unwrap(), + "running 1 test" + ); + assert_contains!(next_line(&mut stdout_lines).await.unwrap(), "FAILED"); + wait_contains("FAILED", &mut stdout_lines).await; + next_line(&mut stdout_lines).await; + wait_contains("Test finished", &mut stderr_lines).await; // Then restore the file write(&foo_file, "export default function foo() { 1 + 1 }").unwrap(); - assert_contains!(stderr_lines.next().unwrap(), "Restarting"); - assert_contains!(stdout_lines.next().unwrap(), "running 1 test"); - assert_contains!(stdout_lines.next().unwrap(), "foo"); - stdout_lines.next(); - stdout_lines.next(); - stdout_lines.next(); - wait_contains("Test finished", &mut stderr_lines); + assert_contains!(next_line(&mut stderr_lines).await.unwrap(), "Restarting"); + assert_contains!( + next_line(&mut stdout_lines).await.unwrap(), + "running 1 test" + ); + assert_contains!(next_line(&mut stdout_lines).await.unwrap(), "foo"); + next_line(&mut stdout_lines).await; + next_line(&mut stdout_lines).await; + next_line(&mut stdout_lines).await; + wait_contains("Test finished", &mut stderr_lines).await; // Test that circular dependencies work fine write( @@ -1010,7 +1188,8 @@ fn test_watch() { } #[flaky_test] -fn test_watch_doc() { +#[tokio::main] +async fn test_watch_doc() { let t = TempDir::new(); let mut child = util::deno_cmd() @@ -1027,9 +1206,12 @@ fn test_watch_doc() { .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - assert_eq!(stdout_lines.next().unwrap(), ""); - assert_contains!(stdout_lines.next().unwrap(), "0 passed | 0 failed"); - wait_contains("Test finished", &mut stderr_lines); + assert_eq!(next_line(&mut stdout_lines).await.unwrap(), ""); + assert_contains!( + next_line(&mut stdout_lines).await.unwrap(), + "0 passed | 0 failed" + ); + wait_contains("Test finished", &mut stderr_lines).await; let foo_file = t.path().join("foo.ts"); write( @@ -1055,12 +1237,12 @@ fn test_watch_doc() { // We only need to scan for a Check file://.../foo.ts$3-6 line that // corresponds to the documentation block being type-checked. - assert_contains!(skip_restarting_line(&mut stderr_lines), "foo.ts$3-6"); + assert_contains!(skip_restarting_line(&mut stderr_lines).await, "foo.ts$3-6"); check_alive_then_kill(child); } -#[test] -fn test_watch_module_graph_error_referrer() { +#[tokio::test] +async fn test_watch_module_graph_error_referrer() { let t = TempDir::new(); let file_to_watch = t.path().join("file_to_watch.js"); write(&file_to_watch, "import './nonexistent.js';").unwrap(); @@ -1076,21 +1258,21 @@ fn test_watch_module_graph_error_referrer() { .spawn() .unwrap(); let (_, mut stderr_lines) = child_lines(&mut child); - let line1 = stderr_lines.next().unwrap(); + let line1 = next_line(&mut stderr_lines).await.unwrap(); assert_contains!(&line1, "Process started"); - let line2 = stderr_lines.next().unwrap(); + let line2 = next_line(&mut stderr_lines).await.unwrap(); assert_contains!(&line2, "error: Module not found"); assert_contains!(&line2, "nonexistent.js"); - let line3 = stderr_lines.next().unwrap(); + let line3 = next_line(&mut stderr_lines).await.unwrap(); assert_contains!(&line3, " at "); assert_contains!(&line3, "file_to_watch.js"); - wait_contains("Process finished", &mut stderr_lines); + wait_contains("Process finished", &mut stderr_lines).await; check_alive_then_kill(child); } // Regression test for https://github.com/denoland/deno/issues/15428. -#[test] -fn test_watch_unload_handler_error_on_drop() { +#[tokio::test] +async fn test_watch_unload_handler_error_on_drop() { let t = TempDir::new(); let file_to_watch = t.path().join("file_to_watch.js"); write( @@ -1116,15 +1298,15 @@ fn test_watch_unload_handler_error_on_drop() { .spawn() .unwrap(); let (_, mut stderr_lines) = child_lines(&mut child); - wait_contains("Process started", &mut stderr_lines); - wait_contains("Uncaught Error: bar", &mut stderr_lines); - wait_contains("Process finished", &mut stderr_lines); + wait_contains("Process started", &mut stderr_lines).await; + wait_contains("Uncaught Error: bar", &mut stderr_lines).await; + wait_contains("Process finished", &mut stderr_lines).await; check_alive_then_kill(child); } #[cfg(unix)] -#[test] -fn test_watch_sigint() { +#[tokio::test] +async fn test_watch_sigint() { use nix::sys::signal; use nix::sys::signal::Signal; use nix::unistd::Pid; @@ -1143,17 +1325,17 @@ fn test_watch_sigint() { .spawn() .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - wait_contains("Test started", &mut stderr_lines); - wait_contains("ok | 1 passed | 0 failed", &mut stdout_lines); - wait_contains("Test finished", &mut stderr_lines); + wait_contains("Test started", &mut stderr_lines).await; + wait_contains("ok | 1 passed | 0 failed", &mut stdout_lines).await; + wait_contains("Test finished", &mut stderr_lines).await; signal::kill(Pid::from_raw(child.id() as i32), Signal::SIGINT).unwrap(); let exit_status = child.wait().unwrap(); assert_eq!(exit_status.code(), Some(130)); } // Regression test for https://github.com/denoland/deno/issues/15465. -#[test] -fn run_watch_reload_once() { +#[tokio::test] +async fn run_watch_reload_once() { let _g = util::http_server(); let t = TempDir::new(); let file_to_watch = t.path().join("file_to_watch.js"); @@ -1176,21 +1358,21 @@ fn run_watch_reload_once() { .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - wait_contains("finished", &mut stderr_lines); - let first_output = stdout_lines.next().unwrap(); + wait_contains("finished", &mut stderr_lines).await; + let first_output = next_line(&mut stdout_lines).await.unwrap(); write(&file_to_watch, file_content).unwrap(); // The remote dynamic module should not have been reloaded again. - wait_contains("finished", &mut stderr_lines); - let second_output = stdout_lines.next().unwrap(); + wait_contains("finished", &mut stderr_lines).await; + let second_output = next_line(&mut stdout_lines).await.unwrap(); assert_eq!(second_output, first_output); check_alive_then_kill(child); } -#[test] -fn run_watch_dynamic_imports() { +#[tokio::test] +async fn run_watch_dynamic_imports() { let t = TempDir::new(); let file_to_watch = t.path().join("file_to_watch.js"); write( @@ -1234,24 +1416,27 @@ fn run_watch_dynamic_imports() { .spawn() .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - wait_contains("No package.json file found", &mut stderr_lines); - wait_contains("Process started", &mut stderr_lines); + wait_contains("No package.json file found", &mut stderr_lines).await; + wait_contains("Process started", &mut stderr_lines).await; wait_contains( "Hopefully dynamic import will be watched...", &mut stdout_lines, - ); + ) + .await; wait_contains( "I'm statically imported from the dynamic import", &mut stdout_lines, - ); + ) + .await; wait_contains( "I'm dynamically imported and I cause restarts!", &mut stdout_lines, - ); + ) + .await; - wait_for_watcher("imported2.js", &mut stderr_lines); - wait_contains("finished", &mut stderr_lines); + wait_for_watcher("imported2.js", &mut stderr_lines).await; + wait_contains("finished", &mut stderr_lines).await; write( &file_to_watch3, @@ -1261,19 +1446,22 @@ fn run_watch_dynamic_imports() { ) .unwrap(); - wait_contains("Restarting", &mut stderr_lines); + wait_contains("Restarting", &mut stderr_lines).await; wait_contains( "Hopefully dynamic import will be watched...", &mut stdout_lines, - ); + ) + .await; wait_contains( "I'm statically imported from the dynamic import and I've changed", &mut stdout_lines, - ); + ) + .await; wait_contains( "I'm dynamically imported and I cause restarts!", &mut stdout_lines, - ); + ) + .await; check_alive_then_kill(child); }