summaryrefslogtreecommitdiff
path: root/cli/tests/integration/watcher_tests.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tests/integration/watcher_tests.rs')
-rw-r--r--cli/tests/integration/watcher_tests.rs597
1 files changed, 597 insertions, 0 deletions
diff --git a/cli/tests/integration/watcher_tests.rs b/cli/tests/integration/watcher_tests.rs
new file mode 100644
index 000000000..3e9aa563a
--- /dev/null
+++ b/cli/tests/integration/watcher_tests.rs
@@ -0,0 +1,597 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+use std::io::BufRead;
+use tempfile::TempDir;
+use test_util as util;
+
+// Helper function to skip watcher output that contains "Restarting"
+// phrase.
+fn skip_restarting_line(
+ mut stderr_lines: impl Iterator<Item = String>,
+) -> String {
+ loop {
+ let msg = stderr_lines.next().unwrap();
+ if !msg.contains("Restarting") {
+ return msg;
+ }
+ }
+}
+
+/// Helper function to skip watcher output that doesn't contain
+/// "{job_name} finished" phrase.
+fn wait_for_process_finished(
+ job_name: &str,
+ stderr_lines: &mut impl Iterator<Item = String>,
+) {
+ let phrase = format!("{} finished", job_name);
+ loop {
+ let msg = stderr_lines.next().unwrap();
+ if msg.contains(&phrase) {
+ break;
+ }
+ }
+}
+
+/// Helper function to skip watcher output that doesn't contain
+/// "{job_name} failed" phrase.
+fn wait_for_process_failed(
+ job_name: &str,
+ stderr_lines: &mut impl Iterator<Item = String>,
+) {
+ let phrase = format!("{} failed", job_name);
+ loop {
+ let msg = stderr_lines.next().unwrap();
+ if msg.contains(&phrase) {
+ break;
+ }
+ }
+}
+
+#[test]
+fn fmt_watch_test() {
+ let t = TempDir::new().expect("tempdir fail");
+ let fixed = util::root_path().join("cli/tests/badly_formatted_fixed.js");
+ let badly_formatted_original =
+ util::root_path().join("cli/tests/badly_formatted.mjs");
+ let badly_formatted = t.path().join("badly_formatted.js");
+ std::fs::copy(&badly_formatted_original, &badly_formatted)
+ .expect("Failed to copy file");
+
+ let mut child = util::deno_cmd()
+ .current_dir(util::root_path())
+ .arg("fmt")
+ .arg(&badly_formatted)
+ .arg("--watch")
+ .arg("--unstable")
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .expect("Failed to spawn script");
+ let stderr = child.stderr.as_mut().unwrap();
+ let stderr_lines =
+ std::io::BufReader::new(stderr).lines().map(|r| r.unwrap());
+
+ // TODO(lucacasonato): remove this timeout. It seems to be needed on Linux.
+ std::thread::sleep(std::time::Duration::from_secs(1));
+
+ assert!(skip_restarting_line(stderr_lines).contains("badly_formatted.js"));
+
+ let expected = std::fs::read_to_string(fixed.clone()).unwrap();
+ let actual = std::fs::read_to_string(badly_formatted.clone()).unwrap();
+ assert_eq!(expected, actual);
+
+ // Change content of the file again to be badly formatted
+ std::fs::copy(&badly_formatted_original, &badly_formatted)
+ .expect("Failed to copy file");
+ std::thread::sleep(std::time::Duration::from_secs(1));
+
+ // Check if file has been automatically formatted by watcher
+ let expected = std::fs::read_to_string(fixed).unwrap();
+ let actual = std::fs::read_to_string(badly_formatted).unwrap();
+ assert_eq!(expected, actual);
+
+ // the watcher process is still alive
+ assert!(child.try_wait().unwrap().is_none());
+
+ child.kill().unwrap();
+ drop(t);
+}
+
+#[test]
+fn bundle_js_watch() {
+ use std::path::PathBuf;
+ // Test strategy extends this of test bundle_js by adding watcher
+ let t = TempDir::new().expect("tempdir fail");
+ let file_to_watch = t.path().join("file_to_watch.js");
+ std::fs::write(&file_to_watch, "console.log('Hello world');")
+ .expect("error writing file");
+ assert!(file_to_watch.is_file());
+ let t = TempDir::new().expect("tempdir fail");
+ let bundle = t.path().join("mod6.bundle.js");
+ let mut deno = util::deno_cmd()
+ .current_dir(util::root_path())
+ .arg("bundle")
+ .arg(&file_to_watch)
+ .arg(&bundle)
+ .arg("--watch")
+ .arg("--unstable")
+ .env("NO_COLOR", "1")
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .expect("failed to spawn script");
+
+ let stderr = deno.stderr.as_mut().unwrap();
+ let mut stderr_lines =
+ std::io::BufReader::new(stderr).lines().map(|r| r.unwrap());
+
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ assert!(stderr_lines.next().unwrap().contains("file_to_watch.js"));
+ assert!(stderr_lines.next().unwrap().contains("mod6.bundle.js"));
+ let file = PathBuf::from(&bundle);
+ assert!(file.is_file());
+ wait_for_process_finished("Bundle", &mut stderr_lines);
+
+ std::fs::write(&file_to_watch, "console.log('Hello world2');")
+ .expect("error writing file");
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ assert!(stderr_lines
+ .next()
+ .unwrap()
+ .contains("File change detected!"));
+ assert!(stderr_lines.next().unwrap().contains("file_to_watch.js"));
+ assert!(stderr_lines.next().unwrap().contains("mod6.bundle.js"));
+ let file = PathBuf::from(&bundle);
+ assert!(file.is_file());
+ wait_for_process_finished("Bundle", &mut stderr_lines);
+
+ // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax
+ std::fs::write(&file_to_watch, "syntax error ^^")
+ .expect("error writing file");
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ assert!(stderr_lines
+ .next()
+ .unwrap()
+ .contains("File change detected!"));
+ assert!(stderr_lines.next().unwrap().contains("error: "));
+ wait_for_process_failed("Bundle", &mut stderr_lines);
+
+ // the watcher process is still alive
+ assert!(deno.try_wait().unwrap().is_none());
+
+ deno.kill().unwrap();
+ drop(t);
+}
+
+/// Confirm that the watcher continues to work even if module resolution fails at the *first* attempt
+#[test]
+fn bundle_watch_not_exit() {
+ let t = TempDir::new().expect("tempdir fail");
+ let file_to_watch = t.path().join("file_to_watch.js");
+ std::fs::write(&file_to_watch, "syntax error ^^")
+ .expect("error writing file");
+ let target_file = t.path().join("target.js");
+
+ let mut deno = util::deno_cmd()
+ .current_dir(util::root_path())
+ .arg("bundle")
+ .arg(&file_to_watch)
+ .arg(&target_file)
+ .arg("--watch")
+ .arg("--unstable")
+ .env("NO_COLOR", "1")
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .expect("failed to spawn script");
+
+ let stderr = deno.stderr.as_mut().unwrap();
+ let mut stderr_lines =
+ std::io::BufReader::new(stderr).lines().map(|r| r.unwrap());
+
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ assert!(stderr_lines.next().unwrap().contains("error:"));
+ assert!(stderr_lines.next().unwrap().contains("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
+ std::fs::write(&file_to_watch, "console.log(42);")
+ .expect("error writing file");
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ assert!(stderr_lines
+ .next()
+ .unwrap()
+ .contains("File change detected!"));
+ assert!(stderr_lines.next().unwrap().contains("file_to_watch.js"));
+ assert!(stderr_lines.next().unwrap().contains("target.js"));
+ wait_for_process_finished("Bundle", &mut stderr_lines);
+ // bundled file is created
+ assert!(target_file.is_file());
+
+ // the watcher process is still alive
+ assert!(deno.try_wait().unwrap().is_none());
+
+ deno.kill().unwrap();
+ drop(t);
+}
+
+#[test]
+fn run_watch() {
+ let t = TempDir::new().expect("tempdir fail");
+ let file_to_watch = t.path().join("file_to_watch.js");
+ std::fs::write(&file_to_watch, "console.log('Hello world');")
+ .expect("error writing file");
+
+ let mut child = util::deno_cmd()
+ .current_dir(util::root_path())
+ .arg("run")
+ .arg("--watch")
+ .arg("--unstable")
+ .arg(&file_to_watch)
+ .env("NO_COLOR", "1")
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .expect("failed to spawn script");
+
+ let stdout = child.stdout.as_mut().unwrap();
+ let mut stdout_lines =
+ std::io::BufReader::new(stdout).lines().map(|r| r.unwrap());
+ let stderr = child.stderr.as_mut().unwrap();
+ let mut stderr_lines =
+ std::io::BufReader::new(stderr).lines().map(|r| r.unwrap());
+
+ assert!(stdout_lines.next().unwrap().contains("Hello world"));
+ wait_for_process_finished("Process", &mut stderr_lines);
+
+ // TODO(lucacasonato): remove this timeout. It seems to be needed on Linux.
+ std::thread::sleep(std::time::Duration::from_secs(1));
+
+ // Change content of the file
+ std::fs::write(&file_to_watch, "console.log('Hello world2');")
+ .expect("error writing file");
+ // Events from the file watcher is "debounced", so we need to wait for the next execution to start
+ std::thread::sleep(std::time::Duration::from_secs(1));
+
+ assert!(stderr_lines.next().unwrap().contains("Restarting"));
+ assert!(stdout_lines.next().unwrap().contains("Hello world2"));
+ wait_for_process_finished("Process", &mut stderr_lines);
+
+ // Add dependency
+ let another_file = t.path().join("another_file.js");
+ std::fs::write(&another_file, "export const foo = 0;")
+ .expect("error writing file");
+ std::fs::write(
+ &file_to_watch,
+ "import { foo } from './another_file.js'; console.log(foo);",
+ )
+ .expect("error writing file");
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ assert!(stderr_lines.next().unwrap().contains("Restarting"));
+ assert!(stdout_lines.next().unwrap().contains('0'));
+ wait_for_process_finished("Process", &mut stderr_lines);
+
+ // Confirm that restarting occurs when a new file is updated
+ std::fs::write(&another_file, "export const foo = 42;")
+ .expect("error writing file");
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ assert!(stderr_lines.next().unwrap().contains("Restarting"));
+ assert!(stdout_lines.next().unwrap().contains("42"));
+ wait_for_process_finished("Process", &mut stderr_lines);
+
+ // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax
+ std::fs::write(&file_to_watch, "syntax error ^^")
+ .expect("error writing file");
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ assert!(stderr_lines.next().unwrap().contains("Restarting"));
+ assert!(stderr_lines.next().unwrap().contains("error:"));
+ wait_for_process_failed("Process", &mut stderr_lines);
+
+ // Then restore the file
+ std::fs::write(
+ &file_to_watch,
+ "import { foo } from './another_file.js'; console.log(foo);",
+ )
+ .expect("error writing file");
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ assert!(stderr_lines.next().unwrap().contains("Restarting"));
+ assert!(stdout_lines.next().unwrap().contains("42"));
+ wait_for_process_finished("Process", &mut stderr_lines);
+
+ // Update the content of the imported file with invalid syntax
+ std::fs::write(&another_file, "syntax error ^^").expect("error writing file");
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ assert!(stderr_lines.next().unwrap().contains("Restarting"));
+ assert!(stderr_lines.next().unwrap().contains("error:"));
+ wait_for_process_failed("Process", &mut stderr_lines);
+
+ // Modify the imported file and make sure that restarting occurs
+ std::fs::write(&another_file, "export const foo = 'modified!';")
+ .expect("error writing file");
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ assert!(stderr_lines.next().unwrap().contains("Restarting"));
+ assert!(stdout_lines.next().unwrap().contains("modified!"));
+ wait_for_process_finished("Process", &mut stderr_lines);
+
+ // the watcher process is still alive
+ assert!(child.try_wait().unwrap().is_none());
+
+ child.kill().unwrap();
+ drop(t);
+}
+
+/// Confirm that the watcher continues to work even if module resolution fails at the *first* attempt
+#[test]
+fn run_watch_not_exit() {
+ let t = TempDir::new().expect("tempdir fail");
+ let file_to_watch = t.path().join("file_to_watch.js");
+ std::fs::write(&file_to_watch, "syntax error ^^")
+ .expect("error writing file");
+
+ let mut child = util::deno_cmd()
+ .current_dir(util::root_path())
+ .arg("run")
+ .arg("--watch")
+ .arg("--unstable")
+ .arg(&file_to_watch)
+ .env("NO_COLOR", "1")
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .expect("failed to spawn script");
+
+ let stdout = child.stdout.as_mut().unwrap();
+ let mut stdout_lines =
+ std::io::BufReader::new(stdout).lines().map(|r| r.unwrap());
+ let stderr = child.stderr.as_mut().unwrap();
+ let mut stderr_lines =
+ std::io::BufReader::new(stderr).lines().map(|r| r.unwrap());
+
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ assert!(stderr_lines.next().unwrap().contains("error:"));
+ assert!(stderr_lines.next().unwrap().contains("Process failed"));
+
+ // Make sure the watcher actually restarts and works fine with the proper syntax
+ std::fs::write(&file_to_watch, "console.log(42);")
+ .expect("error writing file");
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ assert!(stderr_lines.next().unwrap().contains("Restarting"));
+ assert!(stdout_lines.next().unwrap().contains("42"));
+ wait_for_process_finished("Process", &mut stderr_lines);
+
+ // the watcher process is still alive
+ assert!(child.try_wait().unwrap().is_none());
+
+ child.kill().unwrap();
+ drop(t);
+}
+
+#[test]
+fn run_watch_with_import_map_and_relative_paths() {
+ fn create_relative_tmp_file(
+ directory: &TempDir,
+ filename: &'static str,
+ filecontent: &'static str,
+ ) -> std::path::PathBuf {
+ let absolute_path = directory.path().join(filename);
+ std::fs::write(&absolute_path, filecontent).expect("error writing file");
+ let relative_path = absolute_path
+ .strip_prefix(util::root_path())
+ .expect("unable to create relative temporary file")
+ .to_owned();
+ assert!(relative_path.is_relative());
+ relative_path
+ }
+ let temp_directory =
+ TempDir::new_in(util::root_path()).expect("tempdir fail");
+ let file_to_watch = create_relative_tmp_file(
+ &temp_directory,
+ "file_to_watch.js",
+ "console.log('Hello world');",
+ );
+ let import_map_path = create_relative_tmp_file(
+ &temp_directory,
+ "import_map.json",
+ "{\"imports\": {}}",
+ );
+
+ let mut child = util::deno_cmd()
+ .current_dir(util::root_path())
+ .arg("run")
+ .arg("--unstable")
+ .arg("--watch")
+ .arg("--import-map")
+ .arg(&import_map_path)
+ .arg(&file_to_watch)
+ .env("NO_COLOR", "1")
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .expect("failed to spawn script");
+
+ let stdout = child.stdout.as_mut().unwrap();
+ let mut stdout_lines =
+ std::io::BufReader::new(stdout).lines().map(|r| r.unwrap());
+ let stderr = child.stderr.as_mut().unwrap();
+ let mut stderr_lines =
+ std::io::BufReader::new(stderr).lines().map(|r| r.unwrap());
+
+ assert!(stderr_lines.next().unwrap().contains("Process finished"));
+ assert!(stdout_lines.next().unwrap().contains("Hello world"));
+
+ child.kill().unwrap();
+
+ drop(file_to_watch);
+ drop(import_map_path);
+ temp_directory.close().unwrap();
+}
+
+// TODO(bartlomieju): flaky (https://github.com/denoland/deno/issues/10552)
+#[ignore]
+#[test]
+fn test_watch() {
+ macro_rules! assert_contains {
+ ($string:expr, $($test:expr),+) => {
+ let string = $string; // This might be a function call or something
+ if !($(string.contains($test))||+) {
+ panic!("{:?} does not contain any of {:?}", string, [$($test),+]);
+ }
+ }
+ }
+
+ let t = TempDir::new().expect("tempdir fail");
+
+ let mut child = util::deno_cmd()
+ .current_dir(util::root_path())
+ .arg("test")
+ .arg("--watch")
+ .arg("--unstable")
+ .arg("--no-check")
+ .arg(&t.path())
+ .env("NO_COLOR", "1")
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .expect("failed to spawn script");
+
+ let stdout = child.stdout.as_mut().unwrap();
+ let mut stdout_lines =
+ std::io::BufReader::new(stdout).lines().map(|r| r.unwrap());
+ let stderr = child.stderr.as_mut().unwrap();
+ let mut stderr_lines =
+ std::io::BufReader::new(stderr).lines().map(|r| r.unwrap());
+
+ assert_contains!(
+ stdout_lines.next().unwrap(),
+ "No matching test modules found"
+ );
+ wait_for_process_finished("Test", &mut stderr_lines);
+
+ let foo_file = t.path().join("foo.js");
+ let bar_file = t.path().join("bar.js");
+ let foo_test = t.path().join("foo_test.js");
+ let bar_test = t.path().join("bar_test.js");
+ std::fs::write(&foo_file, "export default function foo() { 1 + 1 }")
+ .expect("error writing file");
+ std::fs::write(&bar_file, "export default function bar() { 2 + 2 }")
+ .expect("error writing file");
+ std::fs::write(
+ &foo_test,
+ "import foo from './foo.js'; Deno.test('foo', foo);",
+ )
+ .expect("error writing file");
+ std::fs::write(
+ &bar_test,
+ "import bar from './bar.js'; Deno.test('bar', bar);",
+ )
+ .expect("error writing file");
+
+ 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_for_process_finished("Test", &mut stderr_lines);
+
+ // Change content of the file
+ std::fs::write(
+ &foo_test,
+ "import foo from './foo.js'; Deno.test('foobar', foo);",
+ )
+ .expect("error writing file");
+
+ 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_for_process_finished("Test", &mut stderr_lines);
+
+ // Add test
+ let another_test = t.path().join("new_test.js");
+ std::fs::write(&another_test, "Deno.test('another one', () => 3 + 3)")
+ .expect("error writing file");
+ 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_for_process_finished("Test", &mut stderr_lines);
+
+ // Confirm that restarting occurs when a new file is updated
+ std::fs::write(&another_test, "Deno.test('another one', () => 3 + 3); Deno.test('another another one', () => 4 + 4)")
+ .expect("error writing file");
+ 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_for_process_finished("Test", &mut stderr_lines);
+
+ // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax
+ std::fs::write(&another_test, "syntax error ^^").expect("error writing file");
+ assert_contains!(stderr_lines.next().unwrap(), "Restarting");
+ assert_contains!(stderr_lines.next().unwrap(), "error:");
+ assert_contains!(stderr_lines.next().unwrap(), "Test failed");
+
+ // Then restore the file
+ std::fs::write(&another_test, "Deno.test('another one', () => 3 + 3)")
+ .expect("error writing file");
+ 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_for_process_finished("Test", &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
+ std::fs::write(
+ &foo_file,
+ "export default function foo() { throw new Error('Whoops!'); }",
+ )
+ .expect("error writing file");
+ assert_contains!(stderr_lines.next().unwrap(), "Restarting");
+ assert_contains!(stdout_lines.next().unwrap(), "running 1 test");
+ assert_contains!(stdout_lines.next().unwrap(), "FAILED");
+ while !stdout_lines.next().unwrap().contains("test result") {}
+ stdout_lines.next();
+ wait_for_process_finished("Test", &mut stderr_lines);
+
+ // Then restore the file
+ std::fs::write(&foo_file, "export default function foo() { 1 + 1 }")
+ .expect("error writing file");
+ 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_for_process_finished("Test", &mut stderr_lines);
+
+ // Test that circular dependencies work fine
+ std::fs::write(
+ &foo_file,
+ "import './bar.js'; export default function foo() { 1 + 1 }",
+ )
+ .expect("error writing file");
+ std::fs::write(
+ &bar_file,
+ "import './foo.js'; export default function bar() { 2 + 2 }",
+ )
+ .expect("error writing file");
+
+ // the watcher process is still alive
+ assert!(child.try_wait().unwrap().is_none());
+
+ child.kill().unwrap();
+ drop(t);
+}