diff options
author | Liam Murphy <liampm32@gmail.com> | 2021-05-10 16:06:13 +1000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-10 08:06:13 +0200 |
commit | 7a9ebd15852eee7b676a671098d63bece679e0f7 (patch) | |
tree | 188c5b64a0a30f6c62382aef80b32eec98d91bc1 /cli/tests/integration_tests.rs | |
parent | 84733d90c7cf9b6768acf78f4204b2293f2d1bc0 (diff) |
feat: add deno test --watch (#9160)
This commit implements file watching for deno test.
When a file is changed, only the test modules which
use it as a dependency are rerun.
This is accomplished by reworking the file watching infrastructure
to pass the paths which have changed to the resolver, and then
constructing a module graph for each test module to check if it
contains any changed files.
Diffstat (limited to 'cli/tests/integration_tests.rs')
-rw-r--r-- | cli/tests/integration_tests.rs | 200 |
1 files changed, 190 insertions, 10 deletions
diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index f19966fdf..674f42f87 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -617,6 +617,21 @@ mod integration { } } + /// 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"); @@ -685,6 +700,7 @@ mod integration { .arg(&bundle) .arg("--watch") .arg("--unstable") + .env("NO_COLOR", "1") .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .spawn() @@ -722,11 +738,8 @@ mod integration { .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); + 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()); @@ -762,9 +775,8 @@ mod integration { 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("error:")); - assert!(stderr_lines.next().unwrap().contains("Bundle failed!")); + assert!(stderr_lines.next().unwrap().contains("Bundle failed")); // the target file hasn't been created yet assert!(!target_file.is_file()); @@ -858,7 +870,7 @@ mod integration { 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_finished("Process", &mut stderr_lines); + wait_for_process_failed("Process", &mut stderr_lines); // Then restore the file std::fs::write( @@ -877,7 +889,7 @@ mod integration { 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_finished("Process", &mut stderr_lines); + 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!';") @@ -923,7 +935,7 @@ mod integration { std::thread::sleep(std::time::Duration::from_secs(1)); assert!(stderr_lines.next().unwrap().contains("error:")); - assert!(stderr_lines.next().unwrap().contains("Process failed!")); + 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);") @@ -999,6 +1011,174 @@ mod integration { drop(import_map_path); temp_directory.close().unwrap(); } + + #[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); + } } #[test] |