1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00
denoland-deno/cli/tests/integration/watcher_tests.rs
2021-07-10 01:15:15 +02:00

671 lines
22 KiB
Rust

// 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);
}
// TODO(bartlomieju): flaky (https://github.com/denoland/deno/issues/10552)
#[ignore]
#[test]
fn test_watch_doc() {
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("--doc")
.arg("--unstable")
.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.ts");
std::fs::write(
&foo_file,
r#"
export default function foo() {}
"#,
)
.expect("error writing file");
std::fs::write(
&foo_file,
r#"
/**
* ```ts
* import foo from "./foo.ts";
* ```
*/
export default function foo() {}
"#,
)
.expect("error writing file");
// 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(stderr_lines), "foo.ts$3-6");
assert!(child.try_wait().unwrap().is_none());
child.kill().unwrap();
drop(t);
}